[
  {
    "path": ".codespellignore",
    "content": "padd\n"
  },
  {
    "path": ".editorconfig",
    "content": "# EditorConfig is awesome: https://editorconfig.org/\n\n# top-most EditorConfig file\nroot = true\n\n# Unix-style newlines with a newline ending every file\n[*]\nend_of_line = lf\ninsert_final_newline = true\nindent_style = space\nindent_size = tab\ntab_width = 4\ncharset = utf-8\ntrim_trailing_whitespace = true\n\n[*.yml]\ntab_width = 2\n\n[*.md]\ntab_width = 2\n"
  },
  {
    "path": ".github/CODEOWNERS",
    "content": "# see https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners#codeowners-syntax\n\n# These owners will be the default owners for everything in\n# the repo. Unless a later match takes precedence,\n*       @pi-hole/docker-maintainers\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n<!-- Provide a general summary of the issue in the Title above -->\n<!-- Note: these are comments that don't show up in the actual issue, no need to delete them as you fill out the template -->\n\n<!-- IMPORTANT Complete the entire template please, the info gathered here is usually needed to debug issues anyway so it saves time in the long run. Incomplete/stock template issues may be closed -->\n\n<!-- pick ONE: Bug,\n               Feature Request,\n               Run Issue (running Pi-hole container failing),\n               Build Issue (Building image failing)\nEnter in line below: -->\nThis is a: **FILL ME IN**\n\n\n## Details\n<!-- Provide a more detailed introduction to the issue or feature, try not to duplicate info from lower sections by reviewing the entire template first -->\n\n## Related Issues\n- [ ] I have searched this repository/Pi-hole forums for existing issues and pull requests that look similar\n<!-- Add links below! -->\n\n<!------- FEATURE REQUESTS CAN STOP FILLING IN TEMPLATE HERE -------->\n<!------- ISSUES SHOULD FILL OUT REMAINDER OF TEMPLATE -------->\n\n## How to reproduce the issue\n\n1. Environment data\n  * Operating System: **ENTER HERE** <!-- Debian, Ubuntu, Rasbian, etc -->\n  * Hardware: <!-- PC, RasPi B/2B/3B/4B, Mac, Synology, QNAP, etc -->\n  * Kernel Architecture: <!-- x86/amd64, ArmV7, ArmV8 32bit, ArmV8 64bit, etc -->\n  * Docker Install Info and version:\n    - Software source: <!-- official docker-ce, OS provided package, Hypriot -->\n    - Supplimentary Software: <!-- synology, portainer, etc -->\n  * Hardware architecture: <!-- ARMv7, x86 -->\n\n2. docker-compose.yml contents, docker run shell command, or paste a screenshot of any UI based configuration of containers here\n3. any additional info to help reproduce\n\n\n## These common fixes didn't work for my issue\n<!-- IMPORTANT! Help me help you! Ordered with most common fixes first. -->\n- [ ] I have tried removing/destroying my container, and re-creating a new container\n- [ ] I have tried fresh volume data by backing up and moving/removing the old volume data\n- [ ] I have tried running the stock `docker run` example(s) in the readme (removing any customizations I added)\n- [ ] I have tried a newer or older version of Docker Pi-hole (depending what version the issue started in for me)\n- [ ] I have tried running without my volume data mounts to eliminate volumes as the cause\n\nIf the above debugging / fixes revealed any new information note it here.\nAdd any other debugging steps you've taken or theories on root cause that may help.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: Questions and Configurations\n    url: https://discourse.pi-hole.net\n    about: Ask a question or get help with configurations.\n  - name: Feature Requests\n    url: https://discourse.pi-hole.net/c/feature-requests/8\n    about: See existing Feature Requests and suggest new ones.\n  - name: Documentation\n    url: https://docs.pi-hole.net\n    about: Documentation and guides.\n\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "`{Please select 'base: development' as target branch above! (you can delete this line)}`\n\n<!--- Provide a general summary of your changes in the Title above -->\n\n## Description\n<!--- Describe your changes in detail -->\n\n## Motivation and Context\n<!--- Why is this change required? What problem does it solve? -->\n<!--- If it fixes an open issue, please link to the issue here. -->\n\n## How Has This Been Tested?\n<!--- Please describe in detail how you tested your changes. -->\n<!--- Include details of your testing environment, tests ran to see how -->\n<!--- your change affects other areas of the code, etc. -->\n\n## Types of changes\n<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->\n- [ ] Bug fix (non-breaking change which fixes an issue)\n- [ ] New feature (non-breaking change which adds functionality)\n- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)\n\n## Checklist:\n<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->\n<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->\n- [ ] My code follows the code style of this project.\n- [ ] My change requires a change to the documentation.\n- [ ] I have updated the documentation accordingly.\n"
  },
  {
    "path": ".github/dco.yml",
    "content": "require:\n  members: false\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  # Maintain dependencies for GitHub Actions\n  - package-ecosystem: \"github-actions\"\n    directories:\n      - \"/\"\n    schedule:\n      interval: \"weekly\"\n      day: saturday\n      time: \"10:00\"\n    target-branch: development\n  - package-ecosystem: \"docker\"\n    directory: \"/src/\"\n    schedule:\n      interval: \"weekly\"\n      day: saturday\n      time: \"10:00\"\n    target-branch: development\n  - package-ecosystem: pip\n    directory: \"/test\"\n    schedule:\n      interval: weekly\n      day: saturday\n      time: \"10:00\"\n    open-pull-requests-limit: 10\n    target-branch: development\n"
  },
  {
    "path": ".github/release.yml",
    "content": "changelog:\n  exclude:\n    labels:\n      - internal\n      - dependencies\n    authors:\n      - dependabot\n      - github-actions\n"
  },
  {
    "path": ".github/workflows/build-and-publish.yml",
    "content": "name: Build Image and Publish\non:\n  schedule:\n    - cron: \"0 5 * * *\"\n  push:\n    branches:\n      - development\n  release:\n    types: [published]\n\npermissions:\n  contents: read\n  packages: write\n\nenv:\n  dockerhub: ${{ secrets.DOCKERHUB_NAMESPACE }}/pihole\n  ghcr: ghcr.io/${{ github.repository_owner }}/pihole\n  components_branch: ${{ github.event_name == 'release' && 'master' || 'development' }}\n\njobs:\n  build:\n    runs-on: ${{ matrix.runner }}\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          - platform: linux/amd64\n            runner: ubuntu-latest\n          - platform: linux/386\n            runner: ubuntu-latest\n          - platform: linux/arm/v6\n            runner: ubuntu-24.04-arm\n          - platform: linux/arm/v7\n            runner: ubuntu-24.04-arm\n          - platform: linux/arm64\n            runner: ubuntu-24.04-arm\n          - platform: linux/riscv64\n            runner: ubuntu-24.04-arm\n\n    steps:\n      - name: Prepare name for digest up/download\n        run: |\n          platform=${{ matrix.platform }}\n          echo \"PLATFORM_PAIR=${platform//\\//-}\" >> $GITHUB_ENV\n\n      - &checkout-repo\n        name: Checkout Repo\n        if: github.event_name != 'schedule'\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2\n\n      - &checkout-dev\n        name: Checkout dev branch if scheduled\n        if: github.event_name == 'schedule'\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2\n        with:\n          ref: development\n\n      - &docker-meta\n        name: Docker meta\n        id: meta\n        uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 #v5.10.0\n        with:\n          github-token: ${{ secrets.GITHUB_TOKEN }}\n          images: |\n            ${{ env.dockerhub }}\n            ${{ env.ghcr }}\n          flavor: |\n            latest=${{ startsWith(github.ref, 'refs/tags/') }}\n          tags: |\n            type=schedule,pattern=nightly\n            type=raw,value=nightly,enable=${{ github.event_name == 'push' }}\n            type=ref,event=tag\n\n      - &login-dockerhub\n        name: Login to Docker Hub\n        uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 #v3.7.0\n        with:\n          registry: docker.io\n          username: ${{ secrets.DOCKERHUB_USER }}\n          password: ${{ secrets.DOCKERHUB_PASS }}\n\n      - &login-ghcr\n        name: Login to GitHub Container Registry\n        uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 #v3.7.0\n        with:\n          registry: ghcr.io\n          username: ${{ github.repository_owner }}\n          password: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Set up QEMU\n        uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 #v3.7.0\n        with:\n          platforms: ${{ matrix.platform}}\n\n      - &setup-buildx\n        name: Set up Docker Buildx\n        uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f #v3.12.0\n        with:\n          # Buildx version 0.31.1 broke our publish workflow, this need to be revised when 0.32.0 is released\n          # https://github.com/docker/buildx/releases/tag/v0.31.1\n          version: v0.31.0\n\n      - name: Build container and push by digest\n        id: build\n        uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 #v6.19.2\n        with:\n          context: ./src/\n          platforms: ${{ matrix.platform }}\n          build-args: |\n            PIHOLE_DOCKER_TAG=${{ steps.meta.outputs.version }}\n            FTL_BRANCH=${{ env.components_branch }}\n            CORE_BRANCH=${{ env.components_branch }}\n            WEB_BRANCH=${{ env.components_branch }}\n            PADD_BRANCH=${{ env.components_branch }}\n          labels: ${{ steps.meta.outputs.labels }}\n          outputs: |\n            type=image,name=${{ env.dockerhub }},push-by-digest=true,name-canonical=true,push=true\n\n      - name: Export digests\n        run: |\n          mkdir -p /tmp/digests\n          digest_docker=\"${{ steps.build.outputs.digest }}\"\n          touch \"/tmp/digests/${digest_docker#sha256:}\"\n\n      - name: Upload digest\n        uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f #v6.0.0\n        with:\n          name: digests-${{ env.PLATFORM_PAIR }}\n          path: /tmp/digests/*\n          if-no-files-found: error\n          retention-days: 1\n\n  # Merge all the digests into a single file\n  # If we would push immediately above, the individual runners would overwrite each other's images\n  # https://docs.docker.com/build/ci/github-actions/multi-platform/#distribute-build-across-multiple-runners\n  merge-and-deploy:\n    runs-on: ubuntu-latest\n    needs:\n      - build\n    steps:\n      - *checkout-repo\n      - *checkout-dev\n\n      - name: Download digests\n        uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 #v7.0.0\n        with:\n          path: /tmp/digests\n          pattern: digests-*\n          merge-multiple: true\n\n      - *setup-buildx\n      - *docker-meta\n      - *login-dockerhub\n      - *login-ghcr\n\n      - name: Create manifest list and push (DockerHub and GitHub Container Registry)\n        working-directory: /tmp/digests\n        run: |\n          docker buildx imagetools create $(jq -cr '.tags | map(\"-t \" + .) | join(\" \")' <<< \"$DOCKER_METADATA_OUTPUT_JSON\") \\\n            $(printf '${{ env.dockerhub }}@sha256:%s ' *)\n          docker buildx imagetools create $(jq -cr '.tags | map(\"-t \" + .) | join(\" \")' <<< \"$DOCKER_METADATA_OUTPUT_JSON\") \\\n            $(printf '${{ env.ghcr }}@sha256:%s ' *)\n\n      - name: Inspect images\n        run: |\n          docker buildx imagetools inspect ${{ env.dockerhub }}:${{ steps.meta.outputs.version }}\n          docker buildx imagetools inspect ${{ env.ghcr }}:${{ steps.meta.outputs.version }}\n"
  },
  {
    "path": ".github/workflows/build-and-test.yml",
    "content": "name: Build Image and Test\non:\n  pull_request:\n\npermissions:\n  contents: read\n\njobs:\n  build-and-test:\n    runs-on: ${{ matrix.runner }}\n    strategy:\n      fail-fast: false\n      matrix:\n        include:\n          - platform: linux/amd64\n            runner: ubuntu-latest\n          - platform: linux/386\n            runner: ubuntu-latest\n          - platform: linux/arm/v6\n            runner: ubuntu-24.04-arm\n          - platform: linux/arm/v7\n            runner: ubuntu-24.04-arm\n          - platform: linux/arm64\n            runner: ubuntu-24.04-arm\n          - platform: linux/riscv64\n            runner: ubuntu-24.04-arm\n    env:\n      CI_ARCH: ${{ matrix.platform }}\n    steps:\n    - name: Checkout Repo\n      uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2\n\n    - name: Set up QEMU\n      uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 #v3.7.0\n\n    - name: Set up Python\n      uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 #v6.2.0\n      with:\n        python-version: \"3.13\"\n\n    - name: Run black formatter\n      run: |\n        pip install black\n        black --check --diff test/tests/\n\n    - name: Install wheel\n      run:  pip install wheel\n\n    - name: Install dependencies\n      run: pip install -r test/requirements.txt\n\n    - name: Test with tox\n      run: |\n        CIPLATFORM=${{ env.CI_ARCH }} tox -c test/tox.ini\n"
  },
  {
    "path": ".github/workflows/codespell.yml",
    "content": "name: Codespell\non:\n  pull_request:\n    types: [opened, synchronize, reopened, ready_for_review]\n\npermissions:\n  contents: read\n\njobs:\n  spell-check:\n    if: github.event.pull_request.draft == false\n    runs-on: ubuntu-latest\n    steps:\n    -\n      name: Checkout repository\n      uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2\n    -\n      name: Spell-Checking\n      uses: codespell-project/actions-codespell@8f01853be192eb0f849a5c7d721450e7a467c579 #v2.2\n      with:\n        ignore_words_file: .codespellignore\n"
  },
  {
    "path": ".github/workflows/dockerhub-description.yml",
    "content": "name: Update Docker Hub Description\npermissions:\n  contents: read\non:\n  push:\n    branches:\n      - master\n    paths:\n      - README.md\n      - .github/workflows/dockerhub-description.yml\njobs:\n  dockerHubDescription:\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2\n\n    - name: Docker Hub Description\n      uses: peter-evans/dockerhub-description@1b9a80c056b620d92cedb9d9b5a223409c68ddfa #v5\n      with:\n        username: ${{ secrets.DOCKERHUB_USER }}\n        password: ${{ secrets.DOCKERHUB_PASS }}\n        repository: pihole/pihole\n        short-description: ${{ github.event.repository.description }}\n"
  },
  {
    "path": ".github/workflows/editorconfig.yml",
    "content": "name: Editorconfig-Checker\non:\n  pull_request:\n    types: [opened, synchronize, reopened, ready_for_review]\n\npermissions:\n  contents: read\n\njobs:\n  editorconfig-checker:\n    if: github.event.pull_request.draft == false\n    runs-on: ubuntu-latest\n    steps:\n    - name: Checkout repository\n      uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2\n\n    - name: Get editorconfig-checker\n      uses: editorconfig-checker/action-editorconfig-checker@4b6cd6190d435e7e084fb35e36a096e98506f7b9 #v2.1.0\n\n    - name: Run editorconfig-checker\n      run: editorconfig-checker\n"
  },
  {
    "path": ".github/workflows/housekeeping.yml",
    "content": "name: Remove untagged images from registry\non:\n    workflow_dispatch:\n    schedule:\n        - cron: \"0 0 * * *\"\n\npermissions:\n  packages: write\n\njobs:\n    housekeeping:\n        runs-on: ubuntu-latest\n        steps:\n            -\n                name: Delete all containers from repository without tags\n                uses: Chizkiyahu/delete-untagged-ghcr-action@68758dd8caf1d9dbaed1fe9cc1a1f8fcea1c4cf0 #v6.1.0\n                with:\n                    token: ${{ secrets.PAT_TOKEN }}\n                    repository_owner: ${{ github.repository_owner }}\n                    repository: ${{ github.repository }}\n                    untagged_only: true\n                    owner_type: org # or user\n                    except_untagged_multiplatform: true\n"
  },
  {
    "path": ".github/workflows/merge-conflict.yml",
    "content": "name: \"Check for merge conflicts\"\non:\n  # So that PRs touching the same files as the push are updated\n  push:\n  # So that the `dirtyLabel` is removed if conflicts are resolve\n  # We recommend `pull_request_target` so that github secrets are available.\n  # In `pull_request` we wouldn't be able to change labels of fork PRs\n  pull_request_target:\n    types: [synchronize]\n\npermissions:\n  contents: read\n  pull-requests: write\n\njobs:\n  main:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Check if PRs are have merge conflicts\n        uses: eps1lon/actions-label-merge-conflict@1df065ebe6e3310545d4f4c4e862e43bdca146f0 #v3.0.3\n        with:\n          dirtyLabel: \"Merge Conflict\"\n          repoToken: \"${{ secrets.GITHUB_TOKEN }}\"\n          commentOnDirty: \"This pull request has conflicts, please resolve those before we can evaluate the pull request.\"\n          commentOnClean: \"Conflicts have been resolved.\"\n"
  },
  {
    "path": ".github/workflows/stale.yml",
    "content": "name: Mark stale issues\n\non:\n  schedule:\n    - cron: '0 8 * * *'\n  workflow_dispatch:\n  issue_comment:\n\npermissions:\n  issues: write\n  pull-requests: write\n\nenv:\n  stale_label: stale\n\njobs:\n  stale_action:\n    if: github.event_name != 'issue_comment'\n    runs-on: ubuntu-latest\n    permissions:\n      issues: write\n\n    steps:\n    - uses: actions/stale@997185467fa4f803885201cee163a9f38240193d #v10.1.1\n      with:\n        repo-token: ${{ secrets.GITHUB_TOKEN }}\n        days-before-stale: 30\n        days-before-close: 5\n        stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Please comment or update this issue or it will be closed in 5 days.'\n        stale-issue-label: '${{ env.stale_label }}'\n        exempt-issue-labels: 'pinned, Fixed in next release, bug, never-stale, documentation, investigating, v6'\n        exempt-all-issue-assignees: true\n        operations-per-run: 300\n        close-issue-reason: 'not_planned'\n\n  remove_stale:\n    # trigger \"stale\" removal immediately when stale issues are commented on\n    # we need to explicitly check that the trigger does not run on comment on a PR as\n    # 'issue_comment' triggers on issues AND PR comments\n    # https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#issue_comment-on-issues-only-or-pull-requests-only\n    if: ${{ !github.event.issue.pull_request && github.event_name != 'schedule' }}\n    permissions:\n      contents: read #  for actions/checkout\n      issues: write #  to edit issues label\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2\n      - name: Remove 'stale' label\n        run: gh issue edit ${{ github.event.issue.number }} --remove-label ${{ env.stale_label }}\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n"
  },
  {
    "path": ".github/workflows/stale_pr.yml",
    "content": "name: Close stale PR\n# This action will add a `stale` label and close immediately every PR that meets the following conditions:\n#   - it is already marked with \"merge conflict\" label\n#   - there was no update/comment on the PR in the last 30 days.\n\non:\n  schedule:\n    - cron: '0 10 * * *'\n  workflow_dispatch:\n\njobs:\n  stale:\n\n    runs-on: ubuntu-latest\n    permissions:\n      issues: write\n      pull-requests: write\n\n    steps:\n      - uses: actions/stale@997185467fa4f803885201cee163a9f38240193d #v10.1.1\n        with:\n          repo-token: ${{ secrets.GITHUB_TOKEN }}\n          # Do not automatically mark PR/issue as stale\n          days-before-stale: -1\n          # Override 'days-before-stale' for PR only\n          days-before-pr-stale: 30\n          # Close PRs immediately, after marking them 'stale'\n          days-before-pr-close: 0\n          # only run the action on merge conflict PR\n          any-of-labels: 'Merge Conflict'\n          exempt-pr-labels: 'internal,never-stale,ON HOLD,in progress'\n          exempt-all-pr-assignees: true\n          operations-per-run: 300\n          stale-pr-message: ''\n          close-pr-message: 'Existing merge conflicts have not been addressed. This PR is considered abandoned.'\n"
  },
  {
    "path": ".github/workflows/sync-back-to-dev.yml",
    "content": "name: Sync Back to Development\n\non:\n  push:\n    branches:\n      - master\n\npermissions:\n  contents: write\n  pull-requests: write\n\njobs:\n  sync-branches:\n    runs-on: ubuntu-latest\n    name: Syncing branches\n    steps:\n      - name: Checkout\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2\n      - name: Opening pull request\n        run: gh pr create -B development -H master --title 'Sync master back into development' --body 'Created by Github action' --label 'internal'\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n"
  },
  {
    "path": ".gitignore",
    "content": "*.sw*\n*.pyc\n.cache\n__pycache__\n.tox\n.pipenv\n.eggs\nUNKNOWN.egg-info\n.env\nci-workspace\n.gh-workspace\ndocker-compose.yml\netc-dnsmasq.d/\netc-pihole/\nvar-log/\n.vscode/\n.pytest_cache/\n\n# WIP/test stuff\ndoco.yml\n\n# Ignore FTL Binary if it exists\nsrc/pihole-FTL\n"
  },
  {
    "path": ".gitmodules",
    "content": ""
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Docker Pi-Hole changelog\n\nNotes about releases will be documented on [docker-pi-hole's github releases page](https://github.com/pi-hole/docker-pi-hole/releases).  Breaking changes will be copied to the top of the docker repo's README file to assist with common upgrade issues.\n\nSee the [Pi-hole releases](https://github.com/pi-hole/pi-hole/releases) for details on updates unrelated to docker image releases\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Pull Request Guidelines\n\nPlease review the following before opening a pull request (PR) to help your PR go smoothly:\n\n* Code changes go to the `development` branch first\n  * To ensure proper testing and quality control, target any code change pull requests against `development` branch.\n\n* Make sure the tests pass\n  * Take a look at [TESTING.md](test/TESTING.md) to see how to run tests locally so you do not have to push all your code to a PR and have GitHub Actions run it.\n  * Your tests will probably run faster locally and you get a faster feedback loop.\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (C) 2017 Pi-hole, LLC (https://pi-hole.net)\nPi-hole Core\n\nThis software is licensed under the European Union Public License (EUPL)\nThe license is available in the 22 official languages of the EU. The English version is included here.\nPlease see https://joinup.ec.europa.eu/community/eupl/og_page/eupl for official translations of the other languages.\n\nThis license applies to the whole project EXCEPT:\n\n - any commits made to the master branch prior to the release of version 3.0\n\nThe licenses that existed prior to this change have remained intact.\n\n-------------------------------------------------------------\nEUROPEAN UNION PUBLIC LICENCE v. 1.2\n\nEUPL © the European Union 2007, 2016\n\nThis European Union Public Licence (the EUPL) applies to the Work (as defined below) which is provided under the terms of this Licence. Any use of the Work, other than as authorised under this Licence is prohibited (to the extent such use is covered by a right of the copyright holder of the Work).\nThe Work is provided under the terms of this Licence when the Licensor (as defined below) has placed the following notice immediately following the copyright notice for the Work:\nLicensed under the EUPL\nor has expressed by any other means his willingness to license under the EUPL.\n\n1. Definitions\n\nIn this Licence, the following terms have the following meaning:\n\n- The Licence: this Licence.\n- The Original Work: the work or software distributed or communicated by the Licensor under this Licence, available as Source Code and also as Executable Code as the case may be.\n- Derivative Works: the works or software that could be created by the Licensee, based upon the Original Work or modifications thereof. This Licence does not define the extent of modification or dependence on the Original Work required in order to classify a work as a Derivative Work; this extent is determined by copyright law applicable in the country mentioned in Article 15.\n- The Work: the Original Work or its Derivative Works.\n- The Source Code: the human-readable form of the Work which is the most convenient for people to study and modify.\n- The Executable Code: any code which has generally been compiled and which is meant to be interpreted by a computer as a program.\n- The Licensor: the natural or legal person that distributes or communicates the Work under the Licence.\n- Contributor(s): any natural or legal person who modifies the Work under the Licence, or otherwise contributes to the creation of a Derivative Work.\n- The Licensee or You: any natural or legal person who makes any usage of the Work under the terms of the Licence.\n- Distribution or Communication: any act of selling, giving, lending, renting, distributing, communicating, transmitting, or otherwise making available, online or offline, copies of the Work or providing access to its essential functionalities at the disposal of any other natural or legal person.\n\n2. Scope of the rights granted by the Licence\n\nThe Licensor hereby grants You a worldwide, royalty-free, non-exclusive, sublicensable licence to do the following, for the duration of copyright vested in the Original Work:\n- use the Work in any circumstance and for all usage,\n- reproduce the Work,\n- modify the Work, and make Derivative Works based upon the Work,\n- communicate to the public, including the right to make available or display the Work or copies thereof to the public and perform publicly, as the case may be, the Work,\n- distribute the Work or copies thereof,\n- lend and rent the Work or copies thereof,\n- sublicense rights in the Work or copies thereof.\nThose rights can be exercised on any media, supports and formats, whether now known or later invented, as far as the applicable law permits so.\nIn the countries where moral rights apply, the Licensor waives his right to exercise his moral right to the extent allowed by law in order to make effective the licence of the economic rights here above listed.\nThe Licensor grants to the Licensee royalty-free, non-exclusive usage rights to any patents held by the Licensor, to the extent necessary to make use of the rights granted on the Work under this Licence.\n\n3. Communication of the Source Code\n\nThe Licensor may provide the Work either in its Source Code form, or as Executable Code. If the Work is provided as Executable Code, the Licensor provides in addition a machine-readable copy of the Source Code of the Work along with each copy of the Work that the Licensor distributes or indicates, in a notice following the copyright notice attached to the Work, a repository where the Source Code is easily and freely accessible for as long as the Licensor continues to distribute or communicate the Work.\n\n4. Limitations on copyright\n\nNothing in this Licence is intended to deprive the Licensee of the benefits from any exception or limitation to the exclusive rights of the rights owners in the Work, of the exhaustion of those rights or of other applicable limitations thereto.\n\n5. Obligations of the Licensee\n\nThe grant of the rights mentioned above is subject to some restrictions and obligations imposed on the Licensee. Those obligations are the following:\n\nAttribution right: The Licensee shall keep intact all copyright, patent or trademarks notices and all notices that refer to the Licence and to the disclaimer of warranties. The Licensee must include a copy of such notices and a copy of the Licence with every copy of the Work he/she distributes or communicates. The Licensee must cause any Derivative Work to carry prominent notices stating that the Work has been modified and the date of modification.\n\nCopyleft clause: If the Licensee distributes or communicates copies of the Original Works or Derivative Works, this Distribution or Communication will be done under the terms of this Licence or of a later version of this Licence unless the Original Work is expressly distributed only under this version of the Licence - for example by communicating EUPL v. 1.2 only. The Licensee (becoming Licensor) cannot offer or impose any additional terms or conditions on the Work or Derivative Work that alter or restrict the terms of the Licence.\n\nCompatibility clause: If the Licensee Distributes or Communicates Derivative Works or copies thereof based upon both the Work and another work licensed under a Compatible Licence, this Distribution or Communication can be done under the terms of this Compatible Licence. For the sake of this clause, Compatible Licence refers to the licences listed in the appendix attached to this Licence. Should the Licensee's obligations under the Compatible Licence conflict with his/her obligations under this Licence, the obligations of the Compatible Licence shall prevail.\n\nProvision of Source Code: When distributing or communicating copies of the Work, the Licensee will provide a machine-readable copy of the Source Code or indicate a repository where this Source will be easily and freely available for as long as the Licensee continues to distribute or communicate the Work.\n\nLegal Protection: This Licence does not grant permission to use the trade names, trademarks, service marks, or names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the copyright notice.\n\n6. Chain of Authorship\n\nThe original Licensor warrants that the copyright in the Original Work granted hereunder is owned by him/her or licensed to him/her and that he/she has the power and authority to grant the Licence.\n\nEach Contributor warrants that the copyright in the modifications he/she brings to the Work are owned by him/her or licensed to him/her and that he/she has the power and authority to grant the Licence.\n\nEach time You accept the Licence, the original Licensor and subsequent Contributors grant You a licence to their contributions to the Work, under the terms of this Licence.\n\n7. Disclaimer of Warranty\n\nThe Work is a work in progress, which is continuously improved by numerous Contributors. It is not a finished work and may therefore contain defects or bugs inherent to this type of development.\nFor the above reason, the Work is provided under the Licence on an as is basis and without warranties of any kind concerning the Work, including without limitation merchantability, fitness for a particular purpose, absence of defects or errors, accuracy, non-infringement of intellectual property rights other than copyright as stated in Article 6 of this Licence.\nThis disclaimer of warranty is an essential part of the Licence and a condition for the grant of any rights to the Work.\n\n8. Disclaimer of Liability\n\nExcept in the cases of wilful misconduct or damages directly caused to natural persons, the Licensor will in no event be liable for any direct or indirect, material or moral, damages of any kind, arising out of the Licence or of the use of the Work, including without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, loss of data or any commercial damage, even if the Licensor has been advised of the possibility of such damage. However, the Licensor will be liable under statutory product liability laws as far such laws apply to the Work.\n\n9. Additional agreements\n\nWhile distributing the Work, You may choose to conclude an additional agreement, defining obligations or services consistent with this Licence. However, if accepting obligations, You may act only on your own behalf and on your sole responsibility, not on behalf of the original Licensor or any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against such Contributor by the fact You have accepted any warranty or additional liability.\n\n10. Acceptance of the Licence\n\nThe provisions of this Licence can be accepted by clicking on an icon I agree placed under the bottom of a window displaying the text of this Licence or by affirming consent in any other similar way, in accordance with the rules of applicable law. Clicking on that icon indicates your clear and irrevocable acceptance of this Licence and all of its terms and conditions.\nSimilarly, you irrevocably accept this Licence and all of its terms and conditions by exercising any rights granted to You by Article 2 of this Licence, such as the use of the Work, the creation by You of a Derivative Work or the Distribution or Communication by You of the Work or copies thereof.\n\n11. Information to the public\n\nIn case of any Distribution or Communication of the Work by means of electronic communication by You (for example, by offering to download the Work from a remote location) the distribution channel or media (for example, a website) must at least provide to the public the information requested by the applicable law regarding the Licensor, the Licence and the way it may be accessible, concluded, stored and reproduced by the Licensee.\n\n12. Termination of the Licence\n\nThe Licence and the rights granted hereunder will terminate automatically upon any breach by the Licensee of the terms of the Licence.\nSuch a termination will not terminate the licences of any person who has received the Work from the Licensee under the Licence, provided such persons remain in full compliance with the Licence.\n\n13. Miscellaneous\n\nWithout prejudice of Article 9 above, the Licence represents the complete agreement between the Parties as to the Work.\nIf any provision of the Licence is invalid or unenforceable under applicable law, this will not affect the validity or enforceability of the Licence as a whole. Such provision will be construed or reformed so as necessary to make it valid and enforceable.\nThe European Commission may publish other linguistic versions or new versions of this Licence or updated versions of the Appendix, so far this is required and reasonable, without reducing the scope of the rights granted by the Licence. New versions of the Licence will be published with a unique version number.\nAll linguistic versions of this Licence, approved by the European Commission, have identical value. Parties can take advantage of the linguistic version of their choice.\n\n14. Jurisdiction\n\nWithout prejudice to specific agreement between parties,\n- any litigation resulting from the interpretation of this License, arising between the European Union institutions, bodies, offices or agencies, as a Licensor, and any Licensee, will be subject to the jurisdiction of the Court of Justice of the European Union, as laid down in article 272 of the Treaty on the Functioning of the European Union,\n- any litigation arising between other parties and resulting from the interpretation of this License, will be subject to the exclusive jurisdiction of the competent court where the Licensor resides or conducts its primary business.\n\n15. Applicable Law\n\nWithout prejudice to specific agreement between parties,\n- this Licence shall be governed by the law of the European Union Member State where the Licensor has his seat, resides or has his registered office,\n- this licence shall be governed by Belgian law if the Licensor has no seat, residence or registered office inside a European Union Member State.\n\n===\n\nAppendix\n\nCompatible Licences according to Article 5 EUPL are:\n- GNU General Public License (GPL) v. 2, v. 3\n- GNU Affero General Public License (AGPL) v. 3\n- Open Software License (OSL) v. 2.1, v. 3.0\n- Eclipse Public License (EPL) v. 1.0\n- CeCILL v. 2.0, v. 2.1\n- Mozilla Public Licence (MPL) v. 2\n- GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3\n- Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for works other than software\n- European Union Public Licence (EUPL) v. 1.1, v. 1.2\n- Québec Free and Open-Source Licence - Reciprocity (LiLiQ-R) or Strong Reciprocity (LiLiQ-R+)\n- The European Commission may update this Appendix to later versions of the above licences without producing a new version of the EUPL, as long as they provide the rights granted in Article 2 of this Licence and protect the covered Source Code from exclusive appropriation.\n-  All other changes or additions to this Appendix require the production of a new EUPL version.\n"
  },
  {
    "path": "README.md",
    "content": "# Docker Pi-hole\n\n[![Build Status](https://github.com/pi-hole/docker-pi-hole/workflows/Build%20Image%20and%20Test/badge.svg)](https://github.com/pi-hole/docker-pi-hole/actions?query=workflow%3A%22Build+Image+and+Test%22) [![Docker Stars](https://img.shields.io/docker/stars/pihole/pihole.svg?maxAge=604800)](https://store.docker.com/community/images/pihole/pihole) [![Docker Pulls](https://img.shields.io/docker/pulls/pihole/pihole.svg?maxAge=604800)](https://store.docker.com/community/images/pihole/pihole)\n\n<div align=\"center\">\n  <a href=\"https://pi-hole.net/\">\n    <img src=\"https://pi-hole.github.io/graphics/Vortex/vortex_with_text.svg\" width=\"144\" height=\"256\" alt=\"Pi-hole website\">\n  </a>\n  <br>\n  <strong>Network-wide ad blocking via your own Linux hardware</strong>\n  <br>\n  <br>\n  <div align=\"center\">\n    <a href=\"https://pi-hole.net/\">Pi-hole website</a> |\n    <a href=\"https://docs.pi-hole.net/\">Documentation</a> |\n    <a href=\"https://discourse.pi-hole.net/\">Discourse Forum</a> |\n    <a href=\"https://pi-hole.net/donate\">Donate</a>\n  </div>\n  <br>\n  <br>\n</div>\n<!-- Delete above HTML and insert markdown for dockerhub : ![Pi-hole](https://pi-hole.github.io/graphics/Vortex/Vortex_with_text.png) -->\n\n## Upgrade Notes\n\n> [!NOTE]\n> **Using Watchtower?\\\n> See the [Note on Watchtower](https://docs.pi-hole.net/docker/tips-and-tricks/#note-on-watchtower) in our documentation**.\n\n> [!TIP]\n> Some users [have reported issues](https://github.com/pi-hole/docker-pi-hole/issues/963#issuecomment-1095602502) with using the `--privileged` flag on `2022.04` and above.\\\n> TL;DR, don't use that mode, and be [explicit with the permitted caps](https://docs.pi-hole.net/docker/#note-on-capabilities) (if needed) instead.\n\n## Quick Start\n\nUsing [Docker-compose](https://docs.docker.com/compose/install/):\n\n1. Copy the below docker compose example and update as needed:\n\n```yml\n# More info at https://github.com/pi-hole/docker-pi-hole/ and https://docs.pi-hole.net/\nservices:\n  pihole:\n    container_name: pihole\n    image: pihole/pihole:latest\n    ports:\n      # DNS Ports\n      - \"53:53/tcp\"\n      - \"53:53/udp\"\n      # Default HTTP Port\n      - \"80:80/tcp\"\n      # Default HTTPs Port. FTL will generate a self-signed certificate\n      - \"443:443/tcp\"\n      # Uncomment the line below if you are using Pi-hole as your DHCP server\n      #- \"67:67/udp\"\n      # Uncomment the line below if you are using Pi-hole as your NTP server\n      #- \"123:123/udp\"\n    environment:\n      # Set the appropriate timezone for your location (https://en.wikipedia.org/wiki/List_of_tz_database_time_zones), e.g:\n      TZ: 'Europe/London'\n      # Set a password to access the web interface. Not setting one will result in a random password being assigned\n      FTLCONF_webserver_api_password: 'correct horse battery staple'\n      # If using Docker's default `bridge` network setting the dns listening mode should be set to 'ALL'\n      FTLCONF_dns_listeningMode: 'ALL'\n    # Volumes store your data between container upgrades\n    volumes:\n      # For persisting Pi-hole's databases and common configuration file\n      - './etc-pihole:/etc/pihole'\n      # Uncomment the below if you have custom dnsmasq config files that you want to persist. Not needed for most starting fresh with Pi-hole v6. If you're upgrading from v5 you and have used this directory before, you should keep it enabled for the first v6 container start to allow for a complete migration. It can be removed afterwards. Needs environment variable FTLCONF_misc_etc_dnsmasq_d: 'true'\n      #- './etc-dnsmasq.d:/etc/dnsmasq.d'\n    cap_add:\n      # See https://docs.pi-hole.net/docker/#note-on-capabilities\n      # Required if you are using Pi-hole as your DHCP server, else not needed\n      - NET_ADMIN\n      # Required if you are using Pi-hole as your NTP client to be able to set the host's system time\n      - SYS_TIME\n      # Optional, if Pi-hole should get some more processing time\n      - SYS_NICE\n    restart: unless-stopped\n```\n\n2. Run `docker compose up -d` to build and start pi-hole (Syntax may be `docker-compose` on older systems).\n\n> [!NOTE]\n> Volumes are recommended for persisting data across container re-creations for updating images.\n\n### Automatic Ad List Updates\n\n`cron` is baked into the container and will grab the newest versions of your lists and flush your logs. This happens once per week in the small hours of Sunday morning.\n\n## Documentation\n\nFor more detailed information, please refer to our documentation:\n\n- [Running DHCP from Docker Pi-Hole](https://docs.pi-hole.net/docker/DHCP/)\n- [Configuration](https://docs.pi-hole.net/docker/configuration/)\n- [Tips and Tricks](https://docs.pi-hole.net/docker/tips-and-tricks/)\n- [Docker tags and versioning](https://docs.pi-hole.net/docker/#docker-tags-and-versioning)\n- [Upgrading, Persistence, and Customizations](https://docs.pi-hole.net/docker/upgrading/)\n\n## Docker tags and versioning\n\nThe primary docker tags are explained in the following table.  [Click here to see the full list of tags](https://hub.docker.com/r/pihole/pihole/tags). See [GitHub Release notes](https://github.com/pi-hole/docker-pi-hole/releases) to see the specific version of Pi-hole Core, Web, and FTL included in the release.\n\nThe Date-based (including incremented \"Patch\" versions) do not relate to any kind of semantic version number, rather a date is used to differentiate between the new version and the old version, nothing more.\n\nRelease notes will always contain full details of changes in the container, including changes to core Pi-hole components.\n\n| tag | description |\n| :--- | :--- |\n| `latest` | Always the latest release |\n| `2022.04.0` | Date-based release |\n| `nightly` | Built and pushed whenever there are changes on the `development` branch and additionally produced by the scheduled nightly job. These are the most experimental development images and may change frequently |\n\n## User Feedback\n\nPlease report issues on the [GitHub project](https://github.com/pi-hole/docker-pi-hole) when you suspect something docker related.  Pi-hole or general docker questions are best answered on our [user forums](https://discourse.pi-hole.net/c/bugs-problems-issues/docker/30)\n"
  },
  {
    "path": "build.sh",
    "content": "#!/bin/bash\n\n# Usage function\nusage() {\n    echo \"Usage: $0 [-l] [-f <ftl_branch>] [-c <core_branch>] [-w <web_branch>] [-t <tag>] [use_cache]\"\n    echo \"Options:\"\n    echo \"  -f,  --ftlbranch <branch>     Specify FTL branch (cannot be used in conjunction with -l)\"\n    echo \"  -c,  --corebranch <branch>    Specify Core branch\"\n    echo \"  -w,  --webbranch <branch>     Specify Web branch\"\n    echo \"  -p,  --paddbranch <branch>    Specify PADD branch\"\n    echo \"  -t,  --tag <tag>              Specify Docker image tag (default: pihole:local)\"\n    echo \"  -l,  --local                  Use locally built FTL binary (requires src/pihole-FTL file)\"\n    echo \"  use_cache                     Enable caching (by default --no-cache is used)\"\n    echo \"\"\n    echo \"If no options are specified, the following command will be executed:\"\n    echo \"  docker buildx build src/. --tag pihole:local --load --no-cache\"\n    exit 1\n}\n\n# Set default values\nTAG=\"pihole:local\"\nDOCKER_BUILD_CMD=\"docker buildx build src/. --tag ${TAG} --load --no-cache\"\nFTL_FLAG=false\nCORE_FORK=\"pi-hole\"\nWEB_FORK=\"pi-hole\"\nPADD_FORK=\"pi-hole\"\n\n# Check if buildx is installed\ndocker buildx version >/dev/null 2>&1\nif [ $? -ne 0 ]; then\n    echo \"Error: Docker buildx is required to build this image. For installation instructions, see:\"\n    echo \"       https://github.com/docker/buildx#installing\"\n    exit 1\nfi\n\n# Function to check if a custom branch entered by the user is valid\ncheck_branch_exists() {\n    local repo=$1\n    local branch=$2\n    local fork=$3\n    local url\n\n    if [ \"$repo\" == \"ftl\" ]; then\n        # Special case for FTL - we check for the binary instead of just the branch - in case it is not yet built.\n        url=\"https://ftl.pi-hole.net/${branch}/pihole-FTL-amd64\"\n    else\n        url=\"https://github.com/${fork}/${repo}/blob/${branch}/README.md\"\n    fi\n\n    local http_code\n    http_code=$(curl -sI \"$url\" -o /dev/null -w \"%{http_code}\")\n    if [ \"${http_code}\" -ne 200 ]; then\n        echo \"Error: $repo branch '$branch' not found. Exiting.\"\n        exit 1\n    fi\n}\n\n# Parse command line arguments\nwhile [[ $# -gt 0 ]]; do\n    key=\"$1\"\n\n    case $key in\n    -l | --local)\n        if [ ! -f \"src/pihole-FTL\" ]; then\n            echo \"File 'src/pihole-FTL' not found. Exiting.\"\n            exit 1\n        fi\n        if [ \"$FTL_FLAG\" = true ]; then\n            echo \"Error: Both -l and -f cannot be used together.\"\n            usage\n        fi\n        FTL_FLAG=true\n        DOCKER_BUILD_CMD+=\" --build-arg FTL_SOURCE=local\"\n        shift\n        ;;\n    -f | --ftlbranch)\n        if [ \"$FTL_FLAG\" = true ]; then\n            echo \"Error: Both -l and -f cannot be used together.\"\n            usage\n        fi\n        FTL_FLAG=true\n        FTL_BRANCH=\"$2\"\n        check_branch_exists \"ftl\" \"$FTL_BRANCH\"\n        DOCKER_BUILD_CMD+=\" --build-arg FTL_BRANCH=$FTL_BRANCH\"\n        shift\n        shift\n        ;;\n    -c | --corebranch)\n        CORE_BRANCH=\"$2\"\n        check_branch_exists \"pi-hole\" \"$CORE_BRANCH\" \"$CORE_FORK\"\n        DOCKER_BUILD_CMD+=\" --build-arg CORE_BRANCH=$CORE_BRANCH\"\n        shift\n        shift\n        ;;\n    -w | --webbranch)\n        WEB_BRANCH=\"$2\"\n        check_branch_exists \"web\" \"$WEB_BRANCH\" \"$WEB_FORK\"\n        DOCKER_BUILD_CMD+=\" --build-arg WEB_BRANCH=$WEB_BRANCH\"\n        shift\n        shift\n        ;;\n    -p | --paddbranch)\n        PADD_BRANCH=\"$2\"\n        check_branch_exists \"padd\" \"$PADD_BRANCH\"\n        DOCKER_BUILD_CMD+=\" --build-arg PADD_BRANCH=$PADD_BRANCH\"\n        shift\n        shift\n        ;;\n    -cf | --corefork)\n        CORE_FORK=\"$2\"\n        DOCKER_BUILD_CMD+=\" --build-arg CORE_FORK=$CORE_FORK\"\n        shift\n        shift\n        ;;\n    -wf | --webfork)\n        WEB_FORK=\"$2\"\n        DOCKER_BUILD_CMD+=\" --build-arg WEB_FORK=$WEB_FORK\"\n        shift\n        shift\n        ;;\n    -pf | --paddfork)\n        PADD_FORK=\"$2\"\n        DOCKER_BUILD_CMD+=\" --build-arg PADD_FORK=$PADD_FORK\"\n        shift\n        shift\n        ;;\n    -t | --tag)\n        CUSTOM_TAG=\"$2\"\n        DOCKER_BUILD_CMD=${DOCKER_BUILD_CMD/$TAG/$CUSTOM_TAG}\n        TAG=$CUSTOM_TAG\n        shift\n        shift\n        ;;\n    use_cache)\n        DOCKER_BUILD_CMD=${DOCKER_BUILD_CMD/--no-cache/}\n        shift\n        ;;\n    *)\n        echo \"Unknown option: $1\"\n        usage\n        ;;\n    esac\ndone\n\n# Execute the docker build command\necho \"Executing command: $DOCKER_BUILD_CMD\"\neval \"${DOCKER_BUILD_CMD}\"\n\n# Check exit code of previous command\nif [ $? -ne 0 ]; then\n    echo \"\"\n    echo \"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\"\n    echo \"!! ERROR: Docker build failed, please review logs above !!\"\n    echo \"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\"\n    exit 1\nelse\n    echo \"\"\n    echo \"Successfully built Docker image with tag '$TAG'\"\n    docker images \"${TAG}\"\nfi\n"
  },
  {
    "path": "examples/Caddyfile",
    "content": "pihole-dev.lab {\n  tls internal\n  redir / /admin\n  reverse_proxy pihole:8081\n}\n"
  },
  {
    "path": "examples/docker-compose-caddy-proxy.yml",
    "content": "services:\n\n  # Caddy example derived from Caddy's own example at https://hub.docker.com/_/caddy\n  caddy:\n    container_name: caddy\n    image: caddy:latest\n    networks:\n      - caddy-net # Network exclusively for Caddy-proxied containers\n    restart: unless-stopped\n    ports:\n      - \"80:80\"\n      - \"443:443\"\n      - \"443:443/udp\" # QUIC protocol support: https://www.chromium.org/quic/\n    volumes:\n      - ./Caddyfile:/etc/caddy/Caddyfile # config file on host in same directory as docker-compose.yml for easy editing.\n      #- $PWD/site:/srv  # Only use if you are serving a website behind caddy\n      - caddy_data:/data # Use docker volumes here bc no need to access these files from host\n      - caddy_config:/config # Use docker volumes here bc no need to access these files from host\n\n  # More info at https://github.com/pi-hole/docker-pi-hole/ and https://docs.pi-hole.net/\n  pihole:\n    depends_on:\n      - caddy\n    container_name: pihole\n    image: pihole/pihole:latest\n    ports:\n      - \"8081:80/tcp\" # Pi-hole web admin interface, proxied through Caddy (configure port in Caddyfile)\n      # Following are NOT proxied through Caddy, bound to host net instead:\n      - \"53:53/udp\"\n      - \"53:53/tcp\"\n      #- \"67:67/udp\" # DHCP, if desired. If not bound to host net you need an mDNS proxy service configured somewhere on host net.\n        # ref: https://docs.pi-hole.net/docker/DHCP/\n    environment:\n      # Set the appropriate timezone for your location (https://en.wikipedia.org/wiki/List_of_tz_database_time_zones), e.g:\n      TZ: 'Europe/London'\n      # Set a password to access the web interface. Not setting one will result in a random password being assigned\n      FTLCONF_webserver_api_password: 'correct horse battery staple'\n    # Volumes store your data between container upgrades\n    volumes:\n      # For persisting Pi-hole's databases and common configuration file\n      - './etc-pihole:/etc/pihole'\n      # Uncomment the below if you have custom dnsmasq config files that you want to persist. Not needed for most.\n      #- './etc-dnsmasq.d:/etc/dnsmasq.d'\n    cap_add:\n      # See https://github.com/pi-hole/docker-pi-hole#note-on-capabilities\n      # Required if you are using Pi-hole as your DHCP server, else not needed\n      - NET_ADMIN\n    restart: unless-stopped\n\n# ref: https://hub.docker.com/_/caddy\nnetworks:\n  caddy-net:\n    driver: bridge\n    name: caddy-net\n\n# ref: https://hub.docker.com/_/caddy\nvolumes:\n  caddy_data:\n    external: true # May need to create volume with 'docker volume create caddy_data'\n  caddy_config:\n"
  },
  {
    "path": "src/.dockerignore",
    "content": "**/*.sw*\n.tox\n.git\n**/__pycache__\n.pipenv\n"
  },
  {
    "path": "src/Dockerfile",
    "content": "# syntax=docker/dockerfile:1\nARG FTL_SOURCE=remote\n# Pull Stable images\nFROM alpine:3.23.3@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659 AS base\n\nARG TARGETPLATFORM\nARG WEB_BRANCH=\"development\"\nARG CORE_BRANCH=\"development\"\nARG FTL_BRANCH=\"development\"\nARG PIHOLE_DOCKER_TAG=\"dev-localbuild\"\nARG PADD_BRANCH=\"development\"\n\nARG CORE_FORK=\"pi-hole\"\nARG WEB_FORK=\"pi-hole\"\nARG PADD_FORK=\"pi-hole\"\n\nARG PIHOLE_UID=1000\nARG PIHOLE_GID=1000\n\nENV DNSMASQ_USER=pihole\nENV FTL_CMD=no-daemon\n\nRUN apk add --no-cache \\\n    bash \\\n    bash-completion \\\n    bind-tools \\\n    binutils \\\n    coreutils \\\n    curl \\\n    git \\\n    # Install grep to avoid issues in pihole -w/b with the default busybox grep\n    grep \\\n    iproute2 \\\n    jq \\\n    libcap \\\n    logrotate \\\n    ncurses \\\n    procps-ng \\\n    psmisc \\\n    shadow \\\n    sudo \\\n    tzdata \\\n    unzip \\\n    wget\n\n# For nightly images, we install gdb and screen for ease of debugging (this is\n# not included in the default image to keep it small), and also prepare the\n# system for a core dump. Furthermore, we already add the required signal\n# instructions to the gdb config file\nRUN if [ \"${PIHOLE_DOCKER_TAG}\" = \"nightly\" ]; then \\\n    apk add --no-cache gdb screen && \\\n    echo \"ulimit -c unlimited\" >> /etc/profile && \\\n    echo \"handle SIGHUP nostop SIGPIPE nostop SIGTERM nostop SIG32 nostop SIG33 nostop SIG34 nostop SIG35 nostop SIG36 nostop SIG37 nostop SIG38 nostop SIG39 nostop SIG40 nostop SIG41 nostop\" > /root/.gdbinit; \\\n    fi\n\nADD https://ftl.pi-hole.net/macvendor.db /macvendor.db\nCOPY crontab.txt /crontab.txt\n\n# Add PADD to the container, too.\nADD --chmod=0755 https://raw.githubusercontent.com/${PADD_FORK}/PADD/${PADD_BRANCH}/padd.sh /usr/local/bin/padd\n\n# download a the main repos from github\n# if the branch is master we clone the latest tag as sometimes the master branch contains meta changes that have not been tagged\n# (we need to create a new \"master\" branch to avoid the \"detached HEAD\" state for the version check to work correctly)\n\nRUN clone_repo() { \\\n        FORK=\"$1\"; \\\n        REPO=\"$2\"; \\\n        BRANCH=\"$3\"; \\\n        DEST=\"$4\"; \\\n        CLONE_BRANCH=\"$BRANCH\"; \\\n        if [ \"$BRANCH\" = \"master\" ]; then \\\n            CLONE_BRANCH=$(curl -s https://api.github.com/repos/${FORK}/${REPO}/releases/latest | jq -r .tag_name); \\\n        fi; \\\n        git clone --branch \"$CLONE_BRANCH\" --single-branch --depth 1 \"https://github.com/${FORK}/${REPO}.git\" \"$DEST\"; \\\n        cd \"$DEST\"; \\\n        if [ \"$BRANCH\" = \"master\" ]; then git checkout -b master; fi; \\\n    }; \\\n    clone_repo \"${WEB_FORK}\" \"web\" \"${WEB_BRANCH}\" \"/var/www/html/admin\"; \\\n    clone_repo \"${CORE_FORK}\" \"pi-hole\" \"${CORE_BRANCH}\" \"/etc/.pihole\"\n\n\nRUN cd /etc/.pihole && \\\n    install -Dm755 -d /opt/pihole && \\\n    install -Dm755 -t /opt/pihole gravity.sh && \\\n    install -Dm755 -t /opt/pihole ./advanced/Scripts/*.sh && \\\n    install -Dm755 -t /opt/pihole ./advanced/Scripts/COL_TABLE && \\\n    install -Dm755 -d /etc/pihole && \\\n    install -Dm644 -t /etc/pihole ./advanced/Templates/logrotate && \\\n    install -Dm755 -d /var/log/pihole && \\\n    install -Dm755 -d /var/lib/logrotate && \\\n    install -Dm755 -t /usr/local/bin pihole && \\\n    install -Dm644 ./advanced/bash-completion/pihole.bash /etc/bash_completion.d/pihole && \\\n    install -Dm644 ./advanced/bash-completion/pihole-ftl.bash /etc/bash_completion.d/pihole-FTL && \\\n    install -T -m 0755 ./advanced/Templates/pihole-FTL-prestart.sh /opt/pihole/pihole-FTL-prestart.sh && \\\n    install -T -m 0755 ./advanced/Templates/pihole-FTL-poststop.sh /opt/pihole/pihole-FTL-poststop.sh && \\\n    addgroup -S pihole -g ${PIHOLE_GID} && adduser -S pihole -G pihole -u ${PIHOLE_UID} && \\\n    echo \"${PIHOLE_DOCKER_TAG}\" > /pihole.docker.tag\n\nCOPY --chmod=0755 bash_functions.sh /usr/bin/bash_functions.sh\nCOPY --chmod=0755 start.sh /usr/bin/start.sh\n\nEXPOSE 53 53/udp\nEXPOSE 67/udp\nEXPOSE 80\nEXPOSE 123/udp\nEXPOSE 443\n\n## Buildkit can do some fancy stuff and we can use it to either download FTL from ftl.pi-hole.net or use a local copy\n\nFROM base AS remote-ftl-install\n# Default stage if FTL_SOURCE is not explicitly set to \"local\"\n# Download the latest version of pihole-FTL for the correct architecture\nRUN if   [ \"$TARGETPLATFORM\" = \"linux/amd64\" ];    then FTLARCH=amd64; \\\n    elif [ \"$TARGETPLATFORM\" = \"linux/386\" ];      then FTLARCH=386; \\\n    elif [ \"$TARGETPLATFORM\" = \"linux/arm/v6\" ];   then FTLARCH=armv6; \\\n    elif [ \"$TARGETPLATFORM\" = \"linux/arm/v7\" ];   then FTLARCH=armv7; \\\n    # Note for the future, \"linux/arm6/v8\" is not a valid value for TARGETPLATFORM, despite the CI platform name being that.\n    elif [ \"$TARGETPLATFORM\" = \"linux/arm64\" ];    then FTLARCH=arm64; \\\n    elif [ \"$TARGETPLATFORM\" = \"linux/riscv64\" ];  then FTLARCH=riscv64; \\\n    else FTLARCH=amd64; fi \\\n    && echo \"Arch: ${TARGETPLATFORM}, FTLARCH: ${FTLARCH}\" \\\n    && if [ \"${FTL_BRANCH}\" = \"master\" ]; then URL=\"https://github.com/pi-hole/ftl/releases/latest/download\"; else URL=\"https://ftl.pi-hole.net/${FTL_BRANCH}\"; fi \\\n    && curl -sSL \"${URL}/pihole-FTL-${FTLARCH}\" -o /usr/bin/pihole-FTL \\\n    && chmod +x /usr/bin/pihole-FTL \\\n    && readelf -h /usr/bin/pihole-FTL || (echo \"Error with downloaded FTL binary\" && exit 1) \\\n    && /usr/bin/pihole-FTL  -vv\n\nFROM base AS local-ftl-install\n# pihole-FTL must be built from source and copied to the src directory first!\nCOPY --chmod=0755 pihole-FTL /usr/bin/pihole-FTL\nRUN  readelf -h /usr/bin/pihole-FTL || (echo \"Error with local FTL binary\" && exit 1)\n\n# Use the appropriate FTL Install stage based on the FTL_SOURCE build-arg\nFROM ${FTL_SOURCE}-ftl-install AS final\n\nHEALTHCHECK CMD dig -p $(pihole-FTL --config dns.port) +short +norecurse +retry=0 @127.0.0.1 pi.hole || exit 1\n\nENTRYPOINT [\"start.sh\"]\n"
  },
  {
    "path": "src/bash_functions.sh",
    "content": "#!/bin/bash\n\n# Some of the bash_functions use utilities from Pi-hole's utils.sh\n# shellcheck disable=SC2154\n# shellcheck source=/dev/null\n# . /opt/pihole/utils.sh\n\n#######################\n# returns value from FTLs config file using pihole-FTL --config\n#\n# Takes one argument: key\n# Example getFTLConfigValue dns.piholePTR\n#######################\ngetFTLConfigValue() {\n    pihole-FTL --config -q \"${1}\"\n}\n\n#######################\n# sets value in FTLs config file using pihole-FTL --config\n#\n# Takes two arguments: key and value\n# Example setFTLConfigValue dns.piholePTR PI.HOLE\n#\n# Note, for complex values such as dns.upstreams, you should wrap the value in single quotes:\n# setFTLConfigValue dns.upstreams '[ \"8.8.8.8\" , \"8.8.4.4\" ]'\n#######################\nsetFTLConfigValue() {\n    pihole-FTL --config \"${1}\" \"${2}\" >/dev/null\n}\n\nset_uid_gid() {\n\n    echo \"  [i] Setting up user & group for the pihole user\"\n\n    currentUid=$(id -u pihole)\n\n    # If PIHOLE_UID is set, modify the pihole group's id to match\n    if [ -n \"${PIHOLE_UID}\" ]; then\n        if [[ ${currentUid} -ne ${PIHOLE_UID} ]]; then\n            echo \"  [i] Changing ID for user: pihole (${currentUid} => ${PIHOLE_UID})\"\n            usermod -o -u \"${PIHOLE_UID}\" pihole\n        else\n            echo \"  [i] ID for user pihole is already ${PIHOLE_UID}, no need to change\"\n        fi\n    else\n        echo \"  [i] PIHOLE_UID not set in environment, using default (${currentUid})\"\n    fi\n\n    currentGid=$(id -g pihole)\n\n    # If PIHOLE_GID is set, modify the pihole group's id to match\n    if [ -n \"${PIHOLE_GID}\" ]; then\n        if [[ ${currentGid} -ne ${PIHOLE_GID} ]]; then\n            echo \"  [i] Changing ID for group: pihole (${currentGid} => ${PIHOLE_GID})\"\n            groupmod -o -g \"${PIHOLE_GID}\" pihole\n        else\n            echo \"  [i] ID for group pihole is already ${PIHOLE_GID}, no need to change\"\n        fi\n    else\n        echo \"  [i] PIHOLE_GID not set in environment, using default (${currentGid})\"\n    fi\n    echo \"\"\n}\n\ninstall_additional_packages() {\n    if [ -n \"${ADDITIONAL_PACKAGES}\" ]; then\n        echo \"  [i] Additional packages requested: ${ADDITIONAL_PACKAGES}\"\n        echo \"  [i] Fetching APK repository metadata.\"\n        if ! apk update; then\n            echo \"  [i] Failed to fetch APK repository metadata.\"\n        else\n            echo \"  [i] Installing additional packages: ${ADDITIONAL_PACKAGES}.\"\n            # shellcheck disable=SC2086\n            if ! apk add --no-cache ${ADDITIONAL_PACKAGES}; then\n                echo \"  [i] Failed to install additional packages.\"\n            fi\n        fi\n        echo \"\"\n    fi\n}\n\nstart_cron() {\n    echo \"  [i] Starting crond for scheduled scripts. Randomizing times for gravity and update checker\"\n    # Randomize gravity update time\n    sed -i \"s/59 1 /$((1 + RANDOM % 58)) $((3 + RANDOM % 2))/\" /crontab.txt\n    # Randomize update checker time\n    sed -i \"s/59 17/$((1 + RANDOM % 58)) $((12 + RANDOM % 8))/\" /crontab.txt\n    /usr/bin/crontab /crontab.txt\n\n    /usr/sbin/crond\n    echo \"\"\n}\n\ninstall_logrotate() {\n    # Install the logrotate config file - this is done already in Dockerfile\n    # but if a user has mounted a volume over /etc/pihole, it will have been lost\n    # pihole-FTL-prestart.sh will set the ownership of the file to root:root\n    echo \"  [i] Ensuring logrotate script exists in /etc/pihole\"\n    install -Dm644 -t /etc/pihole /etc/.pihole/advanced/Templates/logrotate\n    echo \"\"\n}\n\nmigrate_gravity() {\n    echo \"  [i] Gravity migration checks\"\n    gravityDBfile=$(getFTLConfigValue files.gravity)\n\n    if [[ ! -f /etc/pihole/adlists.list ]]; then\n        echo \"  [i] No adlist file found, creating one with a default blocklist\"\n        echo \"https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts\" >/etc/pihole/adlists.list\n    fi\n\n    if [ ! -f \"${gravityDBfile}\" ]; then\n        echo \"  [i] ${gravityDBfile} does not exist (Likely due to a fresh volume). This is a required file for Pi-hole to operate.\"\n        echo \"  [i] Gravity will now be run to create the database\"\n        pihole -g\n    else\n        echo \"  [i] Existing gravity database found - schema will be upgraded if necessary\"\n        # source the migration script and run the upgrade function\n        source /etc/.pihole/advanced/Scripts/database_migration/gravity-db.sh\n        local upgradeOutput\n        upgradeOutput=$(upgrade_gravityDB \"${gravityDBfile}\" \"/etc/pihole\")\n        printf \"%b\" \"${upgradeOutput}\\\\n\" | sed 's/^/     /'\n    fi\n    echo \"\"\n}\n\n# shellcheck disable=SC2034\nftl_config() {\n\n    # Force a check of pihole-FTL --config, this will read any environment variables and set them in the config file\n    # suppress the output as we don't need to see the default values.\n    getFTLConfigValue >/dev/null\n\n    # If FTLCONF_files_macvendor is not set\n    if [[ -z \"${FTLCONF_files_macvendor:-}\" ]]; then\n        # User is not passing in a custom location - so force FTL to use the file we moved to / during the build\n        setFTLConfigValue \"files.macvendor\" \"/macvendor.db\"\n        chown pihole:pihole /macvendor.db\n    fi\n\n    # If getFTLConfigValue \"dns.upstreams\" returns [], default to Google's DNS server\n    if [[ $(getFTLConfigValue \"dns.upstreams\") == \"[]\" ]]; then\n        echo \"  [i] No DNS upstream set in environment or config file, defaulting to Google DNS\"\n        setFTLConfigValue \"dns.upstreams\" \"[\\\"8.8.8.8\\\", \\\"8.8.4.4\\\"]\"\n    fi\n\n    setup_web_password\n}\n\nmigrate_v5_configs() {\n    # Previously, Pi-hole created a number of files in /etc/dnsmasq.d\n    # During migration, their content is copied into the new single source of\n    # truth file /etc/pihole/pihole.toml and the old files are moved away to\n    # avoid conflicts with other services on this system\n    echo \"  [i] Migrating dnsmasq configuration files\"\n    V6_CONF_MIGRATION_DIR=\"/etc/pihole/migration_backup_v6\"\n    # Create target directory and make it owned by pihole:pihole\n    mkdir -p \"${V6_CONF_MIGRATION_DIR}\"\n    chown pihole:pihole \"${V6_CONF_MIGRATION_DIR}\"\n\n    # Move all conf files originally created by Pi-hole into this directory\n    # - 01-pihole.conf\n    # - 02-pihole-dhcp.conf\n    # - 04-pihole-static-dhcp.conf\n    # - 05-pihole-custom-cname.conf\n    # - 06-rfc6761.conf\n\n    mv /etc/dnsmasq.d/0{1,2,4,5}-pihole*.conf \"${V6_CONF_MIGRATION_DIR}/\" 2>/dev/null || true\n    mv /etc/dnsmasq.d/06-rfc6761.conf \"${V6_CONF_MIGRATION_DIR}/\" 2>/dev/null || true\n    echo \"\"\n\n    # Finally, after everything is in place, we can create the new config file\n    # /etc/pihole/pihole.toml\n    # This file will be created with the default settings unless the user has\n    # changed settings via setupVars.conf or the other dnsmasq files moved above\n    # During migration, setupVars.conf is moved to /etc/pihole/migration_backup_v6\n    local FTLoutput\n    FTLoutput=$(pihole-FTL migrate v6)\n\n    # Print the output of the FTL migration prefacing every line with six\n    # spaces for alignment with other container output. Replace the first line to match the style of the other messages\n    # We suppress the message about environment variables as these will be set on FTL's first real start\n    printf \"%b\" \"${FTLoutput}\\\\n\" | sed 's/^/      /' | sed 's/      Migrating config to Pi-hole v6.0 format/  [i] Migrating config to Pi-hole v6.0 format/' | sed 's/- 0 entries are forced through environment//'\n\n    # Print a blank line for separation\n    echo \"\"\n}\n\nsetup_web_password() {\n    if [ -z \"${FTLCONF_webserver_api_password+x}\" ] && [ -n \"${WEBPASSWORD_FILE}\" ] && [ -r \"/run/secrets/${WEBPASSWORD_FILE}\" ]; then\n        echo \"  [i] Setting FTLCONF_webserver_api_password from file\"\n        export FTLCONF_webserver_api_password=$(<\"/run/secrets/${WEBPASSWORD_FILE}\")\n    fi\n\n    # If FTLCONF_webserver_api_password is not set\n    if [ -z \"${FTLCONF_webserver_api_password+x}\" ]; then\n        # Is this already set to something other than blank (default) in FTL's config file? (maybe in a volume mount)\n        if [[ $(pihole-FTL --config webserver.api.pwhash) ]]; then\n            echo \"  [i] Password already set in config file\"\n            return\n        else\n            # If we are here, the password is set in neither the environment nor the config file\n            # We will generate a random password.\n            RANDOMPASSWORD=$(tr -dc _A-Z-a-z-0-9 </dev/urandom | head -c 8)\n            echo \"  [i] No password set in environment or config file, assigning random password: $RANDOMPASSWORD\"\n\n            # Explicitly turn off bash printing when working with secrets\n            { set +x; } 2>/dev/null\n\n            pihole-FTL --config webserver.api.password \"$RANDOMPASSWORD\" >/dev/null\n\n            # To avoid printing this if conditional in bash debug, turn off  debug above..\n            # then re-enable debug if necessary (more code but cleaner printed output)\n            if [ \"${PH_VERBOSE:-0}\" -gt 0 ]; then\n                set -x\n            fi\n        fi\n    else\n        echo \"  [i] Assigning password defined by Environment Variable\"\n    fi\n}\n\nfix_capabilities() {\n    # Testing on Docker 20.10.14 with no caps set shows the following caps available to the container:\n    # Current: cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap=ep\n    # FTL can also use CAP_NET_ADMIN and CAP_SYS_NICE. If we try to set them when they haven't been explicitly enabled, FTL will not start. Test for them first:\n    echo \"  [i] Setting capabilities on pihole-FTL where possible\"\n    capsh --has-p=cap_chown 2>/dev/null && CAP_STR+=',CAP_CHOWN'\n    capsh --has-p=cap_net_bind_service 2>/dev/null && CAP_STR+=',CAP_NET_BIND_SERVICE'\n    capsh --has-p=cap_net_raw 2>/dev/null && CAP_STR+=',CAP_NET_RAW'\n    capsh --has-p=cap_net_admin 2>/dev/null && CAP_STR+=',CAP_NET_ADMIN' || DHCP_READY='false'\n    capsh --has-p=cap_sys_nice 2>/dev/null && CAP_STR+=',CAP_SYS_NICE'\n    capsh --has-p=cap_sys_time 2>/dev/null && CAP_STR+=',CAP_SYS_TIME'\n\n    if [[ ${CAP_STR} ]]; then\n        # We have the (some of) the above caps available to us - apply them to pihole-FTL\n        echo \"  [i] Applying the following caps to pihole-FTL:\"\n        IFS=',' read -ra CAPS <<<\"${CAP_STR:1}\"\n        for i in \"${CAPS[@]}\"; do\n            echo \"        * ${i}\"\n        done\n\n        setcap \"${CAP_STR:1}\"+ep \"$(which pihole-FTL)\" || ret=$?\n\n        if [[ $DHCP_READY == false ]] && [[ $FTLCONF_dhcp_active == true ]]; then\n            # DHCP is requested but NET_ADMIN is not available.\n            echo \"ERROR: DHCP requested but NET_ADMIN is not available. DHCP will not be started.\"\n            echo \"      Please add cap_net_admin to the container's capabilities or disable DHCP.\"\n            setFTLConfigValue dhcp.active false\n        fi\n\n        if [[ $ret -ne 0 && \"${DNSMASQ_USER:-pihole}\" != \"root\" ]]; then\n            echo \"  [!] ERROR: Unable to set capabilities for pihole-FTL. Cannot run as non-root.\"\n            echo \"            If you are seeing this error, please set the environment variable 'DNSMASQ_USER' to the value 'root'\"\n            exit 1\n        fi\n    else\n        echo \"  [!] ERROR: Unable to set capabilities for pihole-FTL.\"\n        echo \"            Please ensure that the container has the required capabilities.\"\n        exit 1\n    fi\n    echo \"\"\n}\n"
  },
  {
    "path": "src/crontab.txt",
    "content": "59 1  * * 0 PATH=\"$PATH:/usr/sbin:/usr/local/bin/\" pihole updateGravity >/var/log/pihole/pihole_updateGravity.log || cat /var/log/pihole/pihole_updateGravity.log\n00 00 * * * PATH=\"$PATH:/usr/sbin:/usr/local/bin/\" pihole flush once quiet\n59 17 * * * PATH=\"$PATH:/usr/sbin:/usr/local/bin/\" pihole updatechecker\n"
  },
  {
    "path": "src/start.sh",
    "content": "#!/bin/bash\n\nif [ ! -x /bin/sh ]; then\n    echo \"Executable test for /bin/sh failed. Your Docker version is too old to run Alpine 3.14+ and Pi-hole. You must upgrade Docker.\";\n    exit 1;\nfi\n\nif [ \"${PH_VERBOSE:-0}\" -gt 0 ]; then\n    set -x\nfi\n\ntrap stop TERM INT QUIT HUP ERR\n\nCAPSH_PID=\"\"\nTRAP_TRIGGERED=0\n\nstart() {\n\n    # The below functions are all contained in bash_functions.sh\n    # shellcheck source=/dev/null\n    . /usr/bin/bash_functions.sh\n\n    # If the file /etc/pihole/setupVars.conf exists, but /etc/pihole/pihole.toml does not, then we are migrating v5->v6\n    # FTL Will handle the migration of the config files\n    if [[ -f /etc/pihole/setupVars.conf && ! -f /etc/pihole/pihole.toml ]]; then\n        echo \"  [i] v5 files detected that have not yet been migrated to v6\"\n        echo \"\"\n        migrate_v5_configs\n    fi\n\n    # ===========================\n    # Initial checks\n    # ===========================\n\n    # If PIHOLE_UID is set, modify the pihole user's id to match\n    set_uid_gid\n\n    # Configure FTL with any environment variables if needed\n    echo \"  [i] Starting FTL configuration\"\n    ftl_config\n\n    # Install additional packages inside the container if requested\n    install_additional_packages\n\n    # Start crond for scheduled scripts (logrotate, pihole flush, gravity update etc)\n    start_cron\n\n    # Install the logrotate config file\n    install_logrotate\n\n    #migrate Gravity Database if needed:\n    migrate_gravity\n\n    echo \"  [i] pihole-FTL pre-start checks\"\n    # Run the post stop script to cleanup any remaining artifacts from a previous run\n    sh /opt/pihole/pihole-FTL-poststop.sh\n\n    fix_capabilities\n    sh /opt/pihole/pihole-FTL-prestart.sh\n\n    # Get the FTL log file path from the config\n    FTLlogFile=$(getFTLConfigValue files.log.ftl)\n\n    # Get the EOF position of the FTL log file so that we can tail from there later.\n    local startFrom\n    startFrom=$(stat -c%s \"${FTLlogFile}\")\n\n    echo \"  [i] Starting pihole-FTL ($FTL_CMD) as ${DNSMASQ_USER}\"\n    echo \"\"\n\n    capsh --user=\"${DNSMASQ_USER}\" --keep=1 -- -c \"/usr/bin/pihole-FTL $FTL_CMD >/dev/null\" &\n    # Notes on above:\n    # - DNSMASQ_USER default of pihole is in Dockerfile & can be overwritten by runtime container env\n    # - /var/log/pihole/pihole*.log has FTL's output that no-daemon would normally print in FG too\n    #   prevent duplicating it in docker logs by sending to dev null\n\n    # We need the PID of the capsh process so that we can wait for it to finish\n    CAPSH_PID=$!\n\n    # Wait for FTL to start by monitoring the FTL log file for the \"FTL started\" line\n    if ! timeout 30 tail -F -c +$((startFrom + 1)) -- \"${FTLlogFile}\" | grep -q '########## FTL started'; then\n        echo \"  [!] ERROR: Did not find 'FTL started' message in ${FTLlogFile} in 30 seconds, stopping container\"\n        exit 1\n    fi\n\n    pihole updatechecker\n    local versionsOutput\n    versionsOutput=$(pihole -v)\n    echo \"  [i] Version info:\"\n    printf \"%b\" \"${versionsOutput}\\\\n\" | sed 's/^/      /'\n    echo \"\"\n\n    if [ \"${TAIL_FTL_LOG:-1}\" -eq 1 ]; then\n        # Start tailing the FTL log file from the EOF position we recorded on container start\n        tail -F -c +$((startFrom + 1)) -- \"${FTLlogFile}\" &\n    else\n        echo \"  [i] FTL log output is disabled. Remove the Environment variable TAIL_FTL_LOG, or set it to 1 to enable FTL log output.\"\n    fi\n\n    # Wait for the capsh process (which spawned FTL) to finish\n    wait $CAPSH_PID\n    FTL_EXIT_CODE=$?\n\n    # If we are here, then FTL has exited.\n    # If the trap was triggered, then stop will have already been called\n    if [ $TRAP_TRIGGERED -eq 0 ]; then\n        # Pass the exit code through to the stop function\n        stop $FTL_EXIT_CODE\n    fi\n}\n\nstop() {\n    local FTL_EXIT_CODE=$1\n\n    # if we have nothing in FTL_EXIT_CODE, then have been called by the trap. Close FTL and wait for the CAPSH_PID to finish\n    if [ -z \"${FTL_EXIT_CODE}\" ]; then\n        TRAP_TRIGGERED=1\n        echo \"\"\n        echo \"  [i] Container stop requested...\"\n        echo \"  [i] pihole-FTL is running - Attempting to shut it down cleanly\"\n        echo \"\"\n        killall --signal 15 pihole-FTL\n\n        wait $CAPSH_PID\n        FTL_EXIT_CODE=$?\n    fi\n\n    # Wait for a few seconds to allow the FTL log tail to catch up before exiting the container\n    sleep 2\n\n    # ensure the exit code is an integer, if not set it to 1\n    if ! [[ \"${FTL_EXIT_CODE}\" =~ ^[0-9]+$ ]]; then\n        FTL_EXIT_CODE=1\n    fi\n\n    sh /opt/pihole/pihole-FTL-poststop.sh\n\n    echo \"\"\n    echo \"  [i] pihole-FTL exited with status $FTL_EXIT_CODE\"\n    echo \"\"\n    echo \"  [i] Container will now stop or restart depending on your restart policy\"\n    echo \"      https://docs.docker.com/engine/containers/start-containers-automatically/#use-a-restart-policy\"\n    echo \"\"\n\n    exit \"${FTL_EXIT_CODE}\"\n\n}\n\nstart\n"
  },
  {
    "path": "test/TESTING.md",
    "content": "# Prerequisites\n\nMake sure you have `docker`, `python` and `tox` installed.\n\n# Running tests locally\n\n`tox -c test/tox.ini`\n\nShould result in:\n\n- An image named `pihole:CI_container` being built\n- Tests being ran to confirm the image doesn't have any regressions\n"
  },
  {
    "path": "test/requirements.txt",
    "content": "pytest == 9.0.2\npytest-testinfra == 10.2.2\npytest-clarity == 1.0.1\ntox == 4.35.0\n# Not adding pytest-xdist as using pytest with n > 1 cores\n# causes random issues with the emulated architectures\n"
  },
  {
    "path": "test/tests/__init__.py",
    "content": ""
  },
  {
    "path": "test/tests/conftest.py",
    "content": "import pytest\nimport subprocess\nimport testinfra\nimport testinfra.backend.docker\nimport os\n\n\n# Monkeypatch sh to bash, if they ever support non hard code /bin/sh this can go away\n# https://github.com/pytest-dev/pytest-testinfra/blob/master/testinfra/backend/docker.py\ndef run_bash(self, command, *args, **kwargs):\n    cmd = self.get_command(command, *args)\n    if self.user is not None:\n        out = self.run_local(\n            \"docker exec -u %s %s /bin/bash -c %s\", self.user, self.name, cmd\n        )\n    else:\n        out = self.run_local(\"docker exec %s /bin/bash -c %s\", self.name, cmd)\n    out.command = self.encode(cmd)\n    return out\n\n\ntestinfra.backend.docker.DockerBackend.run = run_bash\n\n\n# scope='session' uses the same container for all the tests;\n# scope='function' uses a new container per test function.\n@pytest.fixture(scope=\"function\")\ndef docker(request):\n    # Get platform from environment variable, default to None if not set\n    platform = os.environ.get(\"CIPLATFORM\")\n\n    # build the docker run command with args\n    cmd = [\"docker\", \"run\", \"-d\", \"-t\"]\n\n    # Only add platform flag if CIPLATFORM is set\n    if platform:\n        cmd.extend([\"--platform\", platform])\n\n    # Get env vars from parameterization\n    env_vars = getattr(request, \"param\", [])\n    if isinstance(env_vars, str):\n        env_vars = [env_vars]\n\n    # add parameterized environment variables\n    for env_var in env_vars:\n        cmd.extend([\"-e\", env_var])\n\n    # add default TZ if not already set\n    if not any(\"TZ=\" in arg for arg in cmd):\n        cmd.extend([\"-e\", 'TZ=\"Europe/London\"'])\n\n    # add the image name\n    cmd.append(\"pihole:CI_container\")\n\n    # run a container\n    docker_id = subprocess.check_output(cmd).decode().strip()\n    # return a testinfra connection to the container\n    yield testinfra.get_host(\"docker://\" + docker_id)\n    # at the end of the test suite, destroy the container\n    subprocess.check_call([\"docker\", \"rm\", \"-f\", docker_id])\n"
  },
  {
    "path": "test/tests/test_bash_functions.py",
    "content": "import pytest\n\n\n# Adding 5 seconds sleep to give the emulated architecture time to run\n@pytest.mark.parametrize(\"docker\", [\"FTLCONF_webserver_port=999\"], indirect=True)\ndef test_ftlconf_webserver_port(docker):\n    func = docker.run(\"echo ${FTLCONF_webserver_port}\")\n    assert \"999\" in func.stdout\n    func = docker.run(\"\"\"\n        sleep 5\n        pihole-FTL --config webserver.port\n        \"\"\")\n    assert \"999\" in func.stdout\n\n\n# Adding 5 seconds sleep to give the emulated architecture time to run\n@pytest.mark.parametrize(\n    \"docker\", [\"FTLCONF_dns_upstreams=1.2.3.4;5.6.7.8#1234\"], indirect=True\n)\ndef test_ftlconf_dns_upstreams(docker):\n    func = docker.run(\"echo ${FTLCONF_dns_upstreams}\")\n    assert \"1.2.3.4;5.6.7.8#1234\" in func.stdout\n    func = docker.run(\"\"\"\n        sleep 5\n        pihole-FTL --config dns.upstreams\n        \"\"\")\n    assert \"[ 1.2.3.4, 5.6.7.8#1234 ]\" in func.stdout\n\n\nCMD_SETUP_WEB_PASSWORD = \". bash_functions.sh ; setup_web_password\"\n\n\ndef test_random_password_assigned_fresh_start(docker):\n    func = docker.run(CMD_SETUP_WEB_PASSWORD)\n    assert \"assigning random password:\" in func.stdout\n\n\n@pytest.mark.parametrize(\n    \"docker\", [\"FTLCONF_webserver_api_password=1234567890\"], indirect=True\n)\ndef test_password_set_by_envvar(docker):\n    func = docker.run(CMD_SETUP_WEB_PASSWORD)\n    assert \"Assigning password defined by Environment Variable\" in func.stdout\n"
  },
  {
    "path": "test/tests/test_general.py",
    "content": "import pytest\nimport os\n\n\n# Adding 5 seconds sleep to give the emulated architecture time to run\n@pytest.mark.parametrize(\"docker\", [\"PIHOLE_UID=456\"], indirect=True)\ndef test_pihole_uid_env_var(docker):\n    func = docker.run(\"echo ${PIHOLE_UID}\")\n    assert \"456\" in func.stdout\n    func = docker.run(\"\"\"\n        sleep 5\n        id -u pihole\n        \"\"\")\n    assert \"456\" in func.stdout\n\n\n# Adding 5 seconds sleep to give the emulated architecture time to run\n@pytest.mark.parametrize(\"docker\", [\"PIHOLE_GID=456\"], indirect=True)\ndef test_pihole_gid_env_var(docker):\n    func = docker.run(\"echo ${PIHOLE_GID}\")\n    assert \"456\" in func.stdout\n    func = docker.run(\"\"\"\n        sleep 5\n        id -g pihole\n        \"\"\")\n    assert \"456\" in func.stdout\n\n\ndef test_pihole_ftl_version(docker):\n    func = docker.run(\"pihole-FTL -vv\")\n    assert func.rc == 0\n    assert \"Version:\" in func.stdout\n\n\n@pytest.mark.skipif(\n    not os.environ.get(\"CIPLATFORM\"),\n    reason=\"CIPLATFORM environment variable not set, running locally\",\n)\ndef test_pihole_ftl_architecture(docker):\n    func = docker.run(\"pihole-FTL -vv\")\n    assert func.rc == 0\n    assert \"Architecture:\" in func.stdout\n    # Get the expected architecture from CIPLATFORM environment variable\n    platform = os.environ.get(\"CIPLATFORM\")\n    assert platform in func.stdout\n\n\n# Wait for FTL to start up, then stop the container gracefully\n# Finally, check the container logs to see if FTL was shut down cleanly\ndef test_pihole_ftl_starts_and_shuts_down_cleanly(docker):\n    import subprocess\n    import time\n\n    # Get the container ID from the docker fixture\n    container_id = docker.backend.name\n\n    # Wait for FTL to fully start up by checking logs\n    max_wait_time = 60  # Maximum wait time in seconds\n    start_time = time.time()\n    ftl_started = False\n\n    while time.time() - start_time < max_wait_time:\n        result = subprocess.run(\n            [\"docker\", \"logs\", container_id], capture_output=True, text=True\n        )\n\n        if \"########## FTL started\" in result.stdout:\n            ftl_started = True\n            break\n\n        time.sleep(1)  # Check every second\n\n    assert ftl_started, f\"FTL did not start within {max_wait_time} seconds\"\n\n    # Stop the container gracefully (sends SIGTERM)\n    subprocess.run([\"docker\", \"stop\", container_id], check=True)\n\n    # Get the container logs\n    result = subprocess.run(\n        [\"docker\", \"logs\", container_id], capture_output=True, text=True\n    )\n\n    # Check for clean shutdown messages in the logs\n    assert \"INFO: ########## FTL terminated after\" in result.stdout\n    assert \"(code 0)\" in result.stdout\n\n\ndef test_cronfile_valid(docker):\n    func = docker.run(\"\"\"\n        /usr/bin/crontab /crontab.txt\n        crond -d 8 -L /cron.log\n        grep 'parse error' /cron.log\n    \"\"\")\n    assert \"parse error\" not in func.stdout\n"
  },
  {
    "path": "test/tox.ini",
    "content": "[tox]\nenvlist = py3\n\n[testenv:py3]\nallowlist_externals = docker\ndeps = -rrequirements.txt\npassenv = CIPLATFORM\nsetenv =\n    COLUMNS=120\n    PY_COLORS=1\ncommands =  # Build the Docker image for testing depending on the architecture, fall back to 'local' if not set\n            # This allows us to run the tests on the host architecture if not on CI\n            docker buildx build --load --platform={env:CIPLATFORM:local} --progress plain -f ../src/Dockerfile -t pihole:CI_container ../src/\n            # run the tests\n            # # Not using > 1 cores as it causes random issues with the emulated architectures\n            pytest {posargs:-vv} ./tests/\n"
  }
]