├── .gitignore ├── Dockerfiles ├── Dockerfile.latest ├── Dockerfile.stable ├── Dockerfile.alpine └── data │ └── docker-entrypoint.sh ├── .github ├── FUNDING.yml ├── dependabot.yml ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── report.yml │ ├── feature_request.yml │ ├── documentation.yml │ ├── howto.yml │ └── bug_report.yml ├── workflows │ ├── release-drafter.yml │ ├── repository.yml │ ├── lint.yml │ ├── action_pull_request.yml │ ├── action_schedule.yml │ ├── lint-generic.yml │ ├── action_branch.yml │ └── params.yml ├── release-drafter.yml └── labels.yml ├── .yamllint ├── LICENSE.md ├── tests ├── start-ci.sh ├── 01-run.sh ├── 07-docker-logs.sh ├── 02-DNS_A_WILDCARD.sh ├── 03-DNS_CNAME_WILDCARD.sh ├── .lib.sh ├── 04-DNS_A.sh └── 05-DNS_CNAME.sh ├── Makefile └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | Makefile.docker 2 | Makefile.lint 3 | -------------------------------------------------------------------------------- /Dockerfiles/Dockerfile.latest: -------------------------------------------------------------------------------- 1 | Dockerfile.stable -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | --- 2 | github: [cytopia] 3 | patreon: devilbox 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 2 3 | updates: 4 | # Maintain dependencies for GitHub Actions 5 | - package-ecosystem: "github-actions" 6 | directory: "/" 7 | schedule: 8 | interval: "daily" 9 | -------------------------------------------------------------------------------- /.yamllint: -------------------------------------------------------------------------------- 1 | --- 2 | extends: default 3 | 4 | ignore: | 5 | .yamllint 6 | 7 | 8 | rules: 9 | truthy: 10 | allowed-values: ['true', 'false'] 11 | check-keys: False 12 | level: error 13 | line-length: disable 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | blank_issues_enabled: false 3 | contact_links: 4 | - name: Devilbox Discord Chat 5 | url: https://discord.gg/2wP3V6kBj4 6 | about: Please notify or discuss about any other requests here. 7 | - name: Devilbox Discourse Forum 8 | url: https://devilbox.discourse.group/ 9 | about: Please ask and answer general questions here. 10 | - name: Devilbox documentation 11 | url: https://devilbox.readthedocs.io/ 12 | about: Find the Devilbox documentation here. 13 | -------------------------------------------------------------------------------- /.github/workflows/release-drafter.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Release Drafter 3 | 4 | on: 5 | push: 6 | # branches to consider in the event; optional, defaults to all 7 | branches: 8 | - master 9 | 10 | jobs: 11 | update_release_draft: 12 | runs-on: ubuntu-latest 13 | steps: 14 | # Drafts your next Release notes as Pull Requests are merged into "master" 15 | - uses: release-drafter/release-drafter@v5 16 | with: 17 | publish: true 18 | env: 19 | GITHUB_TOKEN: ${{ secrets.RELEASE_DRAFTER_TOKEN }} 20 | -------------------------------------------------------------------------------- /.github/workflows/repository.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Repository 3 | 4 | on: 5 | push: 6 | branches: 7 | - master 8 | paths: 9 | - .github/labels.yml 10 | 11 | jobs: 12 | labels: 13 | name: Labels 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout code 18 | uses: actions/checkout@v3 19 | 20 | - name: Sync labels 21 | uses: micnncim/action-label-syncer@v1 22 | env: 23 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 24 | with: 25 | manifest: .github/labels.yml 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/report.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "🕬 Report" 3 | description: Report something to the maintainer 4 | title: "[Report]: " 5 | labels: ["report", "status:triage"] 6 | assignees: 7 | - cytopia 8 | 9 | body: 10 | 11 | - type: markdown 12 | attributes: 13 | value: | 14 | Report something to the maintainer such as versions are outdated or pipelines are not running, etc. For anything else please use other issue types. Thanks for taking the time! 15 | 16 | - type: textarea 17 | attributes: 18 | label: Report 19 | description: What do you want to report? 20 | validations: 21 | required: true 22 | -------------------------------------------------------------------------------- /Dockerfiles/Dockerfile.stable: -------------------------------------------------------------------------------- 1 | FROM debian:stable-slim 2 | LABEL org.opencontainers.image.authors="cytopia@everythingcli.org" 3 | 4 | ENV \ 5 | USER=bind \ 6 | GROUP=bind 7 | 8 | ### 9 | ### Install 10 | ### 11 | RUN set -eux \ 12 | && apt update \ 13 | && apt install --no-install-recommends --no-install-suggests -y \ 14 | bind9 \ 15 | && apt purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false \ 16 | && rm -r /var/lib/apt/lists/* \ 17 | && mkdir /var/log/named \ 18 | && chown bind:bind /var/log/named \ 19 | && chmod 0755 /var/log/named 20 | 21 | 22 | ### 23 | ### Bootstrap Scipts 24 | ### 25 | COPY ./data/docker-entrypoint.sh / 26 | 27 | 28 | ### 29 | ### Ports 30 | ### 31 | EXPOSE 53 32 | EXPOSE 53/udp 33 | 34 | 35 | #### 36 | #### Entrypoint 37 | #### 38 | ENTRYPOINT ["/docker-entrypoint.sh"] 39 | -------------------------------------------------------------------------------- /.github/release-drafter.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name-template: '$RESOLVED_VERSION 🌈' 3 | tag-template: '$RESOLVED_VERSION' 4 | version-template: '$MAJOR.$MINOR' 5 | categories: 6 | - title: '🚀 Features' 7 | labels: 8 | - 'feature' 9 | - 'enhancement' 10 | - title: '🐛 Bug Fixes' 11 | labels: 12 | - 'fix' 13 | - 'bugfix' 14 | - 'bug' 15 | - title: '🧰 Maintenance' 16 | label: 'chore' 17 | change-template: '- $TITLE @$AUTHOR (#$NUMBER)' 18 | change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks. 19 | version-resolver: 20 | major: 21 | labels: 22 | - 'major' 23 | minor: 24 | labels: 25 | - 'minor' 26 | patch: 27 | labels: 28 | - 'patch' 29 | default: minor 30 | template: | 31 | ## Changes 32 | 33 | $CHANGES 34 | -------------------------------------------------------------------------------- /Dockerfiles/Dockerfile.alpine: -------------------------------------------------------------------------------- 1 | FROM alpine 2 | LABEL org.opencontainers.image.authors="cytopia@everythingcli.org" 3 | 4 | ENV \ 5 | USER=named \ 6 | GROUP=named 7 | 8 | ### 9 | ### Install 10 | ### 11 | RUN set -x \ 12 | && apk add --no-cache \ 13 | bash \ 14 | bind \ 15 | bind-tools \ 16 | # Log directory 17 | && mkdir /var/log/named \ 18 | && chown ${USER}:${GROUP} /var/log/named \ 19 | && chmod 0755 /var/log/named \ 20 | # Cache directory 21 | && mkdir /var/cache/bind \ 22 | && chown root:${GROUP} /var/cache/bind \ 23 | && chmod 0775 /var/cache/bind 24 | 25 | 26 | 27 | ### 28 | ### Bootstrap Scipts 29 | ### 30 | COPY ./data/docker-entrypoint.sh / 31 | 32 | 33 | ### 34 | ### Ports 35 | ### 36 | EXPOSE 53 37 | EXPOSE 53/udp 38 | 39 | 40 | #### 41 | #### Entrypoint 42 | #### 43 | ENTRYPOINT ["/docker-entrypoint.sh"] 44 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # ------------------------------------------------------------------------------------------------- 4 | # Job Name 5 | # ------------------------------------------------------------------------------------------------- 6 | name: lint 7 | 8 | 9 | # ------------------------------------------------------------------------------------------------- 10 | # When to run 11 | # ------------------------------------------------------------------------------------------------- 12 | on: 13 | # Runs on Pull Requests 14 | pull_request: 15 | 16 | 17 | # ------------------------------------------------------------------------------------------------- 18 | # What to run 19 | # ------------------------------------------------------------------------------------------------- 20 | jobs: 21 | lint: 22 | #uses: devilbox/github-actions/.github/workflows/lint-generic.yml@master 23 | uses: ./.github/workflows/lint-generic.yml 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "✨ Feature request" 3 | description: Suggest an idea or feature for this project 4 | title: "[Feature]: " 5 | labels: ["feature", "status:triage"] 6 | assignees: [cytopia] 7 | 8 | body: 9 | 10 | - type: textarea 11 | attributes: 12 | label: What is your idea or feature suggestion? 13 | description: Tell us, what idea or feature you suggest to be added. 14 | validations: 15 | required: true 16 | 17 | - type: textarea 18 | attributes: 19 | label: Benefits 20 | description: Tell us, how this will be beneficial. 21 | validations: 22 | required: false 23 | 24 | - type: textarea 25 | attributes: 26 | label: Where can we find information about this? 27 | description: If you are proposing a software or tool, please add relevant links and documentation. 28 | validations: 29 | required: false 30 | 31 | - type: dropdown 32 | attributes: 33 | label: Are you willing to provide a PR to address this? 34 | options: 35 | - "Yes" 36 | - "No" 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F4DD Documentation" 3 | description: Something is missing, unclear or wrong in the documentation. 4 | title: "[Docs]: " 5 | labels: ["documentation", "status:triage"] 6 | assignees: 7 | - cytopia 8 | 9 | body: 10 | 11 | - type: textarea 12 | attributes: 13 | label: What is wrong in the documentation? 14 | description: Tell us, what is wrong in the documentation? 15 | validations: 16 | required: false 17 | 18 | - type: textarea 19 | attributes: 20 | label: What is unclear in the documentation? 21 | description: Tell us, what is unclear in the documentation? 22 | validations: 23 | required: false 24 | 25 | - type: textarea 26 | attributes: 27 | label: What is missing in the documentation? 28 | description: Tell us, what is missing in the documentation? 29 | validations: 30 | required: false 31 | 32 | - type: dropdown 33 | attributes: 34 | label: Are you willing to provide a PR to address this? 35 | options: 36 | - "Yes" 37 | - "No" 38 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 cytopia 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/action_pull_request.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # ------------------------------------------------------------------------------------------------- 4 | # Job Name 5 | # ------------------------------------------------------------------------------------------------- 6 | name: build 7 | 8 | 9 | # ------------------------------------------------------------------------------------------------- 10 | # When to run 11 | # ------------------------------------------------------------------------------------------------- 12 | on: 13 | pull_request: 14 | 15 | 16 | jobs: 17 | 18 | # (1/2) Determine repository params 19 | params: 20 | uses: ./.github/workflows/params.yml 21 | # Only run for forks (contributor) 22 | if: github.event.pull_request.head.repo.fork 23 | 24 | # (2/2) Build 25 | docker: 26 | needs: [params] 27 | uses: devilbox/github-actions/.github/workflows/docker-name-version-flavour-arch.yml@master 28 | with: 29 | enabled: true 30 | can_deploy: false 31 | matrix: ${{ needs.params.outputs.matrix }} 32 | refs: ${{ needs.params.outputs.refs }} 33 | secrets: 34 | dockerhub_username: "" 35 | dockerhub_password: "" 36 | -------------------------------------------------------------------------------- /.github/workflows/action_schedule.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # ------------------------------------------------------------------------------------------------- 4 | # Job Name 5 | # ------------------------------------------------------------------------------------------------- 6 | name: nightly 7 | 8 | 9 | # ------------------------------------------------------------------------------------------------- 10 | # When to run 11 | # ------------------------------------------------------------------------------------------------- 12 | on: 13 | # Runs daily 14 | schedule: 15 | - cron: '0 0 * * *' 16 | 17 | 18 | jobs: 19 | 20 | # (1/2) Determine repository params 21 | params: 22 | uses: ./.github/workflows/params.yml 23 | 24 | # (2/2) Build 25 | docker: 26 | needs: [params] 27 | uses: devilbox/github-actions/.github/workflows/docker-name-version-flavour-arch.yml@master 28 | with: 29 | enabled: true 30 | can_deploy: true 31 | matrix: ${{ needs.params.outputs.matrix }} 32 | refs: ${{ needs.params.outputs.refs }} 33 | secrets: 34 | dockerhub_username: ${{ secrets.DOCKERHUB_USERNAME }} 35 | dockerhub_password: ${{ secrets.DOCKERHUB_PASSWORD }} 36 | -------------------------------------------------------------------------------- /.github/workflows/lint-generic.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: 4 | workflow_call: 5 | 6 | jobs: 7 | 8 | # ----------------------------------------------------------------------------------------------- 9 | # JOB (1/1): Lint 10 | # ----------------------------------------------------------------------------------------------- 11 | lint: 12 | name: lint 13 | runs-on: ubuntu-latest 14 | steps: 15 | 16 | - name: "[SETUP] Checkout repository" 17 | uses: actions/checkout@v3 18 | with: 19 | fetch-depth: 0 20 | 21 | - name: Lint Files 22 | uses: cytopia/shell-command-retry-action@v0.1.6 23 | with: 24 | command: | 25 | make lint-files 26 | 27 | - name: Lint Yaml 28 | uses: cytopia/shell-command-retry-action@v0.1.6 29 | with: 30 | command: | 31 | make lint-yaml 32 | 33 | - name: Lint JSON 34 | uses: cytopia/shell-command-retry-action@v0.1.6 35 | with: 36 | command: | 37 | make lint-json 38 | 39 | - name: Lint Bash 40 | uses: cytopia/shell-command-retry-action@v0.1.6 41 | with: 42 | command: | 43 | make lint-bash 44 | -------------------------------------------------------------------------------- /.github/workflows/action_branch.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # ------------------------------------------------------------------------------------------------- 4 | # Job Name 5 | # ------------------------------------------------------------------------------------------------- 6 | name: build 7 | 8 | 9 | # ------------------------------------------------------------------------------------------------- 10 | # When to run 11 | # ------------------------------------------------------------------------------------------------- 12 | on: 13 | push: 14 | paths: 15 | - 'Makefile' 16 | - 'Dockerfiles/**' 17 | - 'tests/**' 18 | - '.github/workflows/action*.yml' 19 | - '.github/workflows/params.yml' 20 | 21 | jobs: 22 | 23 | # (1/2) Determine repository params 24 | params: 25 | uses: ./.github/workflows/params.yml 26 | 27 | # (2/2) Build 28 | docker: 29 | needs: [params] 30 | uses: devilbox/github-actions/.github/workflows/docker-name-version-flavour-arch.yml@master 31 | with: 32 | enabled: true 33 | can_deploy: ${{ github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/') || startsWith(github.ref, 'refs/heads/release-') }} 34 | matrix: ${{ needs.params.outputs.matrix }} 35 | refs: ${{ needs.params.outputs.refs }} 36 | secrets: 37 | dockerhub_username: ${{ secrets.DOCKERHUB_USERNAME }} 38 | dockerhub_password: ${{ secrets.DOCKERHUB_PASSWORD }} 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/howto.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "❔ Question" 3 | description: How do I do X or Y? 4 | title: "[Question]: " 5 | labels: ["question", "status:triage"] 6 | assignees: [cytopia] 7 | 8 | body: 9 | 10 | - type: markdown 11 | attributes: 12 | value: | 13 | If this is a generic question, please consider using the [Discord Chat](https://discord.gg/2wP3V6kBj4) or the [Devilbox forum](https://devilbox.discourse.group/) instead. 14 | 15 | - type: checkboxes 16 | attributes: 17 | label: Have you already checked elsewhere? 18 | description: You may select more than one. 19 | options: 20 | - label: I have checked existing issues 21 | - label: I have googled already with no luck 22 | - label: I have not done any of the above 23 | validations: 24 | required: true 25 | 26 | - type: textarea 27 | attributes: 28 | label: What is your question? 29 | description: Tell the community, what your question is. Be as specific as possible to make it easier for other people to answer your question. 30 | validations: 31 | required: true 32 | 33 | - type: textarea 34 | attributes: 35 | label: What have you tried already? 36 | description: Add some details on what you have tried already, so this can be ruled out. 37 | validations: 38 | required: true 39 | 40 | - type: textarea 41 | attributes: 42 | label: What is your goal? 43 | description: Tell the community, what you want to accomplish? This might be helpful to know in order to prevent [XY problems](https://en.wikipedia.org/wiki/XY_problem). 44 | validations: 45 | required: true 46 | -------------------------------------------------------------------------------- /tests/start-ci.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | set -u 5 | set -o pipefail 6 | 7 | ### 8 | ### Variables 9 | ### 10 | 11 | IFS=$'\n' 12 | 13 | # Current directory 14 | CWD="$( dirname "${0}" )" 15 | IMAGE="${1}" 16 | NAME="${2}" 17 | VERSION="${3}" 18 | TAG="${4}" 19 | ARCH="${5}" 20 | DEBUG="${6}" 21 | 22 | declare -a TESTS=() 23 | 24 | 25 | 26 | 27 | ### 28 | ### Run tests 29 | ### 30 | 31 | # Get all [0-9]+.sh test files 32 | FILES="$( find "${CWD}" -regex "${CWD}/[0-9].+\.sh" | sort -u )" 33 | for f in ${FILES}; do 34 | TESTS+=("${f}") 35 | done 36 | 37 | # Start a single test 38 | if [ "${#}" -eq "3" ]; then 39 | sh -c "${TESTS[${2}]} ${IMAGE} ${NAME} ${VERSION} ${TAG} ${ARCH}" 40 | 41 | # Run all tests 42 | else 43 | for i in "${TESTS[@]}"; do 44 | echo "########################################################################################################################" 45 | echo "########################################################################################################################" 46 | echo "###" 47 | echo "### [${CWD}/${i}] ${IMAGE}:${TAG} ${NAME}-${VERSION} (${ARCH})" 48 | echo "###" 49 | echo "########################################################################################################################" 50 | echo "########################################################################################################################" 51 | if ! sh -c "${i} ${IMAGE} ${NAME} ${VERSION} ${TAG} ${ARCH} ${DEBUG}"; then 52 | if ! sh -c "${i} ${IMAGE} ${NAME} ${VERSION} ${TAG} ${ARCH} ${DEBUG}"; then 53 | if ! sh -c "${i} ${IMAGE} ${NAME} ${VERSION} ${TAG} ${ARCH} ${DEBUG}"; then 54 | echo "Failed after 3 repeats" 55 | exit 1 56 | fi 57 | fi 58 | fi 59 | printf "\\n\\n" 60 | done 61 | fi 62 | -------------------------------------------------------------------------------- /tests/01-run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | set -u 5 | 6 | # Current directory 7 | CWD="$(cd -P -- "$(dirname -- "$0")" && pwd -P)" 8 | # shellcheck disable=SC1090 9 | . "${CWD}/.lib.sh" 10 | 11 | IMAGE="${1}" 12 | #NAME="${2}" 13 | #VERSION="${3}" 14 | TAG="${4}" 15 | ARCH="${5}" 16 | DEBUG="${6}" 17 | 18 | NAME="bind$( shuf -i 1000000000-2000000000 -n 1 )" 19 | WAIT=10 20 | 21 | 22 | # DEBUG_ENTRYPOINT=2 23 | run "docker run -d --rm --platform ${ARCH} --name ${NAME} -e DEBUG=${DEBUG} -e DEBUG_ENTRYPOINT=2 ${IMAGE}:${TAG}" 24 | run "sleep ${WAIT}" 25 | sanity_check "${NAME}" 26 | if ! run "docker exec ${NAME} named -V"; then 27 | run "docker stop ${NAME}" 28 | exit 1 29 | fi 30 | if ! run "docker exec ${NAME} named -V | grep -oiE '^BIND[[:space:]]+[0-9.]+'"; then 31 | run "docker stop ${NAME}" 32 | exit 1 33 | fi 34 | docker_stop "${NAME}" 35 | 36 | 37 | # DEBUG_ENTRYPOINT=1 38 | run "docker run -d --rm --platform ${ARCH} --name ${NAME} -e DEBUG=${DEBUG} -e DEBUG_ENTRYPOINT=1 ${IMAGE}:${TAG}" 39 | run "sleep ${WAIT}" 40 | sanity_check "${NAME}" 41 | if ! run "docker exec ${NAME} named -V"; then 42 | run "docker stop ${NAME}" 43 | exit 1 44 | fi 45 | if ! run "docker exec ${NAME} named -V | grep -oiE '^BIND[[:space:]]+[0-9.]+'"; then 46 | run "docker stop ${NAME}" 47 | exit 1 48 | fi 49 | docker_stop "${NAME}" 50 | 51 | 52 | # DEBUG_ENTRYPOINT=0 53 | run "docker run -d --rm --platform ${ARCH} --name ${NAME} -e DEBUG=${DEBUG} -e DEBUG_ENTRYPOINT=1 ${IMAGE}:${TAG}" 54 | run "sleep ${WAIT}" 55 | sanity_check "${NAME}" 56 | if ! run "docker exec ${NAME} named -V"; then 57 | run "docker stop ${NAME}" 58 | exit 1 59 | fi 60 | if ! run "docker exec ${NAME} named -V | grep -oiE '^BIND[[:space:]]+[0-9.]+'"; then 61 | run "docker stop ${NAME}" 62 | exit 1 63 | fi 64 | docker_stop "${NAME}" 65 | 66 | 67 | # DEBUG_ENTRYPOINT=null 68 | run "docker run -d --rm --platform ${ARCH} --name ${NAME} -e DEBUG=${DEBUG} ${IMAGE}:${TAG}" 69 | run "sleep ${WAIT}" 70 | sanity_check "${NAME}" 71 | if ! run "docker exec ${NAME} named -V"; then 72 | run "docker stop ${NAME}" 73 | exit 1 74 | fi 75 | if ! run "docker exec ${NAME} named -V | grep -oiE '^BIND[[:space:]]+[0-9.]+'"; then 76 | run "docker stop ${NAME}" 77 | exit 1 78 | fi 79 | docker_stop "${NAME}" 80 | -------------------------------------------------------------------------------- /tests/07-docker-logs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | set -u 5 | 6 | # Current directory 7 | CWD="$(cd -P -- "$(dirname -- "$0")" && pwd -P)" 8 | # shellcheck disable=SC1090 9 | . "${CWD}/.lib.sh" 10 | 11 | IMAGE="${1}" 12 | #NAME="${2}" 13 | #VERSION="${3}" 14 | TAG="${4}" 15 | ARCH="${5}" 16 | DEBUG="${6}" 17 | 18 | NAME="bind$( shuf -i 1000000000-2000000000 -n 1 )" 19 | PORT="5300" 20 | WAIT=5 21 | REPS=10 22 | 23 | 24 | # DEBUG_ENTRYPOINT=2 25 | run "docker run -d --rm --platform ${ARCH} --name ${NAME} -e DEBUG=${DEBUG} -e DEBUG_ENTRYPOINT=2 -e 'DNS_CNAME=www.devilbox=google.com' -e DOCKER_LOGS=1 -p ${PORT}:53/udp ${IMAGE}:${TAG}" 26 | run "sleep ${WAIT}" 27 | sanity_check "${NAME}" 28 | run "dig @127.0.0.1 -p ${PORT} +short www.devilbox || true" 29 | if ! run "docker logs ${NAME} 2>&1 | grep -E 'query:\s+www\.devilbox\s+IN'"; then 30 | run "docker logs ${NAME}" || true 31 | run "docker stop ${NAME}" 32 | exit 1 33 | fi 34 | docker_stop "${NAME}" 35 | 36 | 37 | # DEBUG_ENTRYPOINT=1 38 | run "docker run -d --rm --platform ${ARCH} --name ${NAME} -e DEBUG=${DEBUG} -e DEBUG_ENTRYPOINT=1 -e 'DNS_CNAME=www.devilbox=google.com' -e DOCKER_LOGS=1 -p ${PORT}:53/udp ${IMAGE}:${TAG}" 39 | run "sleep ${WAIT}" 40 | sanity_check "${NAME}" 41 | run "dig @127.0.0.1 -p ${PORT} +short www.devilbox || true" 42 | if ! run "docker logs ${NAME} 2>&1 | grep -E 'query:\s+www\.devilbox\s+IN'"; then 43 | run "docker logs ${NAME}" || true 44 | run "docker stop ${NAME}" 45 | exit 1 46 | fi 47 | docker_stop "${NAME}" 48 | 49 | 50 | # DEBUG_ENTRYPOINT=0 51 | run "docker run -d --rm --platform ${ARCH} --name ${NAME} -e DEBUG=${DEBUG} -e DEBUG_ENTRYPOINT=0 -e 'DNS_CNAMES=www.devilbox=google.com' -e DOCKER_LOGS=1 -p ${PORT}:53/udp ${IMAGE}:${TAG}" 52 | run "sleep ${WAIT}" 53 | sanity_check "${NAME}" 54 | run "dig @127.0.0.1 -p ${PORT} +short www.devilbox || true" 55 | if ! run "docker logs ${NAME} 2>&1 | grep -E 'query:\s+www\.devilbox\s+IN'"; then 56 | run "docker logs ${NAME}" || true 57 | run "docker stop ${NAME}" 58 | exit 1 59 | fi 60 | docker_stop "${NAME}" 61 | 62 | 63 | # DEBUG_ENTRYPOINT=null 64 | run "docker run -d --rm --platform ${ARCH} --name ${NAME} -e DEBUG=${DEBUG} -e 'DNS_CNAME=www.devilbox=google.com' -e DOCKER_LOGS=1 -p ${PORT}:53/udp ${IMAGE}:${TAG}" 65 | run "sleep ${WAIT}" 66 | sanity_check "${NAME}" 67 | run "dig @127.0.0.1 -p ${PORT} +short www.devilbox || true" 68 | if ! run "docker logs ${NAME} 2>&1 | grep -E 'query:\s+www\.devilbox\s+IN'"; then 69 | run "docker logs ${NAME}" || true 70 | run "docker stop ${NAME}" 71 | exit 1 72 | fi 73 | docker_stop "${NAME}" 74 | -------------------------------------------------------------------------------- /.github/workflows/params.yml: -------------------------------------------------------------------------------- 1 | --- 2 | 3 | # ------------------------------------------------------------------------------------------------- 4 | # Job Name 5 | # ------------------------------------------------------------------------------------------------- 6 | name: params 7 | 8 | 9 | # ------------------------------------------------------------------------------------------------- 10 | # Custom Variables 11 | # ------------------------------------------------------------------------------------------------- 12 | env: 13 | MATRIX: >- 14 | [ 15 | { 16 | "NAME": "Bind", 17 | "VERSION": ["9"], 18 | "FLAVOUR": ["stable", "latest"], 19 | "ARCH": ["linux/amd64", "linux/386", "linux/arm64", "linux/arm/v7", "linux/arm/v6", "linux/ppc64le", "linux/s390x", "linux/mips64le"] 20 | }, 21 | { 22 | "NAME": "Bind", 23 | "VERSION": ["9"], 24 | "FLAVOUR": ["alpine"], 25 | "ARCH": ["linux/amd64", "linux/386", "linux/arm64", "linux/arm/v7", "linux/arm/v6", "linux/ppc64le", "linux/s390x"] 26 | } 27 | ] 28 | 29 | 30 | # ------------------------------------------------------------------------------------------------- 31 | # When to run 32 | # ------------------------------------------------------------------------------------------------- 33 | on: 34 | workflow_call: 35 | outputs: 36 | matrix: 37 | description: "The determined version matrix" 38 | value: ${{ jobs.params.outputs.matrix }} 39 | refs: 40 | description: "The determined git ref matrix (only during scheduled run)" 41 | value: ${{ jobs.params.outputs.refs }} 42 | 43 | jobs: 44 | params: 45 | runs-on: ubuntu-latest 46 | 47 | outputs: 48 | matrix: ${{ steps.set-matrix.outputs.matrix }} 49 | refs: ${{ steps.set-refs.outputs.matrix }} 50 | 51 | steps: 52 | - name: "[Set-Output] Matrix" 53 | id: set-matrix 54 | run: | 55 | echo "matrix=$( echo '${{ env.MATRIX }}' | jq -M -c )" >> $GITHUB_OUTPUT 56 | 57 | - name: "[Set-Output] Matrix 'Refs' (master branch and latest tag)" 58 | id: set-refs 59 | uses: cytopia/git-ref-matrix-action@v0.1.13 60 | with: 61 | repository_default_branch: master 62 | branches: master 63 | num_latest_tags: 1 64 | if: github.event_name == 'schedule' 65 | 66 | - name: "[DEBUG] Show settings'" 67 | run: | 68 | echo 'Matrix' 69 | echo '--------------------' 70 | echo '${{ steps.set-matrix.outputs.matrix }}' 71 | echo 72 | 73 | echo 'Matrix: Refs' 74 | echo '--------------------' 75 | echo '${{ steps.set-matrix-refs.outputs.matrix }}' 76 | echo 77 | -------------------------------------------------------------------------------- /.github/labels.yml: -------------------------------------------------------------------------------- 1 | # The labels in this file are automatically synced with the repository 2 | # using the micnncim/action-label-syncer action. 3 | --- 4 | ### 5 | ### Pull Requests (Dependabot) 6 | ### 7 | - name: C-dependency 8 | color: 1abc9c 9 | description: "Category: Dependency" 10 | - name: PR-block 11 | color: 3498db 12 | description: "Pull Request: Do not merge" 13 | - name: PR-merge 14 | color: 3498db 15 | description: "Pull Request: Merge when ready" 16 | 17 | ### 18 | ### Pull Requests (Release Drafter) 19 | ### 20 | - name: major 21 | color: 1abc9c 22 | description: "Create major release" 23 | - name: minor 24 | color: 3498db 25 | description: "Create minor release" 26 | - name: patch 27 | color: 3498db 28 | description: "Create patch release" 29 | 30 | ### 31 | ### General Issues 32 | ### 33 | - name: bug 34 | description: "Bug Report" 35 | color: ee0701 36 | - name: report 37 | description: "General Report" 38 | color: ef561f 39 | - name: feature 40 | description: "Feature Request" 41 | color: dc8b10 42 | - name: question 43 | description: "General question" 44 | color: cc317c 45 | - name: documentation 46 | description: "Documentation related" 47 | color: 45b046 48 | 49 | ### 50 | ### Status: Issue progress 51 | ### 52 | - name: "status:triage" 53 | description: "Issue needs Triaging" 54 | color: f2a7cf 55 | - name: "status:confirmed" 56 | description: "Issue is confirmed" 57 | color: f2a7cf 58 | - name: "status:needs-more-info" 59 | description: "Issue needs more info" 60 | color: f2a7cf 61 | 62 | ### 63 | ### Close 64 | ### 65 | - name: "issue:invalid" 66 | description: "" 67 | color: f5c7fc 68 | - name: "issue:invalid-type" 69 | description: "" 70 | color: f5c7fc 71 | - name: "issue:duplicate" 72 | description: "" 73 | color: f5c7fc 74 | - name: "issue:wontfix" 75 | description: "" 76 | color: f5c7fc 77 | - name: "issue:stale" 78 | description: "" 79 | color: f5c7fc 80 | 81 | ### 82 | ### Issue types 83 | ### 84 | - name: "type:extension" 85 | description: "" 86 | color: 0c9cf2 87 | - name: "type:tool" 88 | description: "" 89 | color: 0c9cf2 90 | - name: "type:env-var" 91 | description: "" 92 | color: 0c9cf2 93 | - name: "type:config" 94 | description: "" 95 | color: 0c9cf2 96 | 97 | ### 98 | ### Issue Host specific 99 | ### 100 | - name: "host:linux" 101 | description: "" 102 | color: fbca04 103 | - name: "host:macos" 104 | description: "" 105 | color: fbca04 106 | - name: "host:windows" 107 | description: "" 108 | color: fbca04 109 | - name: "arch:amd64" 110 | description: "" 111 | color: fbca04 112 | - name: "arch:arm64" 113 | description: "" 114 | color: fbca04 115 | - name: "arch:other" 116 | description: "" 117 | color: fbca04 118 | -------------------------------------------------------------------------------- /tests/02-DNS_A_WILDCARD.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | set -u 5 | 6 | # Current directory 7 | CWD="$(cd -P -- "$(dirname -- "$0")" && pwd -P)" 8 | # shellcheck disable=SC1090 9 | . "${CWD}/.lib.sh" 10 | 11 | IMAGE="${1}" 12 | #NAME="${2}" 13 | #VERSION="${3}" 14 | TAG="${4}" 15 | ARCH="${5}" 16 | DEBUG="${6}" 17 | 18 | NAME="bind$( shuf -i 1000000000-2000000000 -n 1 )" 19 | PORT="5300" 20 | WAIT=5 21 | REPS=10 22 | 23 | 24 | # DEBUG_ENTRYPOINT=2 25 | run "docker run -d --rm --platform ${ARCH} --name ${NAME} -e DEBUG=${DEBUG} -e DEBUG_ENTRYPOINT=2 -e DOCKER_LOGS=1 -e 'DNS_A=*.devilbox=1.1.1.1' -p ${PORT}:53/udp ${IMAGE}:${TAG}" 26 | run "sleep ${WAIT}" 27 | sanity_check "${NAME}" 28 | i=0 29 | while ! run "dig @127.0.0.1 -p ${PORT} +short t1.devilbox | grep -E '^1\.1\.1\.1$'"; do 30 | i=$(( i + 1 )) 31 | if [ "${i}" -gt "${REPS}" ]; then 32 | echo "FAILED: t1.devilbox is not resolvable to 1.1.1.1" 33 | run "dig @127.0.0.1 -p ${PORT} +short t1.devilbox" 34 | run "docker logs ${NAME}" 35 | run "docker stop ${NAME}" 36 | echo "ABORT..." 37 | exit 1 38 | fi 39 | sleep 1 40 | done 41 | docker_stop "${NAME}" 42 | 43 | 44 | # DEBUG_ENTRYPOINT=1 45 | run "docker run -d --rm --platform ${ARCH} --name ${NAME} -e DEBUG=${DEBUG} -e DEBUG_ENTRYPOINT=1 -e DOCKER_LOGS=1 -e 'DNS_A=*.devilbox=1.1.1.1' -p ${PORT}:53/udp ${IMAGE}:${TAG}" 46 | run "sleep ${WAIT}" 47 | sanity_check "${NAME}" 48 | i=0 49 | while ! run "dig @127.0.0.1 -p ${PORT} +short t1.devilbox | grep -E '^1\.1\.1\.1$'"; do 50 | i=$(( i + 1 )) 51 | if [ "${i}" -gt "${REPS}" ]; then 52 | echo "FAILED: t1.devilbox is not resolvable to 1.1.1.1" 53 | run "dig @127.0.0.1 -p ${PORT} +short t1.devilbox" 54 | run "docker logs ${NAME}" 55 | run "docker stop ${NAME}" 56 | echo "ABORT..." 57 | exit 1 58 | fi 59 | sleep 1 60 | done 61 | docker_stop "${NAME}" 62 | 63 | 64 | # DEBUG_ENTRYPOINT=0 65 | run "docker run -d --rm --platform ${ARCH} --name ${NAME} -e DEBUG=${DEBUG} -e DEBUG_ENTRYPOINT=0 -e DOCKER_LOGS=1 -e 'DNS_A=*.devilbox=1.1.1.1' -p ${PORT}:53/udp ${IMAGE}:${TAG}" 66 | run "sleep ${WAIT}" 67 | sanity_check "${NAME}" 68 | i=0 69 | while ! run "dig @127.0.0.1 -p ${PORT} +short t1.devilbox | grep -E '^1\.1\.1\.1$'"; do 70 | i=$(( i + 1 )) 71 | if [ "${i}" -gt "${REPS}" ]; then 72 | echo "FAILED: t1.devilbox is not resolvable to 1.1.1.1" 73 | run "dig @127.0.0.1 -p ${PORT} +short t1.devilbox" 74 | run "docker logs ${NAME}" 75 | run "docker stop ${NAME}" 76 | echo "ABORT..." 77 | exit 1 78 | fi 79 | sleep 1 80 | done 81 | docker_stop "${NAME}" 82 | 83 | 84 | # DEBUG_ENTRYPOINT=null 85 | run "docker run -d --rm --platform ${ARCH} --name ${NAME} -e DEBUG=${DEBUG} -e DOCKER_LOGS=1 -e 'DNS_A=*.devilbox=1.1.1.1' -p ${PORT}:53/udp ${IMAGE}:${TAG}" 86 | run "sleep ${WAIT}" 87 | sanity_check "${NAME}" 88 | i=0 89 | while ! run "dig @127.0.0.1 -p ${PORT} +short t1.devilbox | grep -E '^1\.1\.1\.1$'"; do 90 | i=$(( i + 1 )) 91 | if [ "${i}" -gt "${REPS}" ]; then 92 | echo "FAILED: t1.devilbox is not resolvable to 1.1.1.1" 93 | run "dig @127.0.0.1 -p ${PORT} +short t1.devilbox" 94 | run "docker logs ${NAME}" 95 | run "docker stop ${NAME}" 96 | echo "ABORT..." 97 | exit 1 98 | fi 99 | sleep 1 100 | done 101 | docker_stop "${NAME}" 102 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F41B Bug report" 3 | description: File a bug report 4 | title: "[Bug]: " 5 | labels: ["bug", "status:triage"] 6 | assignees: 7 | - cytopia 8 | 9 | body: 10 | 11 | - type: markdown 12 | attributes: 13 | value: | 14 | Thanks for taking the time to fill out this bug report! 15 | 16 | - type: input 17 | attributes: 18 | label: (Optional) Error message 19 | description: If you encountered any error message, copy and paste it here. This will be used for googling the issue. 20 | validations: 21 | required: false 22 | 23 | - type: textarea 24 | id: what-happened 25 | attributes: 26 | label: What went wrong? 27 | description: What exactly went wrong and what bug did you encounter? 28 | validations: 29 | required: true 30 | 31 | - type: textarea 32 | id: expected-behaviour 33 | attributes: 34 | label: Expected behaviour 35 | description: What did you expect to happen instead? 36 | validations: 37 | required: true 38 | 39 | - type: textarea 40 | id: steps-to-reproduce 41 | attributes: 42 | label: How can we reproduce the bug? 43 | description: How do you trigger this bug? Please walk us through it step by step in detail. This is crucial in order to triage the bug and support you in resolving it. 44 | validations: 45 | required: true 46 | 47 | - type: dropdown 48 | id: host-os 49 | attributes: 50 | label: Host Operating System 51 | description: What operating system are you using? 52 | multiple: false 53 | options: 54 | - Linux 55 | - macOS 56 | - Windows 57 | validations: 58 | required: true 59 | 60 | - type: dropdown 61 | id: host-platform 62 | attributes: 63 | label: Host Platform (amd64, arm64, other) 64 | description: What host platform are you running on? 65 | options: 66 | - amd64 67 | - arm64 68 | - other 69 | validations: 70 | required: true 71 | 72 | - type: dropdown 73 | attributes: 74 | label: (Linux only) Is SELinux enabled? 75 | description: When using Linux as your host operating system, check if SELinux is enabled or not. 76 | options: 77 | - Yes, SELinux is enabled 78 | - No, SELinux is disabled 79 | - I don't know 80 | - I am not on Linux 81 | validations: 82 | required: true 83 | 84 | - type: input 85 | id: docker-version 86 | attributes: 87 | label: Docker version 88 | description: "What Docker version are you using? Please copy and paste the output of `docker --version` into this text area." 89 | validations: 90 | required: true 91 | 92 | - type: textarea 93 | id: log-docker 94 | attributes: 95 | label: "Log: docker logs" 96 | description: "Please copy and paste the output of `docker logs` into this text area" 97 | render: shell 98 | validations: 99 | required: true 100 | 101 | - type: textarea 102 | attributes: 103 | label: (Optional) Additional information 104 | description: Add any additional information that might help with this bug report. 105 | validations: 106 | required: false 107 | -------------------------------------------------------------------------------- /tests/03-DNS_CNAME_WILDCARD.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | set -u 5 | 6 | # Current directory 7 | CWD="$(cd -P -- "$(dirname -- "$0")" && pwd -P)" 8 | # shellcheck disable=SC1090 9 | . "${CWD}/.lib.sh" 10 | 11 | IMAGE="${1}" 12 | #NAME="${2}" 13 | #VERSION="${3}" 14 | TAG="${4}" 15 | ARCH="${5}" 16 | DEBUG="${6}" 17 | 18 | NAME="bind$( shuf -i 1000000000-2000000000 -n 1 )" 19 | PORT="5300" 20 | WAIT=5 21 | REPS=10 22 | 23 | 24 | # DEBUG_ENTRYPOINT=2 25 | run "docker run -d --rm --platform ${ARCH} --name ${NAME} -e DEBUG=${DEBUG} -e DEBUG_ENTRYPOINT=2 -e DOCKER_LOGS=1 -e 'DNS_CNAME=*.devilbox=google.com' -p ${PORT}:53/udp ${IMAGE}:${TAG}" 26 | run "sleep ${WAIT}" 27 | sanity_check "${NAME}" 28 | i=0 29 | while ! run "dig @127.0.0.1 -p ${PORT} +short www.devilbox | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'"; do 30 | i=$(( i + 1 )) 31 | if [ "${i}" -gt "${REPS}" ]; then 32 | echo "FAILED: www.devilbox is not resolvable" 33 | run "dig @127.0.0.1 -p ${PORT} +short www.devilbox" 34 | run "docker logs ${NAME}" 35 | run "docker stop ${NAME}" 36 | echo "ABORT..." 37 | exit 1 38 | fi 39 | sleep 1 40 | done 41 | docker_stop "${NAME}" 42 | 43 | 44 | # DEBUG_ENTRYPOINT=1 45 | run "docker run -d --rm --platform ${ARCH} --name ${NAME} -e DEBUG=${DEBUG} -e DEBUG_ENTRYPOINT=1 -e DOCKER_LOGS=1 -e 'DNS_CNAME=*.devilbox=google.com' -p ${PORT}:53/udp ${IMAGE}:${TAG}" 46 | run "sleep ${WAIT}" 47 | sanity_check "${NAME}" 48 | i=0 49 | while ! run "dig @127.0.0.1 -p ${PORT} +short www.devilbox | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'"; do 50 | i=$(( i + 1 )) 51 | if [ "${i}" -gt "${REPS}" ]; then 52 | echo "FAILED: www.devilbox is not resolvable" 53 | run "dig @127.0.0.1 -p ${PORT} +short www.devilbox" 54 | run "docker logs ${NAME}" 55 | run "docker stop ${NAME}" 56 | echo "ABORT..." 57 | exit 1 58 | fi 59 | sleep 1 60 | done 61 | docker_stop "${NAME}" 62 | 63 | 64 | # DEBUG_ENTRYPOINT=0 65 | run "docker run -d --rm --platform ${ARCH} --name ${NAME} -e DEBUG=${DEBUG} -e DEBUG_ENTRYPOINT=0 -e DOCKER_LOGS=1 -e 'DNS_CNAME=*.devilbox=google.com' -p ${PORT}:53/udp ${IMAGE}:${TAG}" 66 | run "sleep ${WAIT}" 67 | sanity_check "${NAME}" 68 | i=0 69 | while ! run "dig @127.0.0.1 -p ${PORT} +short www.devilbox | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'"; do 70 | i=$(( i + 1 )) 71 | if [ "${i}" -gt "${REPS}" ]; then 72 | echo "FAILED: www.devilbox is not resolvable" 73 | run "dig @127.0.0.1 -p ${PORT} +short www.devilbox" 74 | run "docker logs ${NAME}" 75 | run "docker stop ${NAME}" 76 | echo "ABORT..." 77 | exit 1 78 | fi 79 | sleep 1 80 | done 81 | docker_stop "${NAME}" 82 | 83 | 84 | # DEBUG_ENTRYPOINT=null 85 | run "docker run -d --rm --platform ${ARCH} --name ${NAME} -e DEBUG=${DEBUG} -e DOCKER_LOGS=1 -e 'DNS_CNAME=*.devilbox=google.com' -p ${PORT}:53/udp ${IMAGE}:${TAG}" 86 | run "sleep ${WAIT}" 87 | sanity_check "${NAME}" 88 | i=0 89 | while ! run "dig @127.0.0.1 -p ${PORT} +short www.devilbox | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'"; do 90 | i=$(( i + 1 )) 91 | if [ "${i}" -gt "${REPS}" ]; then 92 | echo "FAILED: www.devilbox is not resolvable" 93 | run "dig @127.0.0.1 -p ${PORT} +short www.devilbox" 94 | run "docker logs ${NAME}" 95 | run "docker stop ${NAME}" 96 | echo "ABORT..." 97 | exit 1 98 | fi 99 | sleep 1 100 | done 101 | docker_stop "${NAME}" 102 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ifneq (,) 2 | .error This Makefile requires GNU Make. 3 | endif 4 | 5 | # Ensure additional Makefiles are present 6 | MAKEFILES = Makefile.docker Makefile.lint 7 | $(MAKEFILES): URL=https://raw.githubusercontent.com/devilbox/makefiles/master/$(@) 8 | $(MAKEFILES): 9 | @if ! (curl --fail -sS -o $(@) $(URL) || wget -O $(@) $(URL)); then \ 10 | echo "Error, curl or wget required."; \ 11 | echo "Exiting."; \ 12 | false; \ 13 | fi 14 | include $(MAKEFILES) 15 | 16 | # Set default Target 17 | .DEFAULT_GOAL := help 18 | 19 | 20 | # ------------------------------------------------------------------------------------------------- 21 | # Default configuration 22 | # ------------------------------------------------------------------------------------------------- 23 | # Own vars 24 | TAG = latest 25 | 26 | # Makefile.docker overwrites 27 | NAME = Bind 28 | VERSION = latest 29 | IMAGE = cytopia/bind 30 | FLAVOUR = latest 31 | DIR = Dockerfiles 32 | FILE = Dockerfile.$(FLAVOUR) 33 | ifeq ($(strip $(FLAVOUR)),latest) 34 | DOCKER_TAG = $(TAG) 35 | else 36 | ifeq ($(strip $(TAG)),latest) 37 | DOCKER_TAG = $(FLAVOUR) 38 | else 39 | DOCKER_TAG = $(FLAVOUR)-$(TAG) 40 | endif 41 | endif 42 | ARCH = linux/amd64 43 | 44 | # Makefile.lint overwrites 45 | FL_IGNORES = .git/,.github/ 46 | SC_IGNORES = .git/,.github/,tests/ 47 | 48 | 49 | # ------------------------------------------------------------------------------------------------- 50 | # Default Target 51 | # ------------------------------------------------------------------------------------------------- 52 | .PHONY: help 53 | help: 54 | @echo "lint Lint project files and repository" 55 | @echo 56 | @echo "build [ARCH=...] [TAG=...] Build Docker image" 57 | @echo "rebuild [ARCH=...] [TAG=...] Build Docker image without cache" 58 | @echo "push [ARCH=...] [TAG=...] Push Docker image to Docker hub" 59 | @echo 60 | @echo "manifest-create [ARCHES=...] [TAG=...] Create multi-arch manifest" 61 | @echo "manifest-push [TAG=...] Push multi-arch manifest" 62 | @echo 63 | @echo "test [ARCH=...] Test built Docker image" 64 | @echo 65 | 66 | 67 | # ------------------------------------------------------------------------------------------------- 68 | # Docker Targets 69 | # ------------------------------------------------------------------------------------------------- 70 | .PHONY: build 71 | build: docker-arch-build 72 | 73 | .PHONY: rebuild 74 | rebuild: docker-arch-rebuild 75 | 76 | .PHONY: push 77 | push: docker-arch-push 78 | 79 | 80 | # ------------------------------------------------------------------------------------------------- 81 | # Manifest Targets 82 | # ------------------------------------------------------------------------------------------------- 83 | .PHONY: manifest-create 84 | manifest-create: docker-manifest-create 85 | 86 | .PHONY: manifest-push 87 | manifest-push: docker-manifest-push 88 | 89 | 90 | # ------------------------------------------------------------------------------------------------- 91 | # Test Targets 92 | # ------------------------------------------------------------------------------------------------- 93 | DEBUG = 0 94 | .PHONY: test 95 | test: _test-integration 96 | 97 | .PHONY: _test-integration 98 | _test-integration: 99 | ./tests/start-ci.sh $(IMAGE) $(NAME) $(VERSION) $(DOCKER_TAG) $(ARCH) $(DEBUG) 100 | -------------------------------------------------------------------------------- /tests/.lib.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | set -u 5 | 6 | run() { 7 | cmd="${1}" 8 | to_stdout=0 9 | cmd_to_stderr=0 10 | 11 | # Output to stdout instead? 12 | if [ "${#}" -gt "1" ]; then 13 | to_stdout="${2}" 14 | fi 15 | 16 | # Command output to stderr as well 17 | if [ "${#}" -gt "2" ]; then 18 | cmd_to_stderr="${3}" 19 | fi 20 | 21 | red="\033[0;31m" 22 | green="\033[0;32m" 23 | yellow="\033[0;33m" 24 | reset="\033[0m" 25 | 26 | if [ "${to_stdout}" -eq "0" ]; then 27 | printf "${yellow}[%s] ${red}%s \$ ${green}${cmd}${reset}\n" "$(hostname)" "$(whoami)" >&2 28 | else 29 | printf "${yellow}[%s] ${red}%s \$ ${green}${cmd}${reset}\n" "$(hostname)" "$(whoami)" 30 | fi 31 | 32 | if OUT="$( bash -c "set -eu && set -o pipefail && LANG=C LC_ALL=C ${cmd}" )"; then 33 | # Output command 34 | if [ -n "${OUT}" ]; then 35 | echo "${OUT}" 36 | if [ "${cmd_to_stderr}" = "1" ]; then 37 | >&2 echo "${OUT}" 38 | fi 39 | fi 40 | # Output exit code 41 | if [ "${to_stdout}" -eq "0" ]; then 42 | printf "${green}[%s]${reset}\n" "OK" >&2 43 | else 44 | printf "${green}[%s]${reset}\n" "OK" 45 | fi 46 | return 0 47 | else 48 | # Output command 49 | if [ -n "${OUT}" ]; then 50 | echo "${OUT}" 51 | if [ "${cmd_to_stderr}" = "1" ]; then 52 | >&2 echo "${OUT}" 53 | fi 54 | fi 55 | # Output exit code 56 | if [ "${to_stdout}" -eq "0" ]; then 57 | printf "${red}[%s]${reset}\n" "NO" >&2 58 | else 59 | printf "${red}[%s]${reset}\n" "NO" 60 | fi 61 | return 1 62 | fi 63 | } 64 | 65 | run_fail() { 66 | cmd="${1}" 67 | to_stdout=0 68 | 69 | # Output to stdout instead? 70 | if [ "${#}" -eq "2" ]; then 71 | to_stdout="${2}" 72 | fi 73 | 74 | red="\033[0;31m" 75 | green="\033[0;32m" 76 | yellow="\033[0;33m" 77 | reset="\033[0m" 78 | 79 | if [ "${to_stdout}" -eq "0" ]; then 80 | printf "${yellow}[%s] ${red}%s \$ ${yellow}[NOT] ${green}${cmd}${reset}\n" "$(hostname)" "$(whoami)" >&2 81 | else 82 | printf "${yellow}[%s] ${red}%s \$ ${yellow}[NOT] ${green}${cmd}${reset}\n" "$(hostname)" "$(whoami)" 83 | fi 84 | 85 | if ! sh -c "LANG=C LC_ALL=C ${cmd}"; then 86 | if [ "${to_stdout}" -eq "0" ]; then 87 | printf "${green}[%s]${reset}\n" "OK" >&2 88 | else 89 | printf "${green}[%s]${reset}\n" "OK" 90 | fi 91 | return 0 92 | else 93 | if [ "${to_stdout}" -eq "0" ]; then 94 | printf "${red}[%s]${reset}\n" "NO" >&2 95 | else 96 | printf "${red}[%s]${reset}\n" "NO" 97 | fi 98 | return 1 99 | fi 100 | } 101 | 102 | sanity_check() { 103 | _name="${1}" 104 | _file="${2:-}" 105 | 106 | if ! run "docker ps | grep '${_name}'"; then 107 | docker ps 108 | docker logs "${_name}" || true 109 | docker stop "${_name}" || true 110 | return 1 111 | fi 112 | 113 | # Test if docker logs works 114 | if ! run "docker logs ${_name}"; then 115 | docker stop "${_name}" || true 116 | return 1 117 | fi 118 | 119 | # Ensure no error string was found 120 | if ! run_fail "docker logs ${_name} 2>&1 | grep -E '\[ERR\]'"; then 121 | docker logs "${_name}" || true 122 | docker stop "${_name}" || true 123 | return 1 124 | fi 125 | 126 | # Ensure no warning string was found 127 | if ! run_fail "docker logs ${_name} 2>&1 | grep -E '\[WARN\]'"; then 128 | docker logs "${_name}" || true 129 | docker stop "${_name}" || true 130 | return 1 131 | fi 132 | } 133 | 134 | 135 | docker_stop() { 136 | _name="${1}" 137 | 138 | run "docker stop ${_name}" || true 139 | run "docker kill ${_name} >/dev/null 2>&1 || true" >/dev/null 2>&1 140 | run "sleep 2" 141 | } 142 | -------------------------------------------------------------------------------- /tests/04-DNS_A.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | set -u 5 | 6 | # Current directory 7 | CWD="$(cd -P -- "$(dirname -- "$0")" && pwd -P)" 8 | # shellcheck disable=SC1090 9 | . "${CWD}/.lib.sh" 10 | 11 | IMAGE="${1}" 12 | #NAME="${2}" 13 | #VERSION="${3}" 14 | TAG="${4}" 15 | ARCH="${5}" 16 | DEBUG="${6}" 17 | 18 | NAME="bind$( shuf -i 1000000000-2000000000 -n 1 )" 19 | PORT="5300" 20 | WAIT=5 21 | REPS=10 22 | 23 | 24 | # DEBUG_ENTRYPOINT=2 25 | run "docker run -d --rm --platform ${ARCH} --name ${NAME} -e DEBUG=${DEBUG} -e DEBUG_ENTRYPOINT=2 -e DOCKER_LOGS=1 -e 'DNS_A=www.devilbox=1.1.1.1' -p ${PORT}:53/udp ${IMAGE}:${TAG}" 26 | run "sleep ${WAIT}" 27 | sanity_check "${NAME}" 28 | i=0 29 | while ! run "dig @127.0.0.1 -p ${PORT} +short www.devilbox | grep -E '^1\.1\.1\.1$'"; do 30 | i=$(( i + 1 )) 31 | if [ "${i}" -gt "${REPS}" ]; then 32 | echo "FAILED: www.devilbox is not resolvable to 1.1.1.1" 33 | run "dig @127.0.0.1 -p ${PORT} +short www.devilbox" 34 | run "docker logs ${NAME}" 35 | run "docker stop ${NAME}" 36 | echo "ABORT..." 37 | exit 1 38 | fi 39 | sleep 1 40 | done 41 | if [ "$( dig @127.0.0.1 -p ${PORT} +short t1.devilbox | wc -l )" != "0" ]; then 42 | run "docker stop ${NAME}" 43 | exit 1 44 | fi 45 | docker_stop "${NAME}" 46 | 47 | 48 | # DEBUG_ENTRYPOINT=1 49 | run "docker run -d --rm --platform ${ARCH} --name ${NAME} -e DEBUG=${DEBUG} -e DEBUG_ENTRYPOINT=1 -e DOCKER_LOGS=1 -e 'DNS_A=www.devilbox=1.1.1.1' -p ${PORT}:53/udp ${IMAGE}:${TAG}" 50 | run "sleep ${WAIT}" 51 | sanity_check "${NAME}" 52 | i=0 53 | while ! run "dig @127.0.0.1 -p ${PORT} +short www.devilbox | grep -E '^1\.1\.1\.1$'"; do 54 | i=$(( i + 1 )) 55 | if [ "${i}" -gt "${REPS}" ]; then 56 | echo "FAILED: www.devilbox is not resolvable to 1.1.1.1" 57 | run "dig @127.0.0.1 -p ${PORT} +short www.devilbox" 58 | run "docker logs ${NAME}" 59 | run "docker stop ${NAME}" 60 | echo "ABORT..." 61 | exit 1 62 | fi 63 | sleep 1 64 | done 65 | if [ "$( dig @127.0.0.1 -p ${PORT} +short t1.devilbox | wc -l )" != "0" ]; then 66 | docker stop "${NAME}" 67 | exit 1 68 | fi 69 | docker_stop "${NAME}" 70 | 71 | 72 | # DEBUG_ENTRYPOINT=0 73 | run "docker run -d --rm --platform ${ARCH} --name ${NAME} -e DEBUG=${DEBUG} -e DEBUG_ENTRYPOINT=0 -e DOCKER_LOGS=1 -e 'DNS_A=www.devilbox=1.1.1.1' -p ${PORT}:53/udp ${IMAGE}:${TAG}" 74 | run "sleep ${WAIT}" 75 | sanity_check "${NAME}" 76 | i=0 77 | while ! run "dig @127.0.0.1 -p ${PORT} +short www.devilbox | grep -E '^1\.1\.1\.1$'"; do 78 | i=$(( i + 1 )) 79 | if [ "${i}" -gt "${REPS}" ]; then 80 | echo "FAILED: www.devilbox is not resolvable to 1.1.1.1" 81 | run "dig @127.0.0.1 -p ${PORT} +short www.devilbox" 82 | run "docker logs ${NAME}" 83 | run "docker stop ${NAME}" 84 | echo "ABORT..." 85 | exit 1 86 | fi 87 | sleep 1 88 | done 89 | if [ "$( dig @127.0.0.1 -p ${PORT} +short t1.devilbox | wc -l )" != "0" ]; then 90 | run "docker stop ${NAME}" 91 | exit 1 92 | fi 93 | docker_stop "${NAME}" 94 | 95 | 96 | # DEBUG_ENTRYPOINT=null 97 | run "docker run -d --rm --platform ${ARCH} --name ${NAME} -e DEBUG=${DEBUG} -e DOCKER_LOGS=1 -e 'DNS_A=www.devilbox=1.1.1.1' -p ${PORT}:53/udp ${IMAGE}:${TAG}" 98 | run "sleep ${WAIT}" 99 | sanity_check "${NAME}" 100 | i=0 101 | while ! run "dig @127.0.0.1 -p ${PORT} +short www.devilbox | grep -E '^1\.1\.1\.1$'"; do 102 | i=$(( i + 1 )) 103 | if [ "${i}" -gt "${REPS}" ]; then 104 | echo "FAILED: www.devilbox is not resolvable to 1.1.1.1" 105 | run "dig @127.0.0.1 -p ${PORT} +short www.devilbox" 106 | run "docker logs ${NAME}" 107 | run "docker stop ${NAME}" 108 | echo "ABORT..." 109 | exit 1 110 | fi 111 | sleep 1 112 | done 113 | if [ "$( dig @127.0.0.1 -p ${PORT} +short t1.devilbox | wc -l )" != "0" ]; then 114 | run "docker stop ${NAME}" 115 | exit 1 116 | fi 117 | docker_stop "${NAME}" 118 | -------------------------------------------------------------------------------- /tests/05-DNS_CNAME.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | set -u 5 | 6 | # Current directory 7 | CWD="$(cd -P -- "$(dirname -- "$0")" && pwd -P)" 8 | # shellcheck disable=SC1090 9 | . "${CWD}/.lib.sh" 10 | 11 | IMAGE="${1}" 12 | #NAME="${2}" 13 | #VERSION="${3}" 14 | TAG="${4}" 15 | ARCH="${5}" 16 | DEBUG="${6}" 17 | 18 | NAME="bind$( shuf -i 1000000000-2000000000 -n 1 )" 19 | PORT="5300" 20 | WAIT=5 21 | REPS=10 22 | 23 | 24 | # DEBUG_ENTRYPOINT=2 25 | run "docker run -d --rm --platform ${ARCH} --name ${NAME} -e DEBUG=${DEBUG} -e DEBUG_ENTRYPOINT=2 -e DOCKER_LOGS=1 -e 'DNS_CNAME=www.devilbox=google.com' -p ${PORT}:53/udp ${IMAGE}:${TAG}" 26 | run "sleep ${WAIT}" 27 | sanity_check "${NAME}" 28 | i=0 29 | while ! run "dig @127.0.0.1 -p ${PORT} +short www.devilbox | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'"; do 30 | i=$(( i + 1 )) 31 | if [ "${i}" -gt "${REPS}" ]; then 32 | echo "FAILED: www.devilbox is not resolvable" 33 | run "dig @127.0.0.1 -p ${PORT} +short www.devilbox" 34 | run "docker logs ${NAME}" 35 | run "docker stop ${NAME}" 36 | echo "ABORT..." 37 | exit 1 38 | fi 39 | sleep 1 40 | done 41 | if [ "$( dig @127.0.0.1 -p ${PORT} +short t1.devilbox | wc -l )" != "0" ]; then 42 | run "docker stop ${NAME}" 43 | exit 1 44 | fi 45 | docker_stop "${NAME}" 46 | 47 | 48 | # DEBUG_ENTRYPOINT=1 49 | run "docker run -d --rm --platform ${ARCH} --name ${NAME} -e DEBUG=${DEBUG} -e DEBUG_ENTRYPOINT=1 -e DOCKER_LOGS=1 -e 'DNS_CNAME=www.devilbox=google.com' -p ${PORT}:53/udp ${IMAGE}:${TAG}" 50 | run "sleep ${WAIT}" 51 | sanity_check "${NAME}" 52 | i=0 53 | while ! run "dig @127.0.0.1 -p ${PORT} +short www.devilbox | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'"; do 54 | i=$(( i + 1 )) 55 | if [ "${i}" -gt "${REPS}" ]; then 56 | echo "FAILED: www.devilbox is not resolvable" 57 | run "dig @127.0.0.1 -p ${PORT} +short www.devilbox" 58 | run "docker logs ${NAME}" 59 | run "docker stop ${NAME}" 60 | echo "ABORT..." 61 | exit 1 62 | fi 63 | sleep 1 64 | done 65 | if [ "$( dig @127.0.0.1 -p ${PORT} +short t1.devilbox | wc -l )" != "0" ]; then 66 | run "docker stop ${NAME}" 67 | exit 1 68 | fi 69 | docker_stop "${NAME}" 70 | 71 | 72 | # DEBUG_ENTRYPOINT=0 73 | run "docker run -d --rm --platform ${ARCH} --name ${NAME} -e DEBUG=${DEBUG} -e DEBUG_ENTRYPOINT=0 -e DOCKER_LOGS=1 -e 'DNS_CNAME=www.devilbox=google.com' -p ${PORT}:53/udp ${IMAGE}:${TAG}" 74 | run "sleep ${WAIT}" 75 | sanity_check "${NAME}" 76 | i=0 77 | while ! run "dig @127.0.0.1 -p ${PORT} +short www.devilbox | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'"; do 78 | i=$(( i + 1 )) 79 | if [ "${i}" -gt "${REPS}" ]; then 80 | echo "FAILED: www.devilbox is not resolvable" 81 | run "dig @127.0.0.1 -p ${PORT} +short www.devilbox" 82 | run "docker logs ${NAME}" 83 | run "docker stop ${NAME}" 84 | echo "ABORT..." 85 | exit 1 86 | fi 87 | sleep 1 88 | done 89 | if [ "$( dig @127.0.0.1 -p ${PORT} +short t1.devilbox | wc -l )" != "0" ]; then 90 | run "docker stop ${NAME}" 91 | exit 1 92 | fi 93 | docker_stop "${NAME}" 94 | 95 | 96 | # DEBUG_ENTRYPOINT=null 97 | run "docker run -d --rm --platform ${ARCH} --name ${NAME} -e DEBUG=${DEBUG} -e DOCKER_LOGS=1 -e 'DNS_CNAME=www.devilbox=google.com' -p ${PORT}:53/udp ${IMAGE}:${TAG}" 98 | run "sleep ${WAIT}" 99 | sanity_check "${NAME}" 100 | i=0 101 | while ! run "dig @127.0.0.1 -p ${PORT} +short www.devilbox | grep -E '^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$'"; do 102 | i=$(( i + 1 )) 103 | if [ "${i}" -gt "${REPS}" ]; then 104 | echo "FAILED: www.devilbox is not resolvable" 105 | run "dig @127.0.0.1 -p ${PORT} +short www.devilbox" 106 | run "docker logs ${NAME}" 107 | run "docker stop ${NAME}" 108 | echo "ABORT..." 109 | exit 1 110 | fi 111 | sleep 1 112 | done 113 | if [ "$( dig @127.0.0.1 -p ${PORT} +short t1.devilbox | wc -l )" != "0" ]; then 114 | docker stop "${NAME}" 115 | exit 1 116 | fi 117 | docker_stop "${NAME}" 118 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bind Docker 2 | 3 | [![Tag](https://img.shields.io/github/tag/cytopia/docker-bind.svg)](https://github.com/cytopia/docker-bind/releases) 4 | [![lint](https://github.com/cytopia/docker-bind/workflows/lint/badge.svg)](https://github.com/cytopia/docker-bind/actions?query=workflow%3Alint) 5 | [![build](https://github.com/cytopia/docker-bind/workflows/build/badge.svg)](https://github.com/cytopia/docker-bind/actions?query=workflow%3Abuild) 6 | [![nightly](https://github.com/cytopia/docker-bind/workflows/nightly/badge.svg)](https://github.com/cytopia/docker-bind/actions?query=workflow%3Anightly) 7 | [![License](https://img.shields.io/badge/license-MIT-%233DA639.svg)](https://opensource.org/licenses/MIT) 8 | 9 | [![Discord](https://img.shields.io/discord/1051541389256704091?color=8c9eff&label=Discord&logo=discord)](https://discord.gg/2wP3V6kBj4) 10 | [![Discourse](https://img.shields.io/discourse/https/devilbox.discourse.group/status.svg?colorB=%234CB697&label=Discourse&logo=discourse)](https://devilbox.discourse.group) 11 | 12 | **Available Architectures:** `amd64`, `i386`, `arm64`, `arm/v7`, `arm/v6`, `ppc64le`, `s390x`, `mips64le` 13 | 14 | [![](https://img.shields.io/docker/pulls/cytopia/bind.svg)](https://hub.docker.com/r/cytopia/bind) 15 | 16 | Bind caching DNS server based on Alpine and Debian slim with support for DNS forwarders, infinite wild-card DNS, infinite extra hosts, reverse DNS, DNSSEC timing settings and others. 17 | 18 | | Bind Project | Reference Implementation | 19 | |:-------------------:|:------------------------:| 20 | | | | 21 | | [Bind DNS Server](https://github.com/cytopia/docker-bind) | The [Devilbox](https://github.com/cytopia/devilbox) | 22 | 23 | 24 | ## 🐋 Available Docker tags 25 | 26 | [![](https://img.shields.io/docker/pulls/cytopia/bind.svg)](https://hub.docker.com/r/cytopia/bind) 27 | 28 | [`latest`][tag_latest] [`stable`][tag_stable] [`alpine`][tag_alpine] 29 | ```bash 30 | docker pull cytopia/bind 31 | ``` 32 | 33 | [tag_latest]: Dockerfiles/Dockerfile.latest 34 | [tag_stable]: Dockerfiles/Dockerfile.stable 35 | [tag_alpine]: Dockerfiles/Dockerfile.alpine 36 | 37 | #### Rolling Releases 38 | 39 | The following Docker image tags are rolling releases and are built and updated every night. 40 | 41 | [![nightly](https://github.com/cytopia/docker-bind/workflows/nightly/badge.svg)](https://github.com/cytopia/docker-bind/actions?query=workflow%3Anightly) 42 | 43 | | Docker Tag | Git Ref | Available Architectures | 44 | |----------------------------------|--------------|------------------------------------------------------------------------------| 45 | | **[`latest`][tag_latest]** | master | `amd64`, `i386`, `arm64`, `arm/v7`, `arm/v6`, `ppc64le`, `s390x`, `mips64le` | 46 | | [`stable`][tag_stable] | master | `amd64`, `i386`, `arm64`, `arm/v7`, `arm/v6`, `ppc64le`, `s390x`, `mips64le` | 47 | | [`alpine`][tag_alpine] | master | `amd64`, `i386`, `arm64`, `arm/v7`, `arm/v6`, `ppc64le`, `s390x`, `mips64le` | 48 | 49 | #### Point in time releases 50 | 51 | The following Docker image tags are built once and can be used for reproducible builds. Its version never changes so you will have to update tags in your pipelines from time to time in order to stay up-to-date. 52 | 53 | [![build](https://github.com/cytopia/docker-bind/workflows/build/badge.svg)](https://github.com/cytopia/docker-bind/actions?query=workflow%3Abuild) 54 | 55 | | Docker Tag | Git Ref | Available Architectures | 56 | |----------------------------------|--------------|-------------------------------------------------------------------------------| 57 | | **[``][tag_latest]** | git: `` | `amd64`, `i386`, `arm64`, `arm/v7`, `arm/v6`, `ppc64le`, `s390x`, `mips64le` | 58 | | [`-stable`][tag_stable] | git: `` | `amd64`, `i386`, `arm64`, `arm/v7`, `arm/v6`, `ppc64le`, `s390x`, `mips64le` | 59 | | [`-alpine`][tag_alpine] | git: `` | `amd64`, `i386`, `arm64`, `arm/v7`, `arm/v6`, `ppc64le`, `s390x`, `mips64le` | 60 | 61 | > 🛈 Where `` refers to the chosen git tag from this repository.
62 | > ⚠ **Warning:** The latest available git tag is also build every night and considered a rolling tag. 63 | 64 | 65 | ---- 66 | 67 | **Table of Contents** 68 | 69 | 1. [Environment variables](#-environment-variables) 70 | 1. [Required environment variables](#required-environment-variables) 71 | 2. [Optional environment variables](#optional-environment-variables) 72 | 1. [DEBUG_ENTRYPOINT](#debug_entrypoint) 73 | 2. [DOCKER_LOGS](#docker_logs) 74 | 3. [DNS_A](#dns_a) 75 | 4. [DNS_CNAME](#dns_cname) 76 | 5. [DNS_PTR](#dns_ptr) 77 | 6. [DNSSEC_VALIDATE](#dnssec_validate) 78 | 7. [DNS_FORWARDER](#dns_forwarder) 79 | 8. [MAX_CACHE_SIZE](#max_cache_size) 80 | 9. [TTL_TIME](#ttl_time) 81 | 10. [REFRESH_TIME](#refresh_time) 82 | 11. [RETRY_TIME](#retry_time) 83 | 12. [EXPIRY_TIME](#expiry_time) 84 | 13. [MAX_CACHE_TIME](#max_cache_time) 85 | 14. [ALLOW_QUERY](#allow_query) 86 | 15. [ALLOW_RECURSION](#allow_recursion) 87 | 2. [Volumes](#-volumes) 88 | 3. [Exposed Ports](#-exposed-ports) 89 | 4. [Examples](#-examples) 90 | 1. [Default run](#default-run) 91 | 2. [Wildcard domain](#wildcard-domain) 92 | 3. [Wildcard subdomain](#wildcard-subdomain) 93 | 4. [Wildcard TLD](#wildcard-tld) 94 | 5. [Wildcard TLD and reverse DNS entry](#wildcard-tld-and-reverse-dns-entry) 95 | 6. [Wildcard TLD and DNS resolver](#wildcard-tld-and-dns-resolver) 96 | 7. [Wildcard TLD, DNS resolver and extra hosts](#wildcard-tld-dns-resolver-and-extra-hosts) 97 | 8. [Extra hosts, DNS resolver, allow query, and allow recursion](#extra-hosts-dns-resolver-allow-query-and-allow-recursion) 98 | 5. [Host integration](#-host-integration) 99 | 6. [Sister Projects](#-sister-projects) 100 | 7. [Community](#-community) 101 | 8. [Articles](#-articles) 102 | 9. [Credits](#-credits) 103 | 10. [Maintainer](#-maintainer) 104 | 11. [License](#-license) 105 | 106 | --- 107 | 108 | ## ∑ Environment Variables 109 | 110 | ### Required environment variables 111 | 112 | - None 113 | 114 | ### Optional environment variables 115 | 116 | | Variable | Type | Default | Description | 117 | |--------------------|--------|-----------|-------------| 118 | | `DEBUG` | bool | `0` | Set to `1` in order to add `set -x` to entrypoint script for bash debugging | 119 | | `DEBUG_ENTRYPOINT` | bool | `0` | Show shell commands executed during start.
Values: `0`, `1` or `2` | 120 | | `DOCKER_LOGS` | bool | `0` | Set to `1` to log info and queries to Docker logs. | 121 | | `DNS_A` | string | | Comma separated list of A records (wildcard supported). | 122 | | `DNS_CNAME` | string | | Comma separated list of CNAME records (wildcard supported). | 123 | | `DNS_PTR` | string | | Comma separated list of PTR records (reverse DNS). | 124 | | `DNSSEC_VALIDATE` | string | `no` | Control the behaviour of DNSSEC validation. The default is to not validate: `no`. Other possible values are: `yes` and `auto`. | 125 | | `DNS_FORWARDER` | string | | Specify a comma separated list of IP addresses as custom DNS resolver. This is useful if your LAN already has a DNS server which adds custom/internal domains and you still want to keep them in this DNS server
Example: `DNS_FORWARDER=8.8.8.8,8.8.4.4` | 126 | | `MAX_CACHE_SIZE` | size | `90%` | Amount of memory used by the server (cached results) | 127 | | `TTL_TIME` | int | `3600` | (time in seconds) see [bind ttl](http://www.zytrax.com/books/dns/apa/ttl.html) and [bind soa](http://www.zytrax.com/books/dns/ch8/soa.html)| 128 | | `REFRESH_TIME` | int | `1200` | (Time in seconds) See [BIND SOA](http://www.zytrax.com/books/dns/ch8/soa.html) | 129 | | `RETRY_TIME` | int | `180` | (Time in seconds) See [BIND SOA](http://www.zytrax.com/books/dns/ch8/soa.html) | 130 | | `EXPIRY_TIME` | int | `1209600` | (Time in seconds) See [BIND SOA](http://www.zytrax.com/books/dns/ch8/soa.html) | 131 | | `MAX_CACHE_TIME` | int | `10800` | (Time in seconds) See [BIND SOA](http://www.zytrax.com/books/dns/ch8/soa.html) | 132 | | `ALLOW_QUERY` | string | | Specify a comma separated list of IP addresses with optional CIDR mask to allow queries from a specific IP address or ranges of IP addresses. This allows for control over who is allowed to query the DNS server. If not specified all hosts are allowed to make queries (defaults to `any`). See [BIND QUERIES](http://www.zytrax.com/books/dns/ch7/queries.html)
Example: `ALLOW_QUERY=192.168.1.0/24,127.0.0.1` | 133 | | `ALLOW_RECURSION` | string | | Specify a comma separated list of IP addresses with optional CIDR mask to allow queries from a specific IP address or ranges of IP addresses. This option allows this DNS server to forward a request to another DNS server when an address cannot be resolved. If not present the allow-query-cache default is assumed. See [BIND QUERIES](http://www.zytrax.com/books/dns/ch7/queries.html)
Example: `ALLOW_RECURSION=192.168.1.0/24,127.0.0.1` | 134 | #### DEBUG_ENTRYPOINT 135 | 136 | * If set to `0`, only warnings and errors are shown 137 | * If set to `1`, info, warnings and errors are shown 138 | * If set to `2`, info, warnings and errors are shown, as well as commands executed during startup 139 | 140 | #### DOCKER_LOGS 141 | 142 | * If set to `0`, no additional logging is done during run-time 143 | * If set to `1`, BIND is more verbose during run-time and shows asked queries as well as general information 144 | 145 | #### DNS_A 146 | 147 | The `DNS_A` option allows you to specify one or more A records (including wildcard if required) which can either 148 | be a full TLD, a domain or any kind of subdomain. It allows you to map your Domain to a specific 149 | IP address. 150 | 151 | The general format is as follows: 152 | ```bash 153 | # Structure 154 | DNS_A='tld1=1.1.1.1, tld2=2.2.2.2, *.tld3=3.3.3.3' 155 | ``` 156 | 157 | Some examples: 158 | ```bash 159 | # 1. One entry: 160 | # The following catches all queries to *.tld (wildcard) and redirects them to 192.168.0.1 161 | DNS_A='*.tld=192.168.0.1' 162 | 163 | # 2. Two entries: 164 | # The following catches all queries to *.tld and redirects them to 192.168.0.1 165 | # As well as all queries from *.example.org and redirects them to 192.168.0.2 166 | DNS_A='*.tld=192.168.0.1, *.example.org=192.168.0.2' 167 | ``` 168 | 169 | #### DNS_CNAME 170 | 171 | The `DNS_CNAME` option allows you to specify one or more CNAME records (including wildcard if required) which can either 172 | be a full TLD, a domain or any kind of subdomain. It allows you to map your Domain to a specific 173 | IP address. 174 | 175 | The general format is as follows: 176 | ``` 177 | # Structure 178 | DNS_CNAME='tld1=google.com, tld2=www.google.com, *.tld3=example.org' 179 | ``` 180 | 181 | Some examples: 182 | ``` 183 | # 1. Using CNAME's for resolving: 184 | # The following catches all queries to *.tld and redirects them to whatever 185 | # IP example.org resolved to 186 | DNS_CNAME='*.tld=example.org' 187 | ``` 188 | 189 | #### DNS_PTR 190 | 191 | The `DNS_PTR` option allows you to specify PTR records (reverse DNS). 192 | 193 | The general format is as follows: 194 | ``` 195 | # Structure 196 | DNS_PTR='192.168.0.1=www.google.com, 192.168.0.2=ftp.google.com' 197 | ``` 198 | 199 | Some examples: 200 | ``` 201 | # 1. Adding reverse DNS: 202 | # The following adds reverse DNS from 192.168.0.1 to resolve to tld 203 | DNS_PTR='192.168.0.1=tld' 204 | ``` 205 | 206 | #### DNSSEC_VALIDATE 207 | 208 | The `DNSSEC_VALIDATE` variable defines the DNSSEC validation. Default is to not validate (`no`). 209 | Possible values are: 210 | 211 | * `yes` - DNSSEC validation is enabled, but a trust anchor must be manually configured. No validation will actually take place. 212 | * `no` - DNSSEC validation is disabled, and recursive server will behave in the "old fashioned" way of performing insecure DNS lookups, until you have manually configured at least one trusted key. 213 | * `auto` - DNSSEC validation is enabled, and a default trust anchor (included as part of BIND) for the DNS root zone is used. 214 | 215 | #### DNS_FORWARDER 216 | 217 | By default this dockerized BIND is not acting as a DNS forwarder, so it will not have any external 218 | DNS available. In order to apply external DNS forwarding, you will have to specify one or more external 219 | DNS server. This could be the one's from google for example (`8.8.8.8` and `8.8.4.4`) or any others 220 | you prefer. In case your LAN has its own DNS server with already defined custom DNS records that you 221 | need to make available, you should use them. 222 | 223 | ```bash 224 | # Structure (comma separated list of IP addresses) 225 | DNS_FORWARDER='8.8.8.8,8.8.4.4' 226 | ``` 227 | 228 | Some examples 229 | ```bash 230 | DNS_FORWARDER='8.8.8.8' 231 | DNS_FORWARDER='8.8.8.8,192.168.0.10' 232 | ``` 233 | #### MAX_CACHE_SIZE 234 | The amount of RAM used by the server to store results. You can use relative (percent) or absolute (bytes) values. 235 | Examples: 236 | * `MAX_CACHE_SIZE=30%` (Use 30% of the systems memory) 237 | * `MAX_CACHE_SIZE=512M` (Use 512 Megabytes) 238 | * `MAX_CACHE_SIZE=2G` (Use 2 Gigabytes) 239 | 240 | #### TTL_TIME 241 | Specify time in seconds. 242 | For more information regarding this setting, see [BIND TTL](http://www.zytrax.com/books/dns/apa/ttl.html) and [BIND SOA](http://www.zytrax.com/books/dns/ch8/soa.html) 243 | 244 | #### REFRESH_TIME 245 | Specify time in seconds. 246 | For more information regarding this setting, see [BIND SOA](http://www.zytrax.com/books/dns/ch8/soa.html) 247 | 248 | #### RETRY_TIME 249 | Specify time in seconds. 250 | For more information regarding this setting, see [BIND SOA](http://www.zytrax.com/books/dns/ch8/soa.html) 251 | 252 | #### EXPIRY_TIME 253 | Specify time in seconds. 254 | For more information regarding this setting, see [BIND SOA](http://www.zytrax.com/books/dns/ch8/soa.html) 255 | 256 | #### MAX_CACHE_TIME 257 | Specify time in seconds. 258 | For more information regarding this setting, see [BIND SOA](http://www.zytrax.com/books/dns/ch8/soa.html) 259 | 260 | #### ALLOW_QUERY 261 | 262 | By default this dockerized BIND does not specify query rules. This exposes the 263 | allow-query options to specify who is allowed to query for results. 264 | Note that ACLs are not yet handled. 265 | 266 | ```bash 267 | # Structure (comma separated list of IP addresses, IP addresses with CIDR mask, or address match list names "none", "any", "localhost", and "localnets") 268 | ALLOW_QUERY='192.168.1.0/24,127.0.0.1' 269 | ``` 270 | 271 | Some examples 272 | ```bash 273 | ALLOW_QUERY='any' 274 | ALLOW_QUERY='192.168.1.0/24,127.0.0.1' 275 | ``` 276 | 277 | #### ALLOW_RECURSION 278 | 279 | By default this dockerized BIND does not allow DNS recursion. If BIND cannot resolve an address it 280 | will act as a DNS client and forward the request to another DNS server. This server is specified in the DNS_FORWARDER list. 281 | Note that ACLs are not yet handled. 282 | 283 | ```bash 284 | # Structure (comma separated list of IP addresses, IP addresses with CIDR mask, or address match list names "none", "any", "localhost", and "localnets") 285 | ALLOW_RECURSION='192.168.1.0/24,127.0.0.1' 286 | ``` 287 | 288 | Some examples 289 | ```bash 290 | ALLOW_RECURSION='any' 291 | ALLOW_RECURSION='192.168.1.0/24,127.0.0.1' 292 | ``` 293 | 294 | ## 📂 Volumes 295 | 296 | - None 297 | 298 | 299 | ## 🖧 Exposed Ports 300 | 301 | | Docker | Description | 302 | |--------|--------------| 303 | | 53 | DNS Resolver | 304 | | 53/udp | DNS Resolver | 305 | 306 | 307 | ## 💡 Examples 308 | 309 | The following examples start the container in foreground and use `-i`, so you can easily stop 310 | it by pressing ` + c`. For a production run, you would rather use `-d` to send it to the 311 | background. 312 | 313 | #### Default run 314 | 315 | Exposing the port is mandatory if you want to use it for your host operating system. 316 | ```bash 317 | docker run -i \ 318 | -p 53:53/tcp \ 319 | -p 53:53/udp \ 320 | -t cytopia/bind 321 | ``` 322 | 323 | #### Wildcard domain 324 | 325 | Let's add a wildcard zone for `*.example.com`. All subdomains (but not example.com itself) will resolve 326 | to `192.168.0.1`. 327 | ```bash 328 | docker run -i \ 329 | -p 53:53/tcp \ 330 | -p 53:53/udp \ 331 | -e DNS_A='*.example.com=192.168.0.1' \ 332 | -t cytopia/bind 333 | ``` 334 | 335 | #### Wildcard subdomain 336 | 337 | Let's add a wildcard zone for `*.aws.example.com`. All subdomains (but not aws.example.com itself) will resolve 338 | to `192.168.0.1`. 339 | ```bash 340 | docker run -i \ 341 | -p 53:53/tcp \ 342 | -p 53:53/udp \ 343 | -e DNS_A='*.aws.example.com=192.168.0.1' \ 344 | -t cytopia/bind 345 | ``` 346 | 347 | #### Wildcard TLD 348 | 349 | Let's add a wildcard zone for `*.loc`. All domains, subdomain (but not loc itself) will resolve 350 | to `192.168.0.4`. 351 | ```bash 352 | docker run -i \ 353 | -p 53:53/tcp \ 354 | -p 53:53/udp \ 355 | -e DNS_A='*.loc=192.168.0.4' \ 356 | -t cytopia/bind 357 | ``` 358 | 359 | #### Wildcard TLD and reverse DNS entry 360 | 361 | Let's add a wildcard zone for `*.loc`, and an A record for loc. All domains, subdomain and loc itself will resolve 362 | to `192.168.0.4`. Additionally we specify that `host.loc` will be the reverse loopup for `192.168.0.4`. 363 | ```bash 364 | docker run -i \ 365 | -p 53:53/tcp \ 366 | -p 53:53/udp \ 367 | -e DNS_A='*.loc=192.168.0.4, loc=192.168.0.4' \ 368 | -e DNS_PTR='192.168.0.4=host.loc' \ 369 | -t cytopia/bind 370 | ``` 371 | 372 | #### Wildcard TLD and DNS resolver 373 | 374 | Let's add a wildcard zone for `*.loc`. All its domains (but not the domain itself) will resolve 375 | to `192.168.0.4`. 376 | 377 | Let's also hook in our imaginary corporate DNS server into this container, so we can make use of 378 | any already defined custom DNS entries by that nameserver. 379 | 380 | * `loc` and all its subdomains (such as: `hostname.loc`) will point to `192.168.0.1`: 381 | * Your corporate DNS servers are `10.0.15.1` and `10.0.15.2` 382 | 383 | ```bash 384 | docker run -i \ 385 | -p 53:53/tcp \ 386 | -p 53:53/udp \ 387 | -e DNS_A='*.loc=192.168.0.1' \ 388 | -e DNS_FORWARDER=10.0.15.1,10.0.15.2 \ 389 | -t cytopia/bind 390 | ``` 391 | 392 | #### Wildcard TLD, DNS resolver and extra hosts 393 | 394 | * All subdomains of `loc` (but not `loc` itself) will point to `192.168.0.1` 395 | * Your corporate DNS servers are `10.0.15.1` and `10.0.15.2` 396 | * Also add two additional hosts with A and PTR records: 397 | - host5.loc -> 192.168.0.2 398 | - host5.org -> 192.168.0.3 399 | 400 | ```bash 401 | docker run -i \ 402 | -p 53:53/tcp \ 403 | -p 53:53/udp \ 404 | -e DNS_A='*.loc=192.168.0.1, host5.loc=192.168.0.2, host5.org=192.168.0.3' \ 405 | -e DNS_PTR='192.168.0.2=host5.loc, 192.168.0.3=host5.org' \ 406 | -e DNS_FORWARDER=10.0.15.1,10.0.15.2 \ 407 | -t cytopia/bind 408 | ``` 409 | 410 | #### Extra hosts, DNS resolver, allow query, and allow recursion 411 | 412 | * Your trusted external DNS servers are `8.8.8.8` and `8.8.4.4` (google DNS servers) 413 | * Allow queries from: 414 | - All 192.168.0.xxx addresses 415 | - Localhost aka 127.0.0.1 416 | * Allow recursion to resolve other queries (such as www.google.com) from: 417 | - All 192.168.0.xxx addresses 418 | - Localhost aka 127.0.0.1 419 | * Add an extra hosts with custom DNS: 420 | - host1 -> 192.168.0.11 421 | 422 | ```bash 423 | docker run -i \ 424 | -p 53:53/tcp \ 425 | -p 53:53/udp \ 426 | -e DNS_A='host1=192.168.0.11' \ 427 | -e DNS_FORWARDER=8.8.8.8,8.8.4.4 \ 428 | -e ALLOW_QUERY=192.168.0.0/24,127.0.0.1 \ 429 | -e ALLOW_RECURSION=192.168.0.0/24,127.0.0.1 \ 430 | -t cytopia/bind 431 | ``` 432 | 433 | ## 🔧 Host integration 434 | 435 | You can run this DNS container locally without having to worry to affect any corporate DNS server 436 | that are given to you via DHCP. 437 | 438 | Add the following line to the very beginning to `/etc/dhcp/dhclient.conf`: 439 | ```bash 440 | prepend domain-name-servers 127.0.0.1; 441 | ``` 442 | Restart network manager 443 | ```bash 444 | # Via service command 445 | sudo service network-manager restart 446 | 447 | # Or the systemd way 448 | sudo systemctl restart network-manager 449 | ``` 450 | 451 | This will make sure that whenever your `/etc/resolv.conf` is deployed, you will have `127.0.0.1` 452 | as the first entry and also make use of any other DNS server which are deployed via the LAN's DHCP server. 453 | 454 | If `cytopia/bind` is not running, it does not affect the name resolution, because you will still 455 | have entries in `/etc/resolv.conf`. 456 | 457 | 458 | ## 🖤 Sister Projects 459 | 460 | Show some love for the following sister projects. 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 496 | 502 | 503 | 504 | 505 | 506 | 507 | 508 |
🖤 Project🐱 GitHub🐋 DockerHub
Devilbox
docker-php-fpmdevilbox/php-fpm
docker-php-fpm-communitydevilbox/php-fpm-community
docker-mysqldevilbox/mysql
491 | docker-apache-2.2
492 | docker-apache-2.4
493 | docker-nginx-stable
494 | docker-nginx-mainline 495 |
497 | devilbox/apache-2.2
498 | devilbox/apache-2.4
499 | devilbox/nginx-stable
500 | devilbox/nginx-mainline 501 |
docker-bindcytopia/bind
509 | 510 | 511 | ## 👫 Community 512 | 513 | In case you seek help, go and visit the community pages. 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 530 | 535 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 |

📘 Documentation

🎮 Discord

🗪 Forum

526 | 527 | 528 | 529 | 531 | 532 | 533 | 534 | 536 | 537 | 538 | 539 |
devilbox.readthedocs.iodiscord/devilboxdevilbox.discourse.group
548 | 549 | 550 | ## 📜 Articles 551 | 552 | * [Serving Bind DNS in Kubernetes](https://medium.com/swlh/serving-bind-dns-in-kubernetes-8639fce37448) 553 | 554 | 555 | ## ❤️ Credits 556 | 557 | Thanks for contributing 🖤 558 | 559 | - **[@atomicbaum1](https://github.com/atomicbaum1)** 560 | - **[@ericp-mrel](https://github.com/ericp-mrel)** 561 | - **[@Zighy](https://github.com/Zighy)** 562 | 563 | 564 | ## 🧘 Maintainer 565 | 566 | **[@cytopia](https://github.com/cytopia)** 567 | 568 | I try to keep up with literally **over 100 projects** besides a full-time job. 569 | If my work is making your life easier, consider contributing. 🖤 570 | 571 | * [GitHub Sponsorship](https://github.com/sponsors/cytopia) 572 | * [Patreon](https://www.patreon.com/devilbox) 573 | * [Open Collective](https://opencollective.com/devilbox) 574 | 575 | **Findme:** 576 | **🐱** [cytopia](https://github.com/cytopia) / [devilbox](https://github.com/devilbox) | 577 | **🐋** [cytopia](https://hub.docker.com/r/cytopia/) / [devilbox](https://hub.docker.com/r/devilbox/) | 578 | **🐦** [everythingcli](https://twitter.com/everythingcli) / [devilbox](https://twitter.com/devilbox) | 579 | **📖** [everythingcli.org](http://www.everythingcli.org/) 580 | 581 | **Contrib:** PyPI: [cytopia](https://pypi.org/user/cytopia/) **·** 582 | Terraform: [cytopia](https://registry.terraform.io/namespaces/cytopia) **·** 583 | Ansible: [cytopia](https://galaxy.ansible.com/cytopia) 584 | 585 | 586 | ## 🗎 License 587 | 588 | **[MIT License](LICENSE.md)** 589 | 590 | Copyright (c) 2022 [cytopia](https://github.com/cytopia) 591 | -------------------------------------------------------------------------------- /Dockerfiles/data/docker-entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | set -u 5 | set -o pipefail 6 | 7 | # Enable bash debugging for this entrypoint script 8 | if [ "${DEBUG:-}" = "1" ]; then 9 | set -x 10 | fi 11 | 12 | 13 | #################################################################################################### 14 | ### 15 | ### (1/6) VARIABLES 16 | ### 17 | #################################################################################################### 18 | 19 | ### 20 | ### Variables 21 | ### 22 | 23 | NAMED_DIR="/etc/bind" 24 | NAMED_CONF="${NAMED_DIR}/named.conf" 25 | NAMED_OPT_CONF="${NAMED_DIR}/named.conf.options" 26 | NAMED_LOG_CONF="${NAMED_DIR}/named.conf.logging" 27 | NAMED_CUST_CONF="${NAMED_DIR}/custom/conf" 28 | NAMED_CUST_ZONE="${NAMED_DIR}/custom/zone" 29 | 30 | # Recreate custom config directories 31 | if [ -d "${NAMED_CUST_CONF}" ]; then 32 | rm -rf "${NAMED_CUST_CONF}" 33 | fi 34 | if [ -d "${NAMED_CUST_ZONE}" ]; then 35 | rm -rf "${NAMED_CUST_ZONE}" 36 | fi 37 | mkdir -p "${NAMED_CUST_CONF}" 38 | mkdir -p "${NAMED_CUST_ZONE}" 39 | 40 | 41 | ### 42 | ### FQDN of primary nameserver. 43 | ### Defaults to current hostname if not otherwise specified. 44 | ### When overwriting, use an FQDN by which this container is reachable. 45 | ### http://rscott.org/dns/soa.html 46 | ### 47 | if [ -f "/etc/alpine-release" ]; then 48 | # Alpine 49 | DEFAULT_MNAME="$( hostname -f | sed 's/\s$//g' | xargs -0 )" 50 | else 51 | # Debian 52 | DEFAULT_MNAME="$( hostname -f | sed 's/\s$//g' | xargs -0 )" 53 | fi 54 | 55 | 56 | ### 57 | ### Contact Email 58 | ### All dot characters '.' must be escaped with an backslash '\' 59 | ### The actual @ character must be an unescaped dot character '.' 60 | ### 61 | DEFAULT_RNAME="admin.${DEFAULT_MNAME}" 62 | 63 | 64 | ### 65 | ### Default values of injectables environment variables 66 | ### 67 | DEFAULT_DEBUG_ENTRYPOINT=1 68 | DEFAULT_DOCKER_LOGS=0 69 | DEFAULT_DNSSEC_VALIDATE="no" 70 | 71 | ### 72 | ### Default time variables (time in seconds) 73 | ### 74 | DEFAULT_TTL_TIME=3600 75 | DEFAULT_REFRESH_TIME=1200 76 | DEFAULT_RETRY_TIME=180 77 | DEFAULT_EXPIRY_TIME=1209600 78 | DEFAULT_MAX_CACHE_TIME=10800 79 | DEFAULT_MAX_CACHE_SIZE="90%" 80 | 81 | 82 | 83 | #################################################################################################### 84 | ### 85 | ### (2/6) HELPER FUNCTIONS 86 | ### 87 | #################################################################################################### 88 | 89 | ### 90 | ### Log to stdout/stderr 91 | ### 92 | log() { 93 | local type="${1}" # ok, warn or err 94 | local message="${2}" # msg to print 95 | local debug="${3}" # 0: only warn and error, >0: ok and info 96 | 97 | local clr_ok="\033[0;32m" 98 | local clr_info="\033[0;34m" 99 | local clr_warn="\033[0;33m" 100 | local clr_err="\033[0;31m" 101 | local clr_rst="\033[0m" 102 | 103 | if [ "${type}" = "ok" ]; then 104 | if [ "${debug}" -gt "0" ]; then 105 | printf "${clr_ok}[OK] %s${clr_rst}\n" "${message}" 106 | fi 107 | elif [ "${type}" = "info" ]; then 108 | if [ "${debug}" -gt "0" ]; then 109 | printf "${clr_info}[INFO] %s${clr_rst}\n" "${message}" 110 | fi 111 | elif [ "${type}" = "warn" ]; then 112 | printf "${clr_warn}[WARN] %s${clr_rst}\n" "${message}" 1>&2 # stdout -> stderr 113 | elif [ "${type}" = "err" ]; then 114 | printf "${clr_err}[ERR] %s${clr_rst}\n" "${message}" 1>&2 # stdout -> stderr 115 | else 116 | printf "${clr_err}[???] %s${clr_rst}\n" "${message}" 1>&2 # stdout -> stderr 117 | fi 118 | } 119 | 120 | 121 | ### 122 | ### Log configuration file 123 | ### 124 | log_file() { 125 | local filename="${1}" 126 | 127 | echo 128 | printf "%0.s-" {1..80}; echo 129 | echo "${filename}" 130 | printf "%0.s-" {1..80}; echo 131 | cat "${filename}" 132 | printf "%0.s^" {1..80}; echo 133 | } 134 | 135 | 136 | ### 137 | ### Wrapper for run_run command 138 | ### 139 | run() { 140 | local cmd="${1}" # command to execute 141 | local debug="${2}" # show commands if debug level > 1 142 | 143 | local clr_red="\033[0;31m" 144 | local clr_green="\033[0;32m" 145 | local clr_reset="\033[0m" 146 | 147 | if [ "${debug}" -gt "1" ]; then 148 | printf "${clr_red}%s \$ ${clr_green}${cmd}${clr_reset}\n" "$( whoami )" 149 | fi 150 | /bin/sh -c "LANG=C LC_ALL=C ${cmd}" 151 | } 152 | 153 | 154 | ### 155 | ### Check if a value is an integer (positive or negative) 156 | ### 157 | is_int() { 158 | echo "${1}" | grep -Eq '^[-+]?[0-9]+$' 159 | } 160 | 161 | 162 | ### 163 | ### Check if a value has multiple lines 164 | ### 165 | is_multiline() { 166 | (( $(grep -c . <<<"${1}") > 1 )) 167 | } 168 | 169 | 170 | ### 171 | ### Check if a value is a valid IP address 172 | ### 173 | is_ip4_addr() { 174 | local regex='^([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})$' 175 | 176 | # Invalid input 177 | if is_multiline "${1}"; then 178 | return 1 179 | fi 180 | # Invalid IPv4 181 | if ! echo "${1}" | grep -Eq "${regex}"; then 182 | return 1 183 | fi 184 | 185 | # 4 IP octets 186 | local o1 187 | local o2 188 | local o3 189 | local o4 190 | 191 | # Get each octet 192 | o1="$( echo "${1}" | awk -F'.' '{print $1}' )" 193 | o2="$( echo "${1}" | awk -F'.' '{print $2}' )" 194 | o3="$( echo "${1}" | awk -F'.' '{print $3}' )" 195 | o4="$( echo "${1}" | awk -F'.' '{print $4}' )" 196 | 197 | # Cannot start with 0 and all must be below 256 198 | if [ "${o1}" -lt "1" ] || \ 199 | [ "${o1}" -gt "255" ] || \ 200 | [ "${o2}" -gt "255" ] || \ 201 | [ "${o3}" -gt "255" ] || \ 202 | [ "${o4}" -gt "255" ]; then 203 | return 1 204 | fi 205 | } 206 | 207 | 208 | ### 209 | ### Check if a value is a valid IPv4 address with CIDR mask 210 | ### 211 | is_ipv4_cidr() { 212 | # http://blog.markhatton.co.uk/2011/03/15/regular-expressions-for-ip-addresses-cidr-ranges-and-hostnames/ 213 | local regex='^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(3[0-2]|[1-2][0-9]|[0-9]))$' 214 | 215 | # Invalid input 216 | if is_multiline "${1}"; then 217 | return 1 218 | fi 219 | # Invalid IPv4 CIDR 220 | if ! echo "${1}" | grep -Eq "${regex}"; then 221 | return 1 222 | fi 223 | } 224 | 225 | 226 | ### 227 | ### Check if a value is a valid IPv4 address or IPv4 address with CIDR mask 228 | ### 229 | is_ipv4_addr_or_ipv4_cidr() { 230 | # Is IPv4 or IPv4 with mask 231 | if is_ip4_addr "${1}" || is_ipv4_cidr "${1}"; then 232 | return 0 233 | fi 234 | } 235 | 236 | 237 | ### 238 | ### Check if a value is a valid cname 239 | ### 240 | is_cname() { 241 | # https://stackoverflow.com/questions/106179/regular-expression-to-match-dns-hostname-or-ip-address 242 | local regex='^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$' 243 | 244 | # Is an IP already 245 | if is_ip4_addr "${1}" || is_ipv4_cidr "${1}"; then 246 | return 1 247 | fi 248 | 249 | # Match for valid CNAME 250 | echo "${1}" | grep -Eq "${regex}" 251 | } 252 | 253 | 254 | ### 255 | ### Check if a value matches any of four predefined address match list names 256 | ### 257 | is_address_match_list() { 258 | # Matches "any" or "none" or "localhost" or "localnets" 259 | if [[ "${1}" == "any" || "${1}" == "none" || "${1}" == "localhost" || "${1}" == "localnets" ]] ; then 260 | return 0 261 | fi 262 | 263 | # Failure 264 | return 1 265 | } 266 | 267 | 268 | 269 | #################################################################################################### 270 | ### 271 | ### (3/6) ACTION FUNCTIONS 272 | ### 273 | #################################################################################################### 274 | 275 | # Add Bind options with or without forwarder 276 | # 277 | # @param config_file Where to store this configuration in. 278 | # @param dnssec_validate dnssec-validation setting 279 | # @param forwarders formated (newline separated and trailing semi-colon) string of ip addr 280 | # @param allow_query formated (newline separated and trailing semi-colon) string of ipv4 addr with optional mask 281 | # @param allow_recursion formated (newline separated and trailing semi-colon) string of ipv4 addr with optional mask 282 | add_options() { 283 | local config_file="${1}" 284 | local dnssec_validate="${2}" 285 | local forwarders="${3}" 286 | local allow_query="${4}" 287 | local allow_recursion="${5}" 288 | local response_policy="${6}" 289 | local max_cache_size="${7}" 290 | 291 | { 292 | echo "options {" 293 | echo " directory \"/var/cache/bind\";" 294 | echo " dnssec-validation ${dnssec_validate};" 295 | echo " auth-nxdomain no; # conform to RFC1035" 296 | echo " listen-on-v6 { any; };" 297 | echo " max-cache-size ${max_cache_size};" 298 | if [ -n "${response_policy}" ]; then 299 | echo " response-policy { zone \"${response_policy}\"; };" 300 | fi 301 | if [ -n "${forwarders}" ]; then 302 | echo " forwarders {" 303 | # shellcheck disable=SC2059 304 | printf "${forwarders}\n" 305 | echo " };" 306 | fi 307 | if [ -n "${allow_recursion}" ]; then 308 | echo " recursion yes;" 309 | echo " allow-recursion {" 310 | # shellcheck disable=SC2059 311 | printf "${allow_recursion}\n" 312 | echo " };" 313 | fi 314 | if [ -n "${allow_query}" ]; then 315 | echo " allow-query {" 316 | # shellcheck disable=SC2059 317 | printf "${allow_query}\n" 318 | echo " };" 319 | fi 320 | echo "};" 321 | } > "${config_file}" 322 | 323 | # Output configuration file 324 | log_file "${config_file}" 325 | } 326 | 327 | 328 | ### 329 | ### Add Reverse zone 330 | ### 331 | add_rev_zone() { 332 | # Zone variables 333 | local addr="${1}" # A.B.C.D 334 | local name="${2}" # Domain / FQDN 335 | local zone="${3}" # C.B.A.in-addr.arpa 336 | local ptr="${4}" # D.C.B.A.in-addr.arpa 337 | 338 | # DNS timing variables 339 | local ttl_time="${5}" 340 | local refresh_time="${6}" 341 | local retry_time="${7}" 342 | local expiry_time="${8}" 343 | local max_cache_time="${9}" 344 | local serial 345 | serial="$( date +'%s' )" 346 | 347 | local debug_level="${10}" 348 | 349 | # Config file 350 | if [ ! -f "${NAMED_CUST_CONF}/${zone}.conf" ]; then 351 | { 352 | echo "zone \"${zone}\" {" 353 | echo " type master;" 354 | echo " allow-transfer { any; };" 355 | echo " allow-update { any; };" 356 | echo " file \"${NAMED_CUST_ZONE}/${zone}\";" 357 | echo "};" 358 | } > "${NAMED_CUST_CONF}/${zone}.conf" 359 | 360 | # Append config to bind 361 | echo "include \"${NAMED_CUST_CONF}/${zone}.conf\";" >> "${NAMED_CONF}" 362 | fi 363 | 364 | # Reverse zone file 365 | if [ ! -f "${NAMED_CUST_ZONE}/${zone}" ]; then 366 | { 367 | printf "\$TTL %s\n" "${ttl_time}" 368 | printf "%-29s IN SOA %s %s (\n" "@" "${DEFAULT_MNAME}." "${DEFAULT_RNAME}." 369 | printf "%-44s %-15s; Serial number\n" "" "${serial}" 370 | printf "%-44s %-15s; Refresh time\n" "" "${refresh_time}" 371 | printf "%-44s %-15s; Retry time\n" "" "${retry_time}" 372 | printf "%-44s %-15s; Expiry time\n" "" "${expiry_time}" 373 | printf "%-44s %-15s; Negative Cache TTL\n" "" "${max_cache_time}" 374 | echo ")" 375 | echo 376 | echo "; NS Records" 377 | printf "%-29s IN NS %-20s\n" "${zone}." "${DEFAULT_MNAME}." 378 | echo 379 | echo "; PTR Records" 380 | printf "%-29s IN PTR %-20s %s\n" "${ptr}." "${name}." "; ${addr}" 381 | 382 | } > "${NAMED_CUST_ZONE}/${zone}" 383 | else 384 | { 385 | printf "%-29s IN PTR %-20s %s\n" "${ptr}." "${name}." "; ${addr}" 386 | } >> "${NAMED_CUST_ZONE}/${zone}" 387 | fi 388 | 389 | # Validate .conf file 390 | if ! output="$( named-checkconf "${NAMED_CUST_CONF}/${zone}.conf" 2>&1 )"; then 391 | log "err" "Configuration failed." "${debug_level}" 392 | if [ -n "${output}" ]; then 393 | echo "${output}" 394 | fi 395 | log_file "${NAMED_CUST_CONF}/${zone}.conf" 396 | exit 1 397 | elif [ "${debug_level}" -gt "1" ]; then 398 | if [ -n "${output}" ]; then 399 | echo "${output}" 400 | fi 401 | fi 402 | # Validate reverze zone file 403 | if ! output="$( named-checkzone "${zone}" "${NAMED_CUST_ZONE}/${zone}" 2>&1 )"; then 404 | log "err" "Configuration failed." "${debug_level}" 405 | if [ -n "${output}" ]; then 406 | echo "${output}" 407 | fi 408 | log_file "${NAMED_CUST_ZONE}/${zone}" 409 | exit 1 410 | elif [ "${debug_level}" -gt "1" ]; then 411 | if [ -n "${output}" ]; then 412 | echo "${output}" 413 | fi 414 | fi 415 | } 416 | 417 | 418 | ### 419 | ### Add Forward zone (response policy zone) 420 | ### 421 | add_fwd_zone() { 422 | # Zone variables 423 | local domain="${1}" # The domain to translate 424 | local record="${2}" # The record type (A, CNAME, etc) 425 | local target="${3}" # The target to translate domain to 426 | 427 | # DNS timing variables 428 | local ttl_time="${4}" 429 | local refresh_time="${5}" 430 | local retry_time="${6}" 431 | local expiry_time="${7}" 432 | local max_cache_time="${8}" 433 | local serial 434 | serial="$( date +'%s' )" 435 | 436 | local debug_level="${9}" 437 | 438 | # Config file 439 | if [ ! -f "${NAMED_CUST_CONF}/rpz.conf" ]; then 440 | { 441 | echo "zone \"rpz\" IN {" 442 | echo " type master;" 443 | echo " allow-transfer { any; };" 444 | echo " allow-update { any; };" 445 | echo " file \"${NAMED_CUST_ZONE}/rpz\";" 446 | echo "};" 447 | } > "${NAMED_CUST_CONF}/rpz.conf" 448 | 449 | # Append config to bind 450 | echo "include \"${NAMED_CUST_CONF}/rpz.conf\";" >> "${NAMED_CONF}" 451 | fi 452 | 453 | # forward zone file 454 | if [ ! -f "${NAMED_CUST_ZONE}/rpz" ]; then 455 | { 456 | #printf "\$ORIGIN %s\n" "${DEFAULT_MNAME}" 457 | printf "\$TTL %s\n" "${ttl_time}" 458 | printf "%-29s IN SOA %s %s (\n" "@" "${DEFAULT_MNAME}." "${DEFAULT_RNAME}." 459 | printf "%-44s %-15s; Serial number\n" "" "${serial}" 460 | printf "%-44s %-15s; Refresh time\n" "" "${refresh_time}" 461 | printf "%-44s %-15s; Retry time\n" "" "${retry_time}" 462 | printf "%-44s %-15s; Expiry time\n" "" "${expiry_time}" 463 | printf "%-44s %-15s; Negative Cache TTL\n" "" "${max_cache_time}" 464 | echo ")" 465 | echo 466 | echo "; NS Records" 467 | printf "%-29s IN %-7s %s\n" "" "NS" "${DEFAULT_MNAME}." 468 | echo 469 | echo "; Custom Records" 470 | printf "%-29s IN %-7s %s\n" "${domain}" "${record}" "${target}" 471 | } > "${NAMED_CUST_ZONE}/rpz" 472 | else 473 | { 474 | printf "%-29s IN %-7s %s\n" "${domain}" "${record}" "${target}" 475 | } >> "${NAMED_CUST_ZONE}/rpz" 476 | fi 477 | 478 | # Validate .conf file 479 | if ! output="$( named-checkconf "${NAMED_CUST_CONF}/rpz.conf" 2>&1 )"; then 480 | log "err" "Configuration failed." "${debug_level}" 481 | if [ -n "${output}" ]; then 482 | echo "${output}" 483 | fi 484 | log_file "${NAMED_CUST_CONF}/rpz.conf" 485 | exit 1 486 | elif [ "${debug_level}" -gt "1" ]; then 487 | if [ -n "${output}" ]; then 488 | echo "${output}" 489 | fi 490 | fi 491 | # Validate zone file 492 | if ! output="$( named-checkzone "rpz" "${NAMED_CUST_ZONE}/rpz" 2>&1 )"; then 493 | log "err" "Configuration failed." "${debug_level}" 494 | if [ -n "${output}" ]; then 495 | echo "${output}" 496 | fi 497 | log_file "${NAMED_CUST_CONF}/rpz.conf" 498 | log_file "${NAMED_CUST_ZONE}/rpz" 499 | exit 1 500 | elif [ "${debug_level}" -gt "1" ]; then 501 | if [ -n "${output}" ]; then 502 | echo "${output}" 503 | fi 504 | fi 505 | } 506 | 507 | 508 | 509 | #################################################################################################### 510 | ### 511 | ### (4/6) BOOTSTRAP 512 | ### 513 | #################################################################################################### 514 | 515 | ### 516 | ### Set Debug level 517 | ### 518 | if printenv DEBUG_ENTRYPOINT >/dev/null 2>&1; then 519 | DEBUG_ENTRYPOINT="$( printenv DEBUG_ENTRYPOINT )" 520 | if ! is_int "${DEBUG_ENTRYPOINT}"; then 521 | log "warn" "Wrong value for DEBUG_ENTRYPOINT: '${DEBUG_ENTRYPOINT}'. Setting to ${DEFAULT_DEBUG_ENTRYPOINT}" "2" 522 | DEBUG_ENTRYPOINT="${DEFAULT_DEBUG_ENTRYPOINT}" 523 | else 524 | if [ "${DEBUG_ENTRYPOINT}" -lt "0" ]; then 525 | log "warn" "Wrong value for DEBUG_ENTRYPOINT: '${DEBUG_ENTRYPOINT}'. Setting to ${DEFAULT_DEBUG_ENTRYPOINT}" "2" 526 | DEBUG_ENTRYPOINT="${DEFAULT_DEBUG_ENTRYPOINT}" 527 | elif [ "${DEBUG_ENTRYPOINT}" -gt "2" ]; then 528 | log "warn" "Wrong value for DEBUG_ENTRYPOINT: '${DEBUG_ENTRYPOINT}'. Setting to ${DEFAULT_DEBUG_ENTRYPOINT}" "2" 529 | DEBUG_ENTRYPOINT="${DEFAULT_DEBUG_ENTRYPOINT}" 530 | fi 531 | fi 532 | else 533 | DEBUG_ENTRYPOINT="${DEFAULT_DEBUG_ENTRYPOINT}" 534 | fi 535 | log "info" "Debug level: ${DEBUG_ENTRYPOINT}" "${DEBUG_ENTRYPOINT}" 536 | 537 | 538 | 539 | #################################################################################################### 540 | ### 541 | ### (5/6) ENTRYPOINT (DEFAULTS) 542 | ### 543 | #################################################################################################### 544 | 545 | ### 546 | ### Re-create BIND default config 547 | ### 548 | { 549 | echo "include \"${NAMED_LOG_CONF}\";" 550 | echo "include \"${NAMED_OPT_CONF}\";" 551 | if [ -f "/etc/bind/named.conf.local" ]; then 552 | echo "include \"/etc/bind/named.conf.local\";" 553 | fi 554 | if [ -f "/etc/bind/named.conf.default-zones" ]; then 555 | echo "include \"/etc/bind/named.conf.default-zones\";" 556 | fi 557 | } > "${NAMED_CONF}" 558 | log_file "${NAMED_CONF}" 559 | 560 | 561 | ### 562 | ### Enable Logging to Docker logs 563 | ### https://stackoverflow.com/questions/11153958/how-to-enable-named-bind-dns-full-logging#12114139 564 | ### 565 | echo > "${NAMED_LOG_CONF}" 566 | if printenv DOCKER_LOGS >/dev/null 2>&1; then 567 | DOCKER_LOGS="$( printenv DOCKER_LOGS )" 568 | if [ "${DOCKER_LOGS}" = "1" ]; then 569 | { 570 | echo "logging {" 571 | echo " category default { default_stderr; };" 572 | echo " category queries { default_stderr; };" 573 | echo "};" 574 | } > "${NAMED_LOG_CONF}" 575 | log "info" "BIND logging: to stderr via Docker logs" "${DEBUG_ENTRYPOINT}" 576 | 577 | # Output configuration file 578 | log_file "${NAMED_LOG_CONF}" 579 | elif [ "${DOCKER_LOGS}" = "0" ]; then 580 | log "info" "BIND logging: disabled explicitly" "${DEBUG_ENTRYPOINT}" 581 | else 582 | log "warn" "Wrong value for \$DOCKER_LOGS: '${DOCKER_LOGS}'. Only supports: '1' or '0'" "${DEBUG_ENTRYPOINT}" 583 | DOCKER_LOGS="${DEFAULT_DOCKER_LOGS}" 584 | fi 585 | fi 586 | 587 | 588 | ### 589 | ### DNS Time settings 590 | ### 591 | if printenv TTL_TIME >/dev/null 2>&1 && [ -n "$( printenv TTL_TIME )" ]; then 592 | TTL_TIME="$( printenv TTL_TIME )" 593 | if is_int "${TTL_TIME}" && [ "${TTL_TIME}" -gt "0" ]; then 594 | log "info" "Changing DNS TTL time to: ${TTL_TIME} sec" "${DEBUG_ENTRYPOINT}" 595 | else 596 | log "warn" "Wrong value for \$TTL_TIME '${TTL_TIME}', defaultint to: ${DEFAULT_TTL_TIME}" "${DEBUG_ENTRYPOINT}" 597 | TTL_TIME="${DEFAULT_TTL_TIME}" 598 | fi 599 | else 600 | log "info" "Using default DNS TTL time: ${DEFAULT_TTL_TIME} sec" "${DEBUG_ENTRYPOINT}" 601 | TTL_TIME="${DEFAULT_TTL_TIME}" 602 | fi 603 | 604 | if printenv REFRESH_TIME >/dev/null 2>&1 && [ -n "$( printenv REFRESH_TIME )" ]; then 605 | REFRESH_TIME="$( printenv REFRESH_TIME )" 606 | if is_int "${REFRESH_TIME}" && [ "${REFRESH_TIME}" -gt "0" ]; then 607 | log "info" "Changing DNS Refresh time to: ${REFRESH_TIME} sec" "${DEBUG_ENTRYPOINT}" 608 | else 609 | log "warn" "Wrong value for \$REFRESH_TIME '${REFRESH_TIME}', defaultint to: ${DEFAULT_REFRESH_TIME}" "${DEBUG_ENTRYPOINT}" 610 | REFRESH_TIME="${DEFAULT_REFRESH_TIME}" 611 | fi 612 | else 613 | log "info" "Using default DNS Refresh time: ${DEFAULT_REFRESH_TIME} sec" "${DEBUG_ENTRYPOINT}" 614 | REFRESH_TIME="${DEFAULT_REFRESH_TIME}" 615 | fi 616 | 617 | if printenv RETRY_TIME >/dev/null 2>&1 && [ -n "$( printenv RETRY_TIME )" ]; then 618 | RETRY_TIME="$( printenv RETRY_TIME )" 619 | if is_int "${RETRY_TIME}" && [ "${RETRY_TIME}" -gt "0" ]; then 620 | log "info" "Changing DNS Retry time to: ${RETRY_TIME} sec" "${DEBUG_ENTRYPOINT}" 621 | else 622 | log "warn" "Wrong value for \$RETRY_TIME '${RETRY_TIME}', defaultint to: ${DEFAULT_RETRY_TIME}" "${DEBUG_ENTRYPOINT}" 623 | RETRY_TIME="${DEFAULT_RETRY_TIME}" 624 | fi 625 | else 626 | log "info" "Using default DNS Retry time: ${DEFAULT_RETRY_TIME} sec" "${DEBUG_ENTRYPOINT}" 627 | RETRY_TIME="${DEFAULT_RETRY_TIME}" 628 | fi 629 | 630 | if printenv EXPIRY_TIME >/dev/null 2>&1 && [ -n "$( printenv EXPIRY_TIME )" ]; then 631 | EXPIRY_TIME="$( printenv EXPIRY_TIME )" 632 | if is_int "${EXPIRY_TIME}" && [ "${EXPIRY_TIME}" -gt "0" ]; then 633 | log "info" "Changing DNS Expiry time to: ${EXPIRY_TIME} sec" "${DEBUG_ENTRYPOINT}" 634 | else 635 | log "warn" "Wrong value for \$EXPIRY_TIME '${EXPIRY_TIME}', defaultint to: ${DEFAULT_EXPIRY_TIME}" "${DEBUG_ENTRYPOINT}" 636 | EXPIRY_TIME="${DEFAULT_EXPIRY_TIME}" 637 | fi 638 | else 639 | log "info" "Using default DNS Expiry time: ${DEFAULT_EXPIRY_TIME} sec" "${DEBUG_ENTRYPOINT}" 640 | EXPIRY_TIME="${DEFAULT_EXPIRY_TIME}" 641 | fi 642 | 643 | if printenv MAX_CACHE_TIME >/dev/null 2>&1 && [ -n "$( printenv MAX_CACHE_TIME )" ]; then 644 | MAX_CACHE_TIME="$( printenv MAX_CACHE_TIME )" 645 | if is_int "${MAX_CACHE_TIME}" && [ "${MAX_CACHE_TIME}" -gt "0" ]; then 646 | log "info" "Changing DNS Max Cache time to: ${MAX_CACHE_TIME} sec" "${DEBUG_ENTRYPOINT}" 647 | else 648 | log "warn" "Wrong value for \$MAX_CACHE_TIME '${MAX_CACHE_TIME}', defaultint to: ${DEFAULT_MAX_CACHE_TIME}" "${DEBUG_ENTRYPOINT}" 649 | MAX_CACHE_TIME="${DEFAULT_MAX_CACHE_TIME}" 650 | fi 651 | else 652 | log "info" "Using default DNS Max Cache time: ${DEFAULT_MAX_CACHE_TIME} sec" "${DEBUG_ENTRYPOINT}" 653 | MAX_CACHE_TIME="${DEFAULT_MAX_CACHE_TIME}" 654 | fi 655 | 656 | if printenv MAX_CACHE_SIZE >/dev/null 2>&1 && [ -n "$( printenv MAX_CACHE_SIZE )" ]; then 657 | MAX_CACHE_SIZE="$( printenv MAX_CACHE_SIZE )" 658 | if [ "${MAX_CACHE_SIZE}" = "unlimited" ]; then 659 | log "info" "Changing DNS Max Cache size to: ${MAX_CACHE_SIZE}" "${DEBUG_ENTRYPOINT}" 660 | elif [ "${MAX_CACHE_SIZE}" = "0" ]; then 661 | log "info" "Changing DNS Max Cache size to: ${MAX_CACHE_SIZE}" "${DEBUG_ENTRYPOINT}" 662 | # Extract value and unit 663 | else 664 | MAX_CACHE_SIZE_VALUE="$( echo "${MAX_CACHE_SIZE}" | grep -Eo '[0-9]+' )" 665 | MAX_CACHE_SIZE_UNIT="$( echo "${MAX_CACHE_SIZE}" | grep -Eo '[^0-9]+' )" # Allowed: %, k, K, m, M, g, G or empty 666 | 667 | if [ -z "${MAX_CACHE_SIZE_VALUE}" ]; then 668 | log "warn" "Wrong value for \$MAX_CACHE_SIZE '${MAX_CACHE_SIZE}', defaultint to: ${DEFAULT_MAX_CACHE_SIZE}" "${DEBUG_ENTRYPOINT}" 669 | MAX_CACHE_SIZE="${DEFAULT_MAX_CACHE_SIZE}" 670 | elif [ -z "${MAX_CACHE_SIZE_UNIT}" ]; then 671 | log "info" "Changing DNS Max Cache size to: ${MAX_CACHE_SIZE}" "${DEBUG_ENTRYPOINT}" 672 | MAX_CACHE_SIZE="${MAX_CACHE_SIZE_VALUE}" 673 | else 674 | # Validate correct unit 675 | if ! echo "${MAX_CACHE_SIZE_UNIT}" | grep -E '[%kKmMgG]' >/dev/null; then 676 | log "warn" "Wrong unit for \$MAX_CACHE_SIZE '${MAX_CACHE_SIZE_UNIT}', defaultint to: ${DEFAULT_MAX_CACHE_SIZE}" "${DEBUG_ENTRYPOINT}" 677 | MAX_CACHE_SIZE="${DEFAULT_MAX_CACHE_SIZE}" 678 | else 679 | log "info" "Changing DNS Max Cache size to: ${MAX_CACHE_SIZE}" "${DEBUG_ENTRYPOINT}" 680 | MAX_CACHE_SIZE="${MAX_CACHE_SIZE_VALUE}${MAX_CACHE_SIZE_UNIT}" 681 | fi 682 | fi 683 | fi 684 | else 685 | log "info" "Using default DNS Max Cache size: ${DEFAULT_MAX_CACHE_SIZE}" "${DEBUG_ENTRYPOINT}" 686 | MAX_CACHE_SIZE="${DEFAULT_MAX_CACHE_SIZE}" 687 | fi 688 | 689 | 690 | 691 | #################################################################################################### 692 | ### 693 | ### (6/6) ENTRYPOINT (ZONES) 694 | ### 695 | #################################################################################################### 696 | 697 | REV_ZONES="" 698 | FWD_ZONES="" 699 | 700 | ### 701 | ### Add Reverse DNS 702 | ### 703 | if printenv DNS_PTR >/dev/null 2>&1; then 704 | while read -r line; do 705 | line="$( echo "${line}" | xargs -0 )" 706 | if [ -z "${line}" ]; then 707 | continue # For leading or trailing comma in DNS_PTR variable 708 | fi 709 | addr="$( echo "${line}" | awk -F '=' '{print $1}' | xargs -0 )" 710 | name="$( echo "${line}" | awk -F '=' '{print $2}' | xargs -0 )" 711 | 712 | # Extract IP address octets 713 | o1="$( echo "${addr}" | awk -F '.' '{print $1}' )" 714 | o2="$( echo "${addr}" | awk -F '.' '{print $2}' )" 715 | o3="$( echo "${addr}" | awk -F '.' '{print $3}' )" 716 | o4="$( echo "${addr}" | awk -F '.' '{print $4}' )" 717 | zone="${o3}.${o2}.${o1}.in-addr.arpa" 718 | ptr="${o4}.${o3}.${o2}.${o1}.in-addr.arpa" 719 | 720 | # Append zones and get unique ones by newline separated 721 | REV_ZONES="$( echo "${REV_ZONES}"$'\n'"${zone}" | grep -vE '^$' | sort -u )" 722 | 723 | log "info" "Adding PTR Record: ${addr} -> ${name}" "${DEBUG_ENTRYPOINT}" 724 | add_rev_zone \ 725 | "${addr}" \ 726 | "${name}" \ 727 | "${zone}" \ 728 | "${ptr}" \ 729 | "${TTL_TIME}" \ 730 | "${REFRESH_TIME}" \ 731 | "${RETRY_TIME}" \ 732 | "${EXPIRY_TIME}" \ 733 | "${MAX_CACHE_TIME}" \ 734 | "${DEBUG_ENTRYPOINT}" 735 | done <<< "${DNS_PTR//,/$'\n'}" 736 | else 737 | log "info" "Not adding any PTR records" "${DEBUG_ENTRYPOINT}" 738 | fi 739 | 740 | 741 | ### 742 | ### Build forward zones (A Record) 743 | ### 744 | if printenv DNS_A >/dev/null 2>&1; then 745 | while read -r line; do 746 | line="$( echo "${line}" | xargs -0 )" 747 | if [ -z "${line}" ]; then 748 | continue # For leading or trailing comma in DNS_A variable 749 | fi 750 | name="$( echo "${line}" | awk -F '=' '{print $1}' | xargs -0 )" 751 | addr="$( echo "${line}" | awk -F '=' '{print $2}' | xargs -0 )" 752 | 753 | # Only a single zone used for forward zones (response policy zone) 754 | FWD_ZONES="rpz" 755 | 756 | log "info" "Adding A Record: ${name} -> ${addr}" "${DEBUG_ENTRYPOINT}" 757 | add_fwd_zone \ 758 | "${name}" \ 759 | "A" \ 760 | "${addr}" \ 761 | "${TTL_TIME}" \ 762 | "${REFRESH_TIME}" \ 763 | "${RETRY_TIME}" \ 764 | "${EXPIRY_TIME}" \ 765 | "${MAX_CACHE_TIME}" \ 766 | "${DEBUG_ENTRYPOINT}" 767 | done <<< "${DNS_A//,/$'\n'}" 768 | else 769 | log "info" "Not adding any A records" "${DEBUG_ENTRYPOINT}" 770 | fi 771 | 772 | 773 | ### 774 | ### Build forward zones (CNAME Record) 775 | ### 776 | if printenv DNS_CNAME >/dev/null 2>&1; then 777 | while read -r line; do 778 | line="$( echo "${line}" | xargs -0 )" 779 | if [ -z "${line}" ]; then 780 | continue # For leading or trailing comma in DNS_CNAME variable 781 | fi 782 | name="$( echo "${line}" | awk -F '=' '{print $1}' | xargs -0 )" 783 | addr="$( echo "${line}" | awk -F '=' '{print $2}' | xargs -0 )" 784 | 785 | # Only a single zone used for forward zones (response policy zone) 786 | FWD_ZONES="rpz" 787 | 788 | log "info" "Adding CNAME Record: ${name} -> ${addr}" "${DEBUG_ENTRYPOINT}" 789 | add_fwd_zone \ 790 | "${name}" \ 791 | "CNAME" \ 792 | "${addr}." \ 793 | "${TTL_TIME}" \ 794 | "${REFRESH_TIME}" \ 795 | "${RETRY_TIME}" \ 796 | "${EXPIRY_TIME}" \ 797 | "${MAX_CACHE_TIME}" \ 798 | "${DEBUG_ENTRYPOINT}" 799 | done <<< "${DNS_CNAME//,/$'\n'}" 800 | else 801 | log "info" "Not adding any CNAME records" "${DEBUG_ENTRYPOINT}" 802 | fi 803 | 804 | 805 | ### 806 | ### Allow query 807 | ### 808 | _allow_query_block="" 809 | if ! printenv ALLOW_QUERY >/dev/null 2>&1; then 810 | log "info" "\$ALLOW_QUERY not set." "${DEBUG_ENTRYPOINT}" 811 | log "info" "DNS query rules will not be set" "${DEBUG_ENTRYPOINT}" 812 | else 813 | # Transform into newline separated forwards and loop over: 814 | # x.x.x.x\n 815 | # y.y.y.y\n 816 | while read -r ip ; do 817 | ip="$( echo "${ip}" | xargs -0 )" 818 | 819 | if ! is_ipv4_addr_or_ipv4_cidr "${ip}" && ! is_address_match_list "${ip}"; then 820 | log "err" "ALLOW_QUERY error: not a valid IPv4 address with optional mask: ${ip}" "${DEBUG_ENTRYPOINT}" 821 | exit 1 822 | fi 823 | 824 | if [ -z "${_allow_query_block}" ]; then 825 | _allow_query_block=" ${ip};" 826 | else 827 | _allow_query_block="${_allow_query_block}\n ${ip};" 828 | fi 829 | done <<< "$( printenv ALLOW_QUERY | sed 's/,/\n/g' )" 830 | 831 | 832 | if [ -z "${_allow_query_block}" ]; then 833 | log "err" "ALLOW_QUERY error: variable specified, but no IP addresses found." "${DEBUG_ENTRYPOINT}" 834 | exit 1 835 | fi 836 | # shellcheck disable=SC2153 837 | log "info" "Adding custom allow-query options: ${ALLOW_QUERY}" "${DEBUG_ENTRYPOINT}" 838 | fi 839 | 840 | 841 | ### 842 | ### Allow recursion 843 | ### 844 | _allow_recursion_block="" 845 | if ! printenv ALLOW_RECURSION >/dev/null 2>&1; then 846 | log "info" "\$ALLOW_RECURSION not set." "${DEBUG_ENTRYPOINT}" 847 | log "info" "DNS recursion rules will not be set" "${DEBUG_ENTRYPOINT}" 848 | else 849 | # Transform into newline separated forwards and loop over: 850 | # x.x.x.x\n 851 | # y.y.y.y\n 852 | while read -r ip ; do 853 | ip="$( echo "${ip}" | xargs -0 )" 854 | 855 | if ! is_ipv4_addr_or_ipv4_cidr "${ip}" && ! is_address_match_list "${ip}"; then 856 | log "err" "ALLOW_RECURSION error: not a valid IPv4 address with optional mask: ${ip}" "${DEBUG_ENTRYPOINT}" 857 | exit 1 858 | fi 859 | 860 | if [ -z "${_allow_recursion_block}" ]; then 861 | _allow_recursion_block=" ${ip};" 862 | else 863 | _allow_recursion_block="${_allow_recursion_block}\n ${ip};" 864 | fi 865 | done <<< "$( printenv ALLOW_RECURSION | sed 's/,/\n/g' )" 866 | 867 | 868 | if [ -z "${_allow_recursion_block}" ]; then 869 | log "err" "ALLOW_RECURSION error: variable specified, but no IP addresses found." "${DEBUG_ENTRYPOINT}" 870 | exit 1 871 | fi 872 | # shellcheck disable=SC2153 873 | log "info" "Adding custom allow-recursion options: ${ALLOW_RECURSION}" "${DEBUG_ENTRYPOINT}" 874 | fi 875 | 876 | 877 | ### 878 | ### DNSSEC validation 879 | ### 880 | if printenv DNSSEC_VALIDATE >/dev/null 2>&1; then 881 | DNSSEC_VALIDATE="$( printenv DNSSEC_VALIDATE )" 882 | if [ "${DNSSEC_VALIDATE}" = "auto" ]; then 883 | DNSSEC_VALIDATE="auto" 884 | elif [ "${DNSSEC_VALIDATE}" = "yes" ]; then 885 | DNSSEC_VALIDATE="yes" 886 | elif [ "${DNSSEC_VALIDATE}" = "no" ]; then 887 | DNSSEC_VALIDATE="no" 888 | else 889 | log "warning" "Wrong value for DNSSEC_VALIDATE: '${DNSSEC_VALIDATE}'. Setting it to '${DEFAULT_DNSSEC_VALIDATE}'" "${DEBUG_ENTRYPOINT}" 890 | DNSSEC_VALIDATE="${DEFAULT_DNSSEC_VALIDATE}" 891 | fi 892 | else 893 | DNSSEC_VALIDATE="${DEFAULT_DNSSEC_VALIDATE}" 894 | fi 895 | log "info" "DNSSEC Validation: ${DNSSEC_VALIDATE}" "${DEBUG_ENTRYPOINT}" 896 | 897 | 898 | ### 899 | ### Forwarder 900 | ### 901 | if ! printenv DNS_FORWARDER >/dev/null 2>&1; then 902 | log "info" "\$DNS_FORWARDER not set." "${DEBUG_ENTRYPOINT}" 903 | log "info" "No custom DNS server will be used as forwarder" "${DEBUG_ENTRYPOINT}" 904 | 905 | add_options \ 906 | "${NAMED_OPT_CONF}" \ 907 | "${DNSSEC_VALIDATE}" \ 908 | "" \ 909 | "${_allow_query_block}" \ 910 | "${_allow_recursion_block}" \ 911 | "${FWD_ZONES}" \ 912 | "${MAX_CACHE_SIZE}" 913 | else 914 | 915 | # To be pupulated 916 | _forwarders_block="" 917 | 918 | # Transform into newline separated forwards and loop over: 919 | # x.x.x.x\n 920 | # y.y.y.y\n 921 | while read -r ip ; do 922 | ip="$( echo "${ip}" | xargs -0 )" 923 | 924 | if ! is_ip4_addr "${ip}"; then 925 | log "err" "DNS_FORWARDER error: not a valid IP address: ${ip}" "${DEBUG_ENTRYPOINT}" 926 | exit 1 927 | fi 928 | 929 | if [ -z "${_forwarders_block}" ]; then 930 | _forwarders_block=" ${ip};" 931 | else 932 | _forwarders_block="${_forwarders_block}\n ${ip};" 933 | fi 934 | done <<< "$( printenv DNS_FORWARDER | sed 's/,/\n/g' )" 935 | 936 | if [ -z "${_forwarders_block}" ]; then 937 | log "err" "DNS_FORWARDER error: variable specified, but no IP addresses found." "${DEBUG_ENTRYPOINT}" 938 | exit 1 939 | fi 940 | 941 | log "info" "Adding custom DNS forwarder: ${DNS_FORWARDER}" "${DEBUG_ENTRYPOINT}" 942 | add_options \ 943 | "${NAMED_OPT_CONF}" \ 944 | "${DNSSEC_VALIDATE}" \ 945 | "${_forwarders_block}" \ 946 | "${_allow_query_block}" \ 947 | "${_allow_recursion_block}" \ 948 | "${FWD_ZONES}" \ 949 | "${MAX_CACHE_SIZE}" 950 | fi 951 | 952 | 953 | ### 954 | ### Log configured zones 955 | ### 956 | while IFS= read -r line; do 957 | if [ -n "${line}" ]; then 958 | log_file "${NAMED_CUST_CONF}/${line}.conf" 959 | log_file "${NAMED_CUST_ZONE}/${line}" 960 | fi 961 | done <<< "${REV_ZONES}" 962 | while IFS= read -r line; do 963 | if [ -n "${line}" ]; then 964 | log_file "${NAMED_CUST_CONF}/${line}.conf" 965 | log_file "${NAMED_CUST_ZONE}/${line}" 966 | fi 967 | done <<< "${FWD_ZONES}" 968 | 969 | 970 | ### 971 | ### Start 972 | ### 973 | log "info" "Starting $( named -V | grep -oiE '^BIND[[:space:]]+[0-9.]+' )" "${DEBUG_ENTRYPOINT}" 974 | named-checkconf "${NAMED_CONF}" 975 | exec /usr/sbin/named -4 -c /etc/bind/named.conf -u "${USER}" -f 976 | --------------------------------------------------------------------------------