Full Code of pi-hole/docker-pi-hole for AI

master ac202988906f cached
40 files
77.0 KB
21.5k tokens
12 symbols
1 requests
Download .txt
Repository: pi-hole/docker-pi-hole
Branch: master
Commit: ac202988906f
Files: 40
Total size: 77.0 KB

Directory structure:
gitextract_w4fbfg1b/

├── .codespellignore
├── .editorconfig
├── .github/
│   ├── CODEOWNERS
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   └── config.yml
│   ├── PULL_REQUEST_TEMPLATE.md
│   ├── dco.yml
│   ├── dependabot.yml
│   ├── release.yml
│   └── workflows/
│       ├── build-and-publish.yml
│       ├── build-and-test.yml
│       ├── codespell.yml
│       ├── dockerhub-description.yml
│       ├── editorconfig.yml
│       ├── housekeeping.yml
│       ├── merge-conflict.yml
│       ├── stale.yml
│       ├── stale_pr.yml
│       └── sync-back-to-dev.yml
├── .gitignore
├── .gitmodules
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── build.sh
├── examples/
│   ├── Caddyfile
│   └── docker-compose-caddy-proxy.yml
├── src/
│   ├── .dockerignore
│   ├── Dockerfile
│   ├── bash_functions.sh
│   ├── crontab.txt
│   └── start.sh
└── test/
    ├── TESTING.md
    ├── requirements.txt
    ├── tests/
    │   ├── __init__.py
    │   ├── conftest.py
    │   ├── test_bash_functions.py
    │   └── test_general.py
    └── tox.ini

================================================
FILE CONTENTS
================================================

================================================
FILE: .codespellignore
================================================
padd


================================================
FILE: .editorconfig
================================================
# EditorConfig is awesome: https://editorconfig.org/

# top-most EditorConfig file
root = true

# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = tab
tab_width = 4
charset = utf-8
trim_trailing_whitespace = true

[*.yml]
tab_width = 2

[*.md]
tab_width = 2


================================================
FILE: .github/CODEOWNERS
================================================
# see https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners#codeowners-syntax

# These owners will be the default owners for everything in
# the repo. Unless a later match takes precedence,
*       @pi-hole/docker-maintainers


================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''

---

<!-- Provide a general summary of the issue in the Title above -->
<!-- Note: these are comments that don't show up in the actual issue, no need to delete them as you fill out the template -->

<!-- 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 -->

<!-- pick ONE: Bug,
               Feature Request,
               Run Issue (running Pi-hole container failing),
               Build Issue (Building image failing)
Enter in line below: -->
This is a: **FILL ME IN**


## Details
<!-- Provide a more detailed introduction to the issue or feature, try not to duplicate info from lower sections by reviewing the entire template first -->

## Related Issues
- [ ] I have searched this repository/Pi-hole forums for existing issues and pull requests that look similar
<!-- Add links below! -->

<!------- FEATURE REQUESTS CAN STOP FILLING IN TEMPLATE HERE -------->
<!------- ISSUES SHOULD FILL OUT REMAINDER OF TEMPLATE -------->

## How to reproduce the issue

1. Environment data
  * Operating System: **ENTER HERE** <!-- Debian, Ubuntu, Rasbian, etc -->
  * Hardware: <!-- PC, RasPi B/2B/3B/4B, Mac, Synology, QNAP, etc -->
  * Kernel Architecture: <!-- x86/amd64, ArmV7, ArmV8 32bit, ArmV8 64bit, etc -->
  * Docker Install Info and version:
    - Software source: <!-- official docker-ce, OS provided package, Hypriot -->
    - Supplimentary Software: <!-- synology, portainer, etc -->
  * Hardware architecture: <!-- ARMv7, x86 -->

2. docker-compose.yml contents, docker run shell command, or paste a screenshot of any UI based configuration of containers here
3. any additional info to help reproduce


## These common fixes didn't work for my issue
<!-- IMPORTANT! Help me help you! Ordered with most common fixes first. -->
- [ ] I have tried removing/destroying my container, and re-creating a new container
- [ ] I have tried fresh volume data by backing up and moving/removing the old volume data
- [ ] I have tried running the stock `docker run` example(s) in the readme (removing any customizations I added)
- [ ] I have tried a newer or older version of Docker Pi-hole (depending what version the issue started in for me)
- [ ] I have tried running without my volume data mounts to eliminate volumes as the cause

If the above debugging / fixes revealed any new information note it here.
Add any other debugging steps you've taken or theories on root cause that may help.


================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
  - name: Questions and Configurations
    url: https://discourse.pi-hole.net
    about: Ask a question or get help with configurations.
  - name: Feature Requests
    url: https://discourse.pi-hole.net/c/feature-requests/8
    about: See existing Feature Requests and suggest new ones.
  - name: Documentation
    url: https://docs.pi-hole.net
    about: Documentation and guides.



================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
`{Please select 'base: development' as target branch above! (you can delete this line)}`

<!--- Provide a general summary of your changes in the Title above -->

## Description
<!--- Describe your changes in detail -->

## Motivation and Context
<!--- Why is this change required? What problem does it solve? -->
<!--- If it fixes an open issue, please link to the issue here. -->

## How Has This Been Tested?
<!--- Please describe in detail how you tested your changes. -->
<!--- Include details of your testing environment, tests ran to see how -->
<!--- your change affects other areas of the code, etc. -->

## Types of changes
<!--- What types of changes does your code introduce? Put an `x` in all the boxes that apply: -->
- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)

## Checklist:
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
<!--- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
- [ ] My code follows the code style of this project.
- [ ] My change requires a change to the documentation.
- [ ] I have updated the documentation accordingly.


================================================
FILE: .github/dco.yml
================================================
require:
  members: false


================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
  # Maintain dependencies for GitHub Actions
  - package-ecosystem: "github-actions"
    directories:
      - "/"
    schedule:
      interval: "weekly"
      day: saturday
      time: "10:00"
    target-branch: development
  - package-ecosystem: "docker"
    directory: "/src/"
    schedule:
      interval: "weekly"
      day: saturday
      time: "10:00"
    target-branch: development
  - package-ecosystem: pip
    directory: "/test"
    schedule:
      interval: weekly
      day: saturday
      time: "10:00"
    open-pull-requests-limit: 10
    target-branch: development


================================================
FILE: .github/release.yml
================================================
changelog:
  exclude:
    labels:
      - internal
      - dependencies
    authors:
      - dependabot
      - github-actions


================================================
FILE: .github/workflows/build-and-publish.yml
================================================
name: Build Image and Publish
on:
  schedule:
    - cron: "0 5 * * *"
  push:
    branches:
      - development
  release:
    types: [published]

permissions:
  contents: read
  packages: write

env:
  dockerhub: ${{ secrets.DOCKERHUB_NAMESPACE }}/pihole
  ghcr: ghcr.io/${{ github.repository_owner }}/pihole
  components_branch: ${{ github.event_name == 'release' && 'master' || 'development' }}

jobs:
  build:
    runs-on: ${{ matrix.runner }}
    strategy:
      fail-fast: false
      matrix:
        include:
          - platform: linux/amd64
            runner: ubuntu-latest
          - platform: linux/386
            runner: ubuntu-latest
          - platform: linux/arm/v6
            runner: ubuntu-24.04-arm
          - platform: linux/arm/v7
            runner: ubuntu-24.04-arm
          - platform: linux/arm64
            runner: ubuntu-24.04-arm
          - platform: linux/riscv64
            runner: ubuntu-24.04-arm

    steps:
      - name: Prepare name for digest up/download
        run: |
          platform=${{ matrix.platform }}
          echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV

      - &checkout-repo
        name: Checkout Repo
        if: github.event_name != 'schedule'
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2

      - &checkout-dev
        name: Checkout dev branch if scheduled
        if: github.event_name == 'schedule'
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
        with:
          ref: development

      - &docker-meta
        name: Docker meta
        id: meta
        uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 #v5.10.0
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          images: |
            ${{ env.dockerhub }}
            ${{ env.ghcr }}
          flavor: |
            latest=${{ startsWith(github.ref, 'refs/tags/') }}
          tags: |
            type=schedule,pattern=nightly
            type=raw,value=nightly,enable=${{ github.event_name == 'push' }}
            type=ref,event=tag

      - &login-dockerhub
        name: Login to Docker Hub
        uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 #v3.7.0
        with:
          registry: docker.io
          username: ${{ secrets.DOCKERHUB_USER }}
          password: ${{ secrets.DOCKERHUB_PASS }}

      - &login-ghcr
        name: Login to GitHub Container Registry
        uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 #v3.7.0
        with:
          registry: ghcr.io
          username: ${{ github.repository_owner }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Set up QEMU
        uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 #v3.7.0
        with:
          platforms: ${{ matrix.platform}}

      - &setup-buildx
        name: Set up Docker Buildx
        uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f #v3.12.0
        with:
          # Buildx version 0.31.1 broke our publish workflow, this need to be revised when 0.32.0 is released
          # https://github.com/docker/buildx/releases/tag/v0.31.1
          version: v0.31.0

      - name: Build container and push by digest
        id: build
        uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 #v6.19.2
        with:
          context: ./src/
          platforms: ${{ matrix.platform }}
          build-args: |
            PIHOLE_DOCKER_TAG=${{ steps.meta.outputs.version }}
            FTL_BRANCH=${{ env.components_branch }}
            CORE_BRANCH=${{ env.components_branch }}
            WEB_BRANCH=${{ env.components_branch }}
            PADD_BRANCH=${{ env.components_branch }}
          labels: ${{ steps.meta.outputs.labels }}
          outputs: |
            type=image,name=${{ env.dockerhub }},push-by-digest=true,name-canonical=true,push=true

      - name: Export digests
        run: |
          mkdir -p /tmp/digests
          digest_docker="${{ steps.build.outputs.digest }}"
          touch "/tmp/digests/${digest_docker#sha256:}"

      - name: Upload digest
        uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f #v6.0.0
        with:
          name: digests-${{ env.PLATFORM_PAIR }}
          path: /tmp/digests/*
          if-no-files-found: error
          retention-days: 1

  # Merge all the digests into a single file
  # If we would push immediately above, the individual runners would overwrite each other's images
  # https://docs.docker.com/build/ci/github-actions/multi-platform/#distribute-build-across-multiple-runners
  merge-and-deploy:
    runs-on: ubuntu-latest
    needs:
      - build
    steps:
      - *checkout-repo
      - *checkout-dev

      - name: Download digests
        uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 #v7.0.0
        with:
          path: /tmp/digests
          pattern: digests-*
          merge-multiple: true

      - *setup-buildx
      - *docker-meta
      - *login-dockerhub
      - *login-ghcr

      - name: Create manifest list and push (DockerHub and GitHub Container Registry)
        working-directory: /tmp/digests
        run: |
          docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
            $(printf '${{ env.dockerhub }}@sha256:%s ' *)
          docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \
            $(printf '${{ env.ghcr }}@sha256:%s ' *)

      - name: Inspect images
        run: |
          docker buildx imagetools inspect ${{ env.dockerhub }}:${{ steps.meta.outputs.version }}
          docker buildx imagetools inspect ${{ env.ghcr }}:${{ steps.meta.outputs.version }}


================================================
FILE: .github/workflows/build-and-test.yml
================================================
name: Build Image and Test
on:
  pull_request:

permissions:
  contents: read

jobs:
  build-and-test:
    runs-on: ${{ matrix.runner }}
    strategy:
      fail-fast: false
      matrix:
        include:
          - platform: linux/amd64
            runner: ubuntu-latest
          - platform: linux/386
            runner: ubuntu-latest
          - platform: linux/arm/v6
            runner: ubuntu-24.04-arm
          - platform: linux/arm/v7
            runner: ubuntu-24.04-arm
          - platform: linux/arm64
            runner: ubuntu-24.04-arm
          - platform: linux/riscv64
            runner: ubuntu-24.04-arm
    env:
      CI_ARCH: ${{ matrix.platform }}
    steps:
    - name: Checkout Repo
      uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2

    - name: Set up QEMU
      uses: docker/setup-qemu-action@c7c53464625b32c7a7e944ae62b3e17d2b600130 #v3.7.0

    - name: Set up Python
      uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 #v6.2.0
      with:
        python-version: "3.13"

    - name: Run black formatter
      run: |
        pip install black
        black --check --diff test/tests/

    - name: Install wheel
      run:  pip install wheel

    - name: Install dependencies
      run: pip install -r test/requirements.txt

    - name: Test with tox
      run: |
        CIPLATFORM=${{ env.CI_ARCH }} tox -c test/tox.ini


================================================
FILE: .github/workflows/codespell.yml
================================================
name: Codespell
on:
  pull_request:
    types: [opened, synchronize, reopened, ready_for_review]

permissions:
  contents: read

jobs:
  spell-check:
    if: github.event.pull_request.draft == false
    runs-on: ubuntu-latest
    steps:
    -
      name: Checkout repository
      uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
    -
      name: Spell-Checking
      uses: codespell-project/actions-codespell@8f01853be192eb0f849a5c7d721450e7a467c579 #v2.2
      with:
        ignore_words_file: .codespellignore


================================================
FILE: .github/workflows/dockerhub-description.yml
================================================
name: Update Docker Hub Description
permissions:
  contents: read
on:
  push:
    branches:
      - master
    paths:
      - README.md
      - .github/workflows/dockerhub-description.yml
jobs:
  dockerHubDescription:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2

    - name: Docker Hub Description
      uses: peter-evans/dockerhub-description@1b9a80c056b620d92cedb9d9b5a223409c68ddfa #v5
      with:
        username: ${{ secrets.DOCKERHUB_USER }}
        password: ${{ secrets.DOCKERHUB_PASS }}
        repository: pihole/pihole
        short-description: ${{ github.event.repository.description }}


================================================
FILE: .github/workflows/editorconfig.yml
================================================
name: Editorconfig-Checker
on:
  pull_request:
    types: [opened, synchronize, reopened, ready_for_review]

permissions:
  contents: read

jobs:
  editorconfig-checker:
    if: github.event.pull_request.draft == false
    runs-on: ubuntu-latest
    steps:
    - name: Checkout repository
      uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2

    - name: Get editorconfig-checker
      uses: editorconfig-checker/action-editorconfig-checker@4b6cd6190d435e7e084fb35e36a096e98506f7b9 #v2.1.0

    - name: Run editorconfig-checker
      run: editorconfig-checker


================================================
FILE: .github/workflows/housekeeping.yml
================================================
name: Remove untagged images from registry
on:
    workflow_dispatch:
    schedule:
        - cron: "0 0 * * *"

permissions:
  packages: write

jobs:
    housekeeping:
        runs-on: ubuntu-latest
        steps:
            -
                name: Delete all containers from repository without tags
                uses: Chizkiyahu/delete-untagged-ghcr-action@68758dd8caf1d9dbaed1fe9cc1a1f8fcea1c4cf0 #v6.1.0
                with:
                    token: ${{ secrets.PAT_TOKEN }}
                    repository_owner: ${{ github.repository_owner }}
                    repository: ${{ github.repository }}
                    untagged_only: true
                    owner_type: org # or user
                    except_untagged_multiplatform: true


================================================
FILE: .github/workflows/merge-conflict.yml
================================================
name: "Check for merge conflicts"
on:
  # So that PRs touching the same files as the push are updated
  push:
  # So that the `dirtyLabel` is removed if conflicts are resolve
  # We recommend `pull_request_target` so that github secrets are available.
  # In `pull_request` we wouldn't be able to change labels of fork PRs
  pull_request_target:
    types: [synchronize]

permissions:
  contents: read
  pull-requests: write

jobs:
  main:
    runs-on: ubuntu-latest
    steps:
      - name: Check if PRs are have merge conflicts
        uses: eps1lon/actions-label-merge-conflict@1df065ebe6e3310545d4f4c4e862e43bdca146f0 #v3.0.3
        with:
          dirtyLabel: "Merge Conflict"
          repoToken: "${{ secrets.GITHUB_TOKEN }}"
          commentOnDirty: "This pull request has conflicts, please resolve those before we can evaluate the pull request."
          commentOnClean: "Conflicts have been resolved."


================================================
FILE: .github/workflows/stale.yml
================================================
name: Mark stale issues

on:
  schedule:
    - cron: '0 8 * * *'
  workflow_dispatch:
  issue_comment:

permissions:
  issues: write
  pull-requests: write

env:
  stale_label: stale

jobs:
  stale_action:
    if: github.event_name != 'issue_comment'
    runs-on: ubuntu-latest
    permissions:
      issues: write

    steps:
    - uses: actions/stale@997185467fa4f803885201cee163a9f38240193d #v10.1.1
      with:
        repo-token: ${{ secrets.GITHUB_TOKEN }}
        days-before-stale: 30
        days-before-close: 5
        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.'
        stale-issue-label: '${{ env.stale_label }}'
        exempt-issue-labels: 'pinned, Fixed in next release, bug, never-stale, documentation, investigating, v6'
        exempt-all-issue-assignees: true
        operations-per-run: 300
        close-issue-reason: 'not_planned'

  remove_stale:
    # trigger "stale" removal immediately when stale issues are commented on
    # we need to explicitly check that the trigger does not run on comment on a PR as
    # 'issue_comment' triggers on issues AND PR comments
    # https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#issue_comment-on-issues-only-or-pull-requests-only
    if: ${{ !github.event.issue.pull_request && github.event_name != 'schedule' }}
    permissions:
      contents: read #  for actions/checkout
      issues: write #  to edit issues label
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
      - name: Remove 'stale' label
        run: gh issue edit ${{ github.event.issue.number }} --remove-label ${{ env.stale_label }}
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}



================================================
FILE: .github/workflows/stale_pr.yml
================================================
name: Close stale PR
# This action will add a `stale` label and close immediately every PR that meets the following conditions:
#   - it is already marked with "merge conflict" label
#   - there was no update/comment on the PR in the last 30 days.

on:
  schedule:
    - cron: '0 10 * * *'
  workflow_dispatch:

jobs:
  stale:

    runs-on: ubuntu-latest
    permissions:
      issues: write
      pull-requests: write

    steps:
      - uses: actions/stale@997185467fa4f803885201cee163a9f38240193d #v10.1.1
        with:
          repo-token: ${{ secrets.GITHUB_TOKEN }}
          # Do not automatically mark PR/issue as stale
          days-before-stale: -1
          # Override 'days-before-stale' for PR only
          days-before-pr-stale: 30
          # Close PRs immediately, after marking them 'stale'
          days-before-pr-close: 0
          # only run the action on merge conflict PR
          any-of-labels: 'Merge Conflict'
          exempt-pr-labels: 'internal,never-stale,ON HOLD,in progress'
          exempt-all-pr-assignees: true
          operations-per-run: 300
          stale-pr-message: ''
          close-pr-message: 'Existing merge conflicts have not been addressed. This PR is considered abandoned.'


================================================
FILE: .github/workflows/sync-back-to-dev.yml
================================================
name: Sync Back to Development

on:
  push:
    branches:
      - master

permissions:
  contents: write
  pull-requests: write

jobs:
  sync-branches:
    runs-on: ubuntu-latest
    name: Syncing branches
    steps:
      - name: Checkout
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd #v6.0.2
      - name: Opening pull request
        run: gh pr create -B development -H master --title 'Sync master back into development' --body 'Created by Github action' --label 'internal'
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}


================================================
FILE: .gitignore
================================================
*.sw*
*.pyc
.cache
__pycache__
.tox
.pipenv
.eggs
UNKNOWN.egg-info
.env
ci-workspace
.gh-workspace
docker-compose.yml
etc-dnsmasq.d/
etc-pihole/
var-log/
.vscode/
.pytest_cache/

# WIP/test stuff
doco.yml

# Ignore FTL Binary if it exists
src/pihole-FTL


================================================
FILE: .gitmodules
================================================


================================================
FILE: CHANGELOG.md
================================================
# Docker Pi-Hole changelog

Notes 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.

See the [Pi-hole releases](https://github.com/pi-hole/pi-hole/releases) for details on updates unrelated to docker image releases


================================================
FILE: CONTRIBUTING.md
================================================
# Pull Request Guidelines

Please review the following before opening a pull request (PR) to help your PR go smoothly:

* Code changes go to the `development` branch first
  * To ensure proper testing and quality control, target any code change pull requests against `development` branch.

* Make sure the tests pass
  * 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.
  * Your tests will probably run faster locally and you get a faster feedback loop.


================================================
FILE: LICENSE
================================================
Copyright (C) 2017 Pi-hole, LLC (https://pi-hole.net)
Pi-hole Core

This software is licensed under the European Union Public License (EUPL)
The license is available in the 22 official languages of the EU. The English version is included here.
Please see https://joinup.ec.europa.eu/community/eupl/og_page/eupl for official translations of the other languages.

This license applies to the whole project EXCEPT:

 - any commits made to the master branch prior to the release of version 3.0

The licenses that existed prior to this change have remained intact.

-------------------------------------------------------------
EUROPEAN UNION PUBLIC LICENCE v. 1.2

EUPL © the European Union 2007, 2016

This 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).
The 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:
Licensed under the EUPL
or has expressed by any other means his willingness to license under the EUPL.

1. Definitions

In this Licence, the following terms have the following meaning:

- The Licence: this Licence.
- 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.
- 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.
- The Work: the Original Work or its Derivative Works.
- The Source Code: the human-readable form of the Work which is the most convenient for people to study and modify.
- The Executable Code: any code which has generally been compiled and which is meant to be interpreted by a computer as a program.
- The Licensor: the natural or legal person that distributes or communicates the Work under the Licence.
- Contributor(s): any natural or legal person who modifies the Work under the Licence, or otherwise contributes to the creation of a Derivative Work.
- The Licensee or You: any natural or legal person who makes any usage of the Work under the terms of the Licence.
- 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.

2. Scope of the rights granted by the Licence

The 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:
- use the Work in any circumstance and for all usage,
- reproduce the Work,
- modify the Work, and make Derivative Works based upon the Work,
- 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,
- distribute the Work or copies thereof,
- lend and rent the Work or copies thereof,
- sublicense rights in the Work or copies thereof.
Those rights can be exercised on any media, supports and formats, whether now known or later invented, as far as the applicable law permits so.
In 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.
The 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.

3. Communication of the Source Code

The 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.

4. Limitations on copyright

Nothing 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.

5. Obligations of the Licensee

The grant of the rights mentioned above is subject to some restrictions and obligations imposed on the Licensee. Those obligations are the following:

Attribution 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.

Copyleft 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.

Compatibility 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.

Provision 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.

Legal 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.

6. Chain of Authorship

The 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.

Each 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.

Each 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.

7. Disclaimer of Warranty

The 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.
For 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.
This disclaimer of warranty is an essential part of the Licence and a condition for the grant of any rights to the Work.

8. Disclaimer of Liability

Except 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.

9. Additional agreements

While 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.

10. Acceptance of the Licence

The 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.
Similarly, 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.

11. Information to the public

In 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.

12. Termination of the Licence

The Licence and the rights granted hereunder will terminate automatically upon any breach by the Licensee of the terms of the Licence.
Such 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.

13. Miscellaneous

Without prejudice of Article 9 above, the Licence represents the complete agreement between the Parties as to the Work.
If 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.
The 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.
All linguistic versions of this Licence, approved by the European Commission, have identical value. Parties can take advantage of the linguistic version of their choice.

14. Jurisdiction

Without prejudice to specific agreement between parties,
- 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,
- 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.

15. Applicable Law

Without prejudice to specific agreement between parties,
- 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,
- this licence shall be governed by Belgian law if the Licensor has no seat, residence or registered office inside a European Union Member State.

===

Appendix

Compatible Licences according to Article 5 EUPL are:
- GNU General Public License (GPL) v. 2, v. 3
- GNU Affero General Public License (AGPL) v. 3
- Open Software License (OSL) v. 2.1, v. 3.0
- Eclipse Public License (EPL) v. 1.0
- CeCILL v. 2.0, v. 2.1
- Mozilla Public Licence (MPL) v. 2
- GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3
- Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for works other than software
- European Union Public Licence (EUPL) v. 1.1, v. 1.2
- Québec Free and Open-Source Licence - Reciprocity (LiLiQ-R) or Strong Reciprocity (LiLiQ-R+)
- 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.
-  All other changes or additions to this Appendix require the production of a new EUPL version.


================================================
FILE: README.md
================================================
# Docker Pi-hole

[![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)

<div align="center">
  <a href="https://pi-hole.net/">
    <img src="https://pi-hole.github.io/graphics/Vortex/vortex_with_text.svg" width="144" height="256" alt="Pi-hole website">
  </a>
  <br>
  <strong>Network-wide ad blocking via your own Linux hardware</strong>
  <br>
  <br>
  <div align="center">
    <a href="https://pi-hole.net/">Pi-hole website</a> |
    <a href="https://docs.pi-hole.net/">Documentation</a> |
    <a href="https://discourse.pi-hole.net/">Discourse Forum</a> |
    <a href="https://pi-hole.net/donate">Donate</a>
  </div>
  <br>
  <br>
</div>
<!-- Delete above HTML and insert markdown for dockerhub : ![Pi-hole](https://pi-hole.github.io/graphics/Vortex/Vortex_with_text.png) -->

## Upgrade Notes

> [!NOTE]
> **Using Watchtower?\
> See the [Note on Watchtower](https://docs.pi-hole.net/docker/tips-and-tricks/#note-on-watchtower) in our documentation**.

> [!TIP]
> 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.\
> 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.

## Quick Start

Using [Docker-compose](https://docs.docker.com/compose/install/):

1. Copy the below docker compose example and update as needed:

```yml
# More info at https://github.com/pi-hole/docker-pi-hole/ and https://docs.pi-hole.net/
services:
  pihole:
    container_name: pihole
    image: pihole/pihole:latest
    ports:
      # DNS Ports
      - "53:53/tcp"
      - "53:53/udp"
      # Default HTTP Port
      - "80:80/tcp"
      # Default HTTPs Port. FTL will generate a self-signed certificate
      - "443:443/tcp"
      # Uncomment the line below if you are using Pi-hole as your DHCP server
      #- "67:67/udp"
      # Uncomment the line below if you are using Pi-hole as your NTP server
      #- "123:123/udp"
    environment:
      # Set the appropriate timezone for your location (https://en.wikipedia.org/wiki/List_of_tz_database_time_zones), e.g:
      TZ: 'Europe/London'
      # Set a password to access the web interface. Not setting one will result in a random password being assigned
      FTLCONF_webserver_api_password: 'correct horse battery staple'
      # If using Docker's default `bridge` network setting the dns listening mode should be set to 'ALL'
      FTLCONF_dns_listeningMode: 'ALL'
    # Volumes store your data between container upgrades
    volumes:
      # For persisting Pi-hole's databases and common configuration file
      - './etc-pihole:/etc/pihole'
      # 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'
      #- './etc-dnsmasq.d:/etc/dnsmasq.d'
    cap_add:
      # See https://docs.pi-hole.net/docker/#note-on-capabilities
      # Required if you are using Pi-hole as your DHCP server, else not needed
      - NET_ADMIN
      # Required if you are using Pi-hole as your NTP client to be able to set the host's system time
      - SYS_TIME
      # Optional, if Pi-hole should get some more processing time
      - SYS_NICE
    restart: unless-stopped
```

2. Run `docker compose up -d` to build and start pi-hole (Syntax may be `docker-compose` on older systems).

> [!NOTE]
> Volumes are recommended for persisting data across container re-creations for updating images.

### Automatic Ad List Updates

`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.

## Documentation

For more detailed information, please refer to our documentation:

- [Running DHCP from Docker Pi-Hole](https://docs.pi-hole.net/docker/DHCP/)
- [Configuration](https://docs.pi-hole.net/docker/configuration/)
- [Tips and Tricks](https://docs.pi-hole.net/docker/tips-and-tricks/)
- [Docker tags and versioning](https://docs.pi-hole.net/docker/#docker-tags-and-versioning)
- [Upgrading, Persistence, and Customizations](https://docs.pi-hole.net/docker/upgrading/)

## Docker tags and versioning

The 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.

The 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.

Release notes will always contain full details of changes in the container, including changes to core Pi-hole components.

| tag | description |
| :--- | :--- |
| `latest` | Always the latest release |
| `2022.04.0` | Date-based release |
| `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 |

## User Feedback

Please 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)


================================================
FILE: build.sh
================================================
#!/bin/bash

# Usage function
usage() {
    echo "Usage: $0 [-l] [-f <ftl_branch>] [-c <core_branch>] [-w <web_branch>] [-t <tag>] [use_cache]"
    echo "Options:"
    echo "  -f,  --ftlbranch <branch>     Specify FTL branch (cannot be used in conjunction with -l)"
    echo "  -c,  --corebranch <branch>    Specify Core branch"
    echo "  -w,  --webbranch <branch>     Specify Web branch"
    echo "  -p,  --paddbranch <branch>    Specify PADD branch"
    echo "  -t,  --tag <tag>              Specify Docker image tag (default: pihole:local)"
    echo "  -l,  --local                  Use locally built FTL binary (requires src/pihole-FTL file)"
    echo "  use_cache                     Enable caching (by default --no-cache is used)"
    echo ""
    echo "If no options are specified, the following command will be executed:"
    echo "  docker buildx build src/. --tag pihole:local --load --no-cache"
    exit 1
}

# Set default values
TAG="pihole:local"
DOCKER_BUILD_CMD="docker buildx build src/. --tag ${TAG} --load --no-cache"
FTL_FLAG=false
CORE_FORK="pi-hole"
WEB_FORK="pi-hole"
PADD_FORK="pi-hole"

# Check if buildx is installed
docker buildx version >/dev/null 2>&1
if [ $? -ne 0 ]; then
    echo "Error: Docker buildx is required to build this image. For installation instructions, see:"
    echo "       https://github.com/docker/buildx#installing"
    exit 1
fi

# Function to check if a custom branch entered by the user is valid
check_branch_exists() {
    local repo=$1
    local branch=$2
    local fork=$3
    local url

    if [ "$repo" == "ftl" ]; then
        # Special case for FTL - we check for the binary instead of just the branch - in case it is not yet built.
        url="https://ftl.pi-hole.net/${branch}/pihole-FTL-amd64"
    else
        url="https://github.com/${fork}/${repo}/blob/${branch}/README.md"
    fi

    local http_code
    http_code=$(curl -sI "$url" -o /dev/null -w "%{http_code}")
    if [ "${http_code}" -ne 200 ]; then
        echo "Error: $repo branch '$branch' not found. Exiting."
        exit 1
    fi
}

# Parse command line arguments
while [[ $# -gt 0 ]]; do
    key="$1"

    case $key in
    -l | --local)
        if [ ! -f "src/pihole-FTL" ]; then
            echo "File 'src/pihole-FTL' not found. Exiting."
            exit 1
        fi
        if [ "$FTL_FLAG" = true ]; then
            echo "Error: Both -l and -f cannot be used together."
            usage
        fi
        FTL_FLAG=true
        DOCKER_BUILD_CMD+=" --build-arg FTL_SOURCE=local"
        shift
        ;;
    -f | --ftlbranch)
        if [ "$FTL_FLAG" = true ]; then
            echo "Error: Both -l and -f cannot be used together."
            usage
        fi
        FTL_FLAG=true
        FTL_BRANCH="$2"
        check_branch_exists "ftl" "$FTL_BRANCH"
        DOCKER_BUILD_CMD+=" --build-arg FTL_BRANCH=$FTL_BRANCH"
        shift
        shift
        ;;
    -c | --corebranch)
        CORE_BRANCH="$2"
        check_branch_exists "pi-hole" "$CORE_BRANCH" "$CORE_FORK"
        DOCKER_BUILD_CMD+=" --build-arg CORE_BRANCH=$CORE_BRANCH"
        shift
        shift
        ;;
    -w | --webbranch)
        WEB_BRANCH="$2"
        check_branch_exists "web" "$WEB_BRANCH" "$WEB_FORK"
        DOCKER_BUILD_CMD+=" --build-arg WEB_BRANCH=$WEB_BRANCH"
        shift
        shift
        ;;
    -p | --paddbranch)
        PADD_BRANCH="$2"
        check_branch_exists "padd" "$PADD_BRANCH"
        DOCKER_BUILD_CMD+=" --build-arg PADD_BRANCH=$PADD_BRANCH"
        shift
        shift
        ;;
    -cf | --corefork)
        CORE_FORK="$2"
        DOCKER_BUILD_CMD+=" --build-arg CORE_FORK=$CORE_FORK"
        shift
        shift
        ;;
    -wf | --webfork)
        WEB_FORK="$2"
        DOCKER_BUILD_CMD+=" --build-arg WEB_FORK=$WEB_FORK"
        shift
        shift
        ;;
    -pf | --paddfork)
        PADD_FORK="$2"
        DOCKER_BUILD_CMD+=" --build-arg PADD_FORK=$PADD_FORK"
        shift
        shift
        ;;
    -t | --tag)
        CUSTOM_TAG="$2"
        DOCKER_BUILD_CMD=${DOCKER_BUILD_CMD/$TAG/$CUSTOM_TAG}
        TAG=$CUSTOM_TAG
        shift
        shift
        ;;
    use_cache)
        DOCKER_BUILD_CMD=${DOCKER_BUILD_CMD/--no-cache/}
        shift
        ;;
    *)
        echo "Unknown option: $1"
        usage
        ;;
    esac
done

# Execute the docker build command
echo "Executing command: $DOCKER_BUILD_CMD"
eval "${DOCKER_BUILD_CMD}"

# Check exit code of previous command
if [ $? -ne 0 ]; then
    echo ""
    echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
    echo "!! ERROR: Docker build failed, please review logs above !!"
    echo "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
    exit 1
else
    echo ""
    echo "Successfully built Docker image with tag '$TAG'"
    docker images "${TAG}"
fi


================================================
FILE: examples/Caddyfile
================================================
pihole-dev.lab {
  tls internal
  redir / /admin
  reverse_proxy pihole:8081
}


================================================
FILE: examples/docker-compose-caddy-proxy.yml
================================================
services:

  # Caddy example derived from Caddy's own example at https://hub.docker.com/_/caddy
  caddy:
    container_name: caddy
    image: caddy:latest
    networks:
      - caddy-net # Network exclusively for Caddy-proxied containers
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp" # QUIC protocol support: https://www.chromium.org/quic/
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile # config file on host in same directory as docker-compose.yml for easy editing.
      #- $PWD/site:/srv  # Only use if you are serving a website behind caddy
      - caddy_data:/data # Use docker volumes here bc no need to access these files from host
      - caddy_config:/config # Use docker volumes here bc no need to access these files from host

  # More info at https://github.com/pi-hole/docker-pi-hole/ and https://docs.pi-hole.net/
  pihole:
    depends_on:
      - caddy
    container_name: pihole
    image: pihole/pihole:latest
    ports:
      - "8081:80/tcp" # Pi-hole web admin interface, proxied through Caddy (configure port in Caddyfile)
      # Following are NOT proxied through Caddy, bound to host net instead:
      - "53:53/udp"
      - "53:53/tcp"
      #- "67:67/udp" # DHCP, if desired. If not bound to host net you need an mDNS proxy service configured somewhere on host net.
        # ref: https://docs.pi-hole.net/docker/DHCP/
    environment:
      # Set the appropriate timezone for your location (https://en.wikipedia.org/wiki/List_of_tz_database_time_zones), e.g:
      TZ: 'Europe/London'
      # Set a password to access the web interface. Not setting one will result in a random password being assigned
      FTLCONF_webserver_api_password: 'correct horse battery staple'
    # Volumes store your data between container upgrades
    volumes:
      # For persisting Pi-hole's databases and common configuration file
      - './etc-pihole:/etc/pihole'
      # Uncomment the below if you have custom dnsmasq config files that you want to persist. Not needed for most.
      #- './etc-dnsmasq.d:/etc/dnsmasq.d'
    cap_add:
      # See https://github.com/pi-hole/docker-pi-hole#note-on-capabilities
      # Required if you are using Pi-hole as your DHCP server, else not needed
      - NET_ADMIN
    restart: unless-stopped

# ref: https://hub.docker.com/_/caddy
networks:
  caddy-net:
    driver: bridge
    name: caddy-net

# ref: https://hub.docker.com/_/caddy
volumes:
  caddy_data:
    external: true # May need to create volume with 'docker volume create caddy_data'
  caddy_config:


================================================
FILE: src/.dockerignore
================================================
**/*.sw*
.tox
.git
**/__pycache__
.pipenv


================================================
FILE: src/Dockerfile
================================================
# syntax=docker/dockerfile:1
ARG FTL_SOURCE=remote
# Pull Stable images
FROM alpine:3.23.3@sha256:25109184c71bdad752c8312a8623239686a9a2071e8825f20acb8f2198c3f659 AS base

ARG TARGETPLATFORM
ARG WEB_BRANCH="development"
ARG CORE_BRANCH="development"
ARG FTL_BRANCH="development"
ARG PIHOLE_DOCKER_TAG="dev-localbuild"
ARG PADD_BRANCH="development"

ARG CORE_FORK="pi-hole"
ARG WEB_FORK="pi-hole"
ARG PADD_FORK="pi-hole"

ARG PIHOLE_UID=1000
ARG PIHOLE_GID=1000

ENV DNSMASQ_USER=pihole
ENV FTL_CMD=no-daemon

RUN apk add --no-cache \
    bash \
    bash-completion \
    bind-tools \
    binutils \
    coreutils \
    curl \
    git \
    # Install grep to avoid issues in pihole -w/b with the default busybox grep
    grep \
    iproute2 \
    jq \
    libcap \
    logrotate \
    ncurses \
    procps-ng \
    psmisc \
    shadow \
    sudo \
    tzdata \
    unzip \
    wget

# For nightly images, we install gdb and screen for ease of debugging (this is
# not included in the default image to keep it small), and also prepare the
# system for a core dump. Furthermore, we already add the required signal
# instructions to the gdb config file
RUN if [ "${PIHOLE_DOCKER_TAG}" = "nightly" ]; then \
    apk add --no-cache gdb screen && \
    echo "ulimit -c unlimited" >> /etc/profile && \
    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; \
    fi

ADD https://ftl.pi-hole.net/macvendor.db /macvendor.db
COPY crontab.txt /crontab.txt

# Add PADD to the container, too.
ADD --chmod=0755 https://raw.githubusercontent.com/${PADD_FORK}/PADD/${PADD_BRANCH}/padd.sh /usr/local/bin/padd

# download a the main repos from github
# if the branch is master we clone the latest tag as sometimes the master branch contains meta changes that have not been tagged
# (we need to create a new "master" branch to avoid the "detached HEAD" state for the version check to work correctly)

RUN clone_repo() { \
        FORK="$1"; \
        REPO="$2"; \
        BRANCH="$3"; \
        DEST="$4"; \
        CLONE_BRANCH="$BRANCH"; \
        if [ "$BRANCH" = "master" ]; then \
            CLONE_BRANCH=$(curl -s https://api.github.com/repos/${FORK}/${REPO}/releases/latest | jq -r .tag_name); \
        fi; \
        git clone --branch "$CLONE_BRANCH" --single-branch --depth 1 "https://github.com/${FORK}/${REPO}.git" "$DEST"; \
        cd "$DEST"; \
        if [ "$BRANCH" = "master" ]; then git checkout -b master; fi; \
    }; \
    clone_repo "${WEB_FORK}" "web" "${WEB_BRANCH}" "/var/www/html/admin"; \
    clone_repo "${CORE_FORK}" "pi-hole" "${CORE_BRANCH}" "/etc/.pihole"


RUN cd /etc/.pihole && \
    install -Dm755 -d /opt/pihole && \
    install -Dm755 -t /opt/pihole gravity.sh && \
    install -Dm755 -t /opt/pihole ./advanced/Scripts/*.sh && \
    install -Dm755 -t /opt/pihole ./advanced/Scripts/COL_TABLE && \
    install -Dm755 -d /etc/pihole && \
    install -Dm644 -t /etc/pihole ./advanced/Templates/logrotate && \
    install -Dm755 -d /var/log/pihole && \
    install -Dm755 -d /var/lib/logrotate && \
    install -Dm755 -t /usr/local/bin pihole && \
    install -Dm644 ./advanced/bash-completion/pihole.bash /etc/bash_completion.d/pihole && \
    install -Dm644 ./advanced/bash-completion/pihole-ftl.bash /etc/bash_completion.d/pihole-FTL && \
    install -T -m 0755 ./advanced/Templates/pihole-FTL-prestart.sh /opt/pihole/pihole-FTL-prestart.sh && \
    install -T -m 0755 ./advanced/Templates/pihole-FTL-poststop.sh /opt/pihole/pihole-FTL-poststop.sh && \
    addgroup -S pihole -g ${PIHOLE_GID} && adduser -S pihole -G pihole -u ${PIHOLE_UID} && \
    echo "${PIHOLE_DOCKER_TAG}" > /pihole.docker.tag

COPY --chmod=0755 bash_functions.sh /usr/bin/bash_functions.sh
COPY --chmod=0755 start.sh /usr/bin/start.sh

EXPOSE 53 53/udp
EXPOSE 67/udp
EXPOSE 80
EXPOSE 123/udp
EXPOSE 443

## 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

FROM base AS remote-ftl-install
# Default stage if FTL_SOURCE is not explicitly set to "local"
# Download the latest version of pihole-FTL for the correct architecture
RUN if   [ "$TARGETPLATFORM" = "linux/amd64" ];    then FTLARCH=amd64; \
    elif [ "$TARGETPLATFORM" = "linux/386" ];      then FTLARCH=386; \
    elif [ "$TARGETPLATFORM" = "linux/arm/v6" ];   then FTLARCH=armv6; \
    elif [ "$TARGETPLATFORM" = "linux/arm/v7" ];   then FTLARCH=armv7; \
    # Note for the future, "linux/arm6/v8" is not a valid value for TARGETPLATFORM, despite the CI platform name being that.
    elif [ "$TARGETPLATFORM" = "linux/arm64" ];    then FTLARCH=arm64; \
    elif [ "$TARGETPLATFORM" = "linux/riscv64" ];  then FTLARCH=riscv64; \
    else FTLARCH=amd64; fi \
    && echo "Arch: ${TARGETPLATFORM}, FTLARCH: ${FTLARCH}" \
    && 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 \
    && curl -sSL "${URL}/pihole-FTL-${FTLARCH}" -o /usr/bin/pihole-FTL \
    && chmod +x /usr/bin/pihole-FTL \
    && readelf -h /usr/bin/pihole-FTL || (echo "Error with downloaded FTL binary" && exit 1) \
    && /usr/bin/pihole-FTL  -vv

FROM base AS local-ftl-install
# pihole-FTL must be built from source and copied to the src directory first!
COPY --chmod=0755 pihole-FTL /usr/bin/pihole-FTL
RUN  readelf -h /usr/bin/pihole-FTL || (echo "Error with local FTL binary" && exit 1)

# Use the appropriate FTL Install stage based on the FTL_SOURCE build-arg
FROM ${FTL_SOURCE}-ftl-install AS final

HEALTHCHECK CMD dig -p $(pihole-FTL --config dns.port) +short +norecurse +retry=0 @127.0.0.1 pi.hole || exit 1

ENTRYPOINT ["start.sh"]


================================================
FILE: src/bash_functions.sh
================================================
#!/bin/bash

# Some of the bash_functions use utilities from Pi-hole's utils.sh
# shellcheck disable=SC2154
# shellcheck source=/dev/null
# . /opt/pihole/utils.sh

#######################
# returns value from FTLs config file using pihole-FTL --config
#
# Takes one argument: key
# Example getFTLConfigValue dns.piholePTR
#######################
getFTLConfigValue() {
    pihole-FTL --config -q "${1}"
}

#######################
# sets value in FTLs config file using pihole-FTL --config
#
# Takes two arguments: key and value
# Example setFTLConfigValue dns.piholePTR PI.HOLE
#
# Note, for complex values such as dns.upstreams, you should wrap the value in single quotes:
# setFTLConfigValue dns.upstreams '[ "8.8.8.8" , "8.8.4.4" ]'
#######################
setFTLConfigValue() {
    pihole-FTL --config "${1}" "${2}" >/dev/null
}

set_uid_gid() {

    echo "  [i] Setting up user & group for the pihole user"

    currentUid=$(id -u pihole)

    # If PIHOLE_UID is set, modify the pihole group's id to match
    if [ -n "${PIHOLE_UID}" ]; then
        if [[ ${currentUid} -ne ${PIHOLE_UID} ]]; then
            echo "  [i] Changing ID for user: pihole (${currentUid} => ${PIHOLE_UID})"
            usermod -o -u "${PIHOLE_UID}" pihole
        else
            echo "  [i] ID for user pihole is already ${PIHOLE_UID}, no need to change"
        fi
    else
        echo "  [i] PIHOLE_UID not set in environment, using default (${currentUid})"
    fi

    currentGid=$(id -g pihole)

    # If PIHOLE_GID is set, modify the pihole group's id to match
    if [ -n "${PIHOLE_GID}" ]; then
        if [[ ${currentGid} -ne ${PIHOLE_GID} ]]; then
            echo "  [i] Changing ID for group: pihole (${currentGid} => ${PIHOLE_GID})"
            groupmod -o -g "${PIHOLE_GID}" pihole
        else
            echo "  [i] ID for group pihole is already ${PIHOLE_GID}, no need to change"
        fi
    else
        echo "  [i] PIHOLE_GID not set in environment, using default (${currentGid})"
    fi
    echo ""
}

install_additional_packages() {
    if [ -n "${ADDITIONAL_PACKAGES}" ]; then
        echo "  [i] Additional packages requested: ${ADDITIONAL_PACKAGES}"
        echo "  [i] Fetching APK repository metadata."
        if ! apk update; then
            echo "  [i] Failed to fetch APK repository metadata."
        else
            echo "  [i] Installing additional packages: ${ADDITIONAL_PACKAGES}."
            # shellcheck disable=SC2086
            if ! apk add --no-cache ${ADDITIONAL_PACKAGES}; then
                echo "  [i] Failed to install additional packages."
            fi
        fi
        echo ""
    fi
}

start_cron() {
    echo "  [i] Starting crond for scheduled scripts. Randomizing times for gravity and update checker"
    # Randomize gravity update time
    sed -i "s/59 1 /$((1 + RANDOM % 58)) $((3 + RANDOM % 2))/" /crontab.txt
    # Randomize update checker time
    sed -i "s/59 17/$((1 + RANDOM % 58)) $((12 + RANDOM % 8))/" /crontab.txt
    /usr/bin/crontab /crontab.txt

    /usr/sbin/crond
    echo ""
}

install_logrotate() {
    # Install the logrotate config file - this is done already in Dockerfile
    # but if a user has mounted a volume over /etc/pihole, it will have been lost
    # pihole-FTL-prestart.sh will set the ownership of the file to root:root
    echo "  [i] Ensuring logrotate script exists in /etc/pihole"
    install -Dm644 -t /etc/pihole /etc/.pihole/advanced/Templates/logrotate
    echo ""
}

migrate_gravity() {
    echo "  [i] Gravity migration checks"
    gravityDBfile=$(getFTLConfigValue files.gravity)

    if [[ ! -f /etc/pihole/adlists.list ]]; then
        echo "  [i] No adlist file found, creating one with a default blocklist"
        echo "https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts" >/etc/pihole/adlists.list
    fi

    if [ ! -f "${gravityDBfile}" ]; then
        echo "  [i] ${gravityDBfile} does not exist (Likely due to a fresh volume). This is a required file for Pi-hole to operate."
        echo "  [i] Gravity will now be run to create the database"
        pihole -g
    else
        echo "  [i] Existing gravity database found - schema will be upgraded if necessary"
        # source the migration script and run the upgrade function
        source /etc/.pihole/advanced/Scripts/database_migration/gravity-db.sh
        local upgradeOutput
        upgradeOutput=$(upgrade_gravityDB "${gravityDBfile}" "/etc/pihole")
        printf "%b" "${upgradeOutput}\\n" | sed 's/^/     /'
    fi
    echo ""
}

# shellcheck disable=SC2034
ftl_config() {

    # Force a check of pihole-FTL --config, this will read any environment variables and set them in the config file
    # suppress the output as we don't need to see the default values.
    getFTLConfigValue >/dev/null

    # If FTLCONF_files_macvendor is not set
    if [[ -z "${FTLCONF_files_macvendor:-}" ]]; then
        # User is not passing in a custom location - so force FTL to use the file we moved to / during the build
        setFTLConfigValue "files.macvendor" "/macvendor.db"
        chown pihole:pihole /macvendor.db
    fi

    # If getFTLConfigValue "dns.upstreams" returns [], default to Google's DNS server
    if [[ $(getFTLConfigValue "dns.upstreams") == "[]" ]]; then
        echo "  [i] No DNS upstream set in environment or config file, defaulting to Google DNS"
        setFTLConfigValue "dns.upstreams" "[\"8.8.8.8\", \"8.8.4.4\"]"
    fi

    setup_web_password
}

migrate_v5_configs() {
    # Previously, Pi-hole created a number of files in /etc/dnsmasq.d
    # During migration, their content is copied into the new single source of
    # truth file /etc/pihole/pihole.toml and the old files are moved away to
    # avoid conflicts with other services on this system
    echo "  [i] Migrating dnsmasq configuration files"
    V6_CONF_MIGRATION_DIR="/etc/pihole/migration_backup_v6"
    # Create target directory and make it owned by pihole:pihole
    mkdir -p "${V6_CONF_MIGRATION_DIR}"
    chown pihole:pihole "${V6_CONF_MIGRATION_DIR}"

    # Move all conf files originally created by Pi-hole into this directory
    # - 01-pihole.conf
    # - 02-pihole-dhcp.conf
    # - 04-pihole-static-dhcp.conf
    # - 05-pihole-custom-cname.conf
    # - 06-rfc6761.conf

    mv /etc/dnsmasq.d/0{1,2,4,5}-pihole*.conf "${V6_CONF_MIGRATION_DIR}/" 2>/dev/null || true
    mv /etc/dnsmasq.d/06-rfc6761.conf "${V6_CONF_MIGRATION_DIR}/" 2>/dev/null || true
    echo ""

    # Finally, after everything is in place, we can create the new config file
    # /etc/pihole/pihole.toml
    # This file will be created with the default settings unless the user has
    # changed settings via setupVars.conf or the other dnsmasq files moved above
    # During migration, setupVars.conf is moved to /etc/pihole/migration_backup_v6
    local FTLoutput
    FTLoutput=$(pihole-FTL migrate v6)

    # Print the output of the FTL migration prefacing every line with six
    # spaces for alignment with other container output. Replace the first line to match the style of the other messages
    # We suppress the message about environment variables as these will be set on FTL's first real start
    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//'

    # Print a blank line for separation
    echo ""
}

setup_web_password() {
    if [ -z "${FTLCONF_webserver_api_password+x}" ] && [ -n "${WEBPASSWORD_FILE}" ] && [ -r "/run/secrets/${WEBPASSWORD_FILE}" ]; then
        echo "  [i] Setting FTLCONF_webserver_api_password from file"
        export FTLCONF_webserver_api_password=$(<"/run/secrets/${WEBPASSWORD_FILE}")
    fi

    # If FTLCONF_webserver_api_password is not set
    if [ -z "${FTLCONF_webserver_api_password+x}" ]; then
        # Is this already set to something other than blank (default) in FTL's config file? (maybe in a volume mount)
        if [[ $(pihole-FTL --config webserver.api.pwhash) ]]; then
            echo "  [i] Password already set in config file"
            return
        else
            # If we are here, the password is set in neither the environment nor the config file
            # We will generate a random password.
            RANDOMPASSWORD=$(tr -dc _A-Z-a-z-0-9 </dev/urandom | head -c 8)
            echo "  [i] No password set in environment or config file, assigning random password: $RANDOMPASSWORD"

            # Explicitly turn off bash printing when working with secrets
            { set +x; } 2>/dev/null

            pihole-FTL --config webserver.api.password "$RANDOMPASSWORD" >/dev/null

            # To avoid printing this if conditional in bash debug, turn off  debug above..
            # then re-enable debug if necessary (more code but cleaner printed output)
            if [ "${PH_VERBOSE:-0}" -gt 0 ]; then
                set -x
            fi
        fi
    else
        echo "  [i] Assigning password defined by Environment Variable"
    fi
}

fix_capabilities() {
    # Testing on Docker 20.10.14 with no caps set shows the following caps available to the container:
    # 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
    # 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:
    echo "  [i] Setting capabilities on pihole-FTL where possible"
    capsh --has-p=cap_chown 2>/dev/null && CAP_STR+=',CAP_CHOWN'
    capsh --has-p=cap_net_bind_service 2>/dev/null && CAP_STR+=',CAP_NET_BIND_SERVICE'
    capsh --has-p=cap_net_raw 2>/dev/null && CAP_STR+=',CAP_NET_RAW'
    capsh --has-p=cap_net_admin 2>/dev/null && CAP_STR+=',CAP_NET_ADMIN' || DHCP_READY='false'
    capsh --has-p=cap_sys_nice 2>/dev/null && CAP_STR+=',CAP_SYS_NICE'
    capsh --has-p=cap_sys_time 2>/dev/null && CAP_STR+=',CAP_SYS_TIME'

    if [[ ${CAP_STR} ]]; then
        # We have the (some of) the above caps available to us - apply them to pihole-FTL
        echo "  [i] Applying the following caps to pihole-FTL:"
        IFS=',' read -ra CAPS <<<"${CAP_STR:1}"
        for i in "${CAPS[@]}"; do
            echo "        * ${i}"
        done

        setcap "${CAP_STR:1}"+ep "$(which pihole-FTL)" || ret=$?

        if [[ $DHCP_READY == false ]] && [[ $FTLCONF_dhcp_active == true ]]; then
            # DHCP is requested but NET_ADMIN is not available.
            echo "ERROR: DHCP requested but NET_ADMIN is not available. DHCP will not be started."
            echo "      Please add cap_net_admin to the container's capabilities or disable DHCP."
            setFTLConfigValue dhcp.active false
        fi

        if [[ $ret -ne 0 && "${DNSMASQ_USER:-pihole}" != "root" ]]; then
            echo "  [!] ERROR: Unable to set capabilities for pihole-FTL. Cannot run as non-root."
            echo "            If you are seeing this error, please set the environment variable 'DNSMASQ_USER' to the value 'root'"
            exit 1
        fi
    else
        echo "  [!] ERROR: Unable to set capabilities for pihole-FTL."
        echo "            Please ensure that the container has the required capabilities."
        exit 1
    fi
    echo ""
}


================================================
FILE: src/crontab.txt
================================================
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
00 00 * * * PATH="$PATH:/usr/sbin:/usr/local/bin/" pihole flush once quiet
59 17 * * * PATH="$PATH:/usr/sbin:/usr/local/bin/" pihole updatechecker


================================================
FILE: src/start.sh
================================================
#!/bin/bash

if [ ! -x /bin/sh ]; then
    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.";
    exit 1;
fi

if [ "${PH_VERBOSE:-0}" -gt 0 ]; then
    set -x
fi

trap stop TERM INT QUIT HUP ERR

CAPSH_PID=""
TRAP_TRIGGERED=0

start() {

    # The below functions are all contained in bash_functions.sh
    # shellcheck source=/dev/null
    . /usr/bin/bash_functions.sh

    # If the file /etc/pihole/setupVars.conf exists, but /etc/pihole/pihole.toml does not, then we are migrating v5->v6
    # FTL Will handle the migration of the config files
    if [[ -f /etc/pihole/setupVars.conf && ! -f /etc/pihole/pihole.toml ]]; then
        echo "  [i] v5 files detected that have not yet been migrated to v6"
        echo ""
        migrate_v5_configs
    fi

    # ===========================
    # Initial checks
    # ===========================

    # If PIHOLE_UID is set, modify the pihole user's id to match
    set_uid_gid

    # Configure FTL with any environment variables if needed
    echo "  [i] Starting FTL configuration"
    ftl_config

    # Install additional packages inside the container if requested
    install_additional_packages

    # Start crond for scheduled scripts (logrotate, pihole flush, gravity update etc)
    start_cron

    # Install the logrotate config file
    install_logrotate

    #migrate Gravity Database if needed:
    migrate_gravity

    echo "  [i] pihole-FTL pre-start checks"
    # Run the post stop script to cleanup any remaining artifacts from a previous run
    sh /opt/pihole/pihole-FTL-poststop.sh

    fix_capabilities
    sh /opt/pihole/pihole-FTL-prestart.sh

    # Get the FTL log file path from the config
    FTLlogFile=$(getFTLConfigValue files.log.ftl)

    # Get the EOF position of the FTL log file so that we can tail from there later.
    local startFrom
    startFrom=$(stat -c%s "${FTLlogFile}")

    echo "  [i] Starting pihole-FTL ($FTL_CMD) as ${DNSMASQ_USER}"
    echo ""

    capsh --user="${DNSMASQ_USER}" --keep=1 -- -c "/usr/bin/pihole-FTL $FTL_CMD >/dev/null" &
    # Notes on above:
    # - DNSMASQ_USER default of pihole is in Dockerfile & can be overwritten by runtime container env
    # - /var/log/pihole/pihole*.log has FTL's output that no-daemon would normally print in FG too
    #   prevent duplicating it in docker logs by sending to dev null

    # We need the PID of the capsh process so that we can wait for it to finish
    CAPSH_PID=$!

    # Wait for FTL to start by monitoring the FTL log file for the "FTL started" line
    if ! timeout 30 tail -F -c +$((startFrom + 1)) -- "${FTLlogFile}" | grep -q '########## FTL started'; then
        echo "  [!] ERROR: Did not find 'FTL started' message in ${FTLlogFile} in 30 seconds, stopping container"
        exit 1
    fi

    pihole updatechecker
    local versionsOutput
    versionsOutput=$(pihole -v)
    echo "  [i] Version info:"
    printf "%b" "${versionsOutput}\\n" | sed 's/^/      /'
    echo ""

    if [ "${TAIL_FTL_LOG:-1}" -eq 1 ]; then
        # Start tailing the FTL log file from the EOF position we recorded on container start
        tail -F -c +$((startFrom + 1)) -- "${FTLlogFile}" &
    else
        echo "  [i] FTL log output is disabled. Remove the Environment variable TAIL_FTL_LOG, or set it to 1 to enable FTL log output."
    fi

    # Wait for the capsh process (which spawned FTL) to finish
    wait $CAPSH_PID
    FTL_EXIT_CODE=$?

    # If we are here, then FTL has exited.
    # If the trap was triggered, then stop will have already been called
    if [ $TRAP_TRIGGERED -eq 0 ]; then
        # Pass the exit code through to the stop function
        stop $FTL_EXIT_CODE
    fi
}

stop() {
    local FTL_EXIT_CODE=$1

    # 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
    if [ -z "${FTL_EXIT_CODE}" ]; then
        TRAP_TRIGGERED=1
        echo ""
        echo "  [i] Container stop requested..."
        echo "  [i] pihole-FTL is running - Attempting to shut it down cleanly"
        echo ""
        killall --signal 15 pihole-FTL

        wait $CAPSH_PID
        FTL_EXIT_CODE=$?
    fi

    # Wait for a few seconds to allow the FTL log tail to catch up before exiting the container
    sleep 2

    # ensure the exit code is an integer, if not set it to 1
    if ! [[ "${FTL_EXIT_CODE}" =~ ^[0-9]+$ ]]; then
        FTL_EXIT_CODE=1
    fi

    sh /opt/pihole/pihole-FTL-poststop.sh

    echo ""
    echo "  [i] pihole-FTL exited with status $FTL_EXIT_CODE"
    echo ""
    echo "  [i] Container will now stop or restart depending on your restart policy"
    echo "      https://docs.docker.com/engine/containers/start-containers-automatically/#use-a-restart-policy"
    echo ""

    exit "${FTL_EXIT_CODE}"

}

start


================================================
FILE: test/TESTING.md
================================================
# Prerequisites

Make sure you have `docker`, `python` and `tox` installed.

# Running tests locally

`tox -c test/tox.ini`

Should result in:

- An image named `pihole:CI_container` being built
- Tests being ran to confirm the image doesn't have any regressions


================================================
FILE: test/requirements.txt
================================================
pytest == 9.0.2
pytest-testinfra == 10.2.2
pytest-clarity == 1.0.1
tox == 4.35.0
# Not adding pytest-xdist as using pytest with n > 1 cores
# causes random issues with the emulated architectures


================================================
FILE: test/tests/__init__.py
================================================


================================================
FILE: test/tests/conftest.py
================================================
import pytest
import subprocess
import testinfra
import testinfra.backend.docker
import os


# Monkeypatch sh to bash, if they ever support non hard code /bin/sh this can go away
# https://github.com/pytest-dev/pytest-testinfra/blob/master/testinfra/backend/docker.py
def run_bash(self, command, *args, **kwargs):
    cmd = self.get_command(command, *args)
    if self.user is not None:
        out = self.run_local(
            "docker exec -u %s %s /bin/bash -c %s", self.user, self.name, cmd
        )
    else:
        out = self.run_local("docker exec %s /bin/bash -c %s", self.name, cmd)
    out.command = self.encode(cmd)
    return out


testinfra.backend.docker.DockerBackend.run = run_bash


# scope='session' uses the same container for all the tests;
# scope='function' uses a new container per test function.
@pytest.fixture(scope="function")
def docker(request):
    # Get platform from environment variable, default to None if not set
    platform = os.environ.get("CIPLATFORM")

    # build the docker run command with args
    cmd = ["docker", "run", "-d", "-t"]

    # Only add platform flag if CIPLATFORM is set
    if platform:
        cmd.extend(["--platform", platform])

    # Get env vars from parameterization
    env_vars = getattr(request, "param", [])
    if isinstance(env_vars, str):
        env_vars = [env_vars]

    # add parameterized environment variables
    for env_var in env_vars:
        cmd.extend(["-e", env_var])

    # add default TZ if not already set
    if not any("TZ=" in arg for arg in cmd):
        cmd.extend(["-e", 'TZ="Europe/London"'])

    # add the image name
    cmd.append("pihole:CI_container")

    # run a container
    docker_id = subprocess.check_output(cmd).decode().strip()
    # return a testinfra connection to the container
    yield testinfra.get_host("docker://" + docker_id)
    # at the end of the test suite, destroy the container
    subprocess.check_call(["docker", "rm", "-f", docker_id])


================================================
FILE: test/tests/test_bash_functions.py
================================================
import pytest


# Adding 5 seconds sleep to give the emulated architecture time to run
@pytest.mark.parametrize("docker", ["FTLCONF_webserver_port=999"], indirect=True)
def test_ftlconf_webserver_port(docker):
    func = docker.run("echo ${FTLCONF_webserver_port}")
    assert "999" in func.stdout
    func = docker.run("""
        sleep 5
        pihole-FTL --config webserver.port
        """)
    assert "999" in func.stdout


# Adding 5 seconds sleep to give the emulated architecture time to run
@pytest.mark.parametrize(
    "docker", ["FTLCONF_dns_upstreams=1.2.3.4;5.6.7.8#1234"], indirect=True
)
def test_ftlconf_dns_upstreams(docker):
    func = docker.run("echo ${FTLCONF_dns_upstreams}")
    assert "1.2.3.4;5.6.7.8#1234" in func.stdout
    func = docker.run("""
        sleep 5
        pihole-FTL --config dns.upstreams
        """)
    assert "[ 1.2.3.4, 5.6.7.8#1234 ]" in func.stdout


CMD_SETUP_WEB_PASSWORD = ". bash_functions.sh ; setup_web_password"


def test_random_password_assigned_fresh_start(docker):
    func = docker.run(CMD_SETUP_WEB_PASSWORD)
    assert "assigning random password:" in func.stdout


@pytest.mark.parametrize(
    "docker", ["FTLCONF_webserver_api_password=1234567890"], indirect=True
)
def test_password_set_by_envvar(docker):
    func = docker.run(CMD_SETUP_WEB_PASSWORD)
    assert "Assigning password defined by Environment Variable" in func.stdout


================================================
FILE: test/tests/test_general.py
================================================
import pytest
import os


# Adding 5 seconds sleep to give the emulated architecture time to run
@pytest.mark.parametrize("docker", ["PIHOLE_UID=456"], indirect=True)
def test_pihole_uid_env_var(docker):
    func = docker.run("echo ${PIHOLE_UID}")
    assert "456" in func.stdout
    func = docker.run("""
        sleep 5
        id -u pihole
        """)
    assert "456" in func.stdout


# Adding 5 seconds sleep to give the emulated architecture time to run
@pytest.mark.parametrize("docker", ["PIHOLE_GID=456"], indirect=True)
def test_pihole_gid_env_var(docker):
    func = docker.run("echo ${PIHOLE_GID}")
    assert "456" in func.stdout
    func = docker.run("""
        sleep 5
        id -g pihole
        """)
    assert "456" in func.stdout


def test_pihole_ftl_version(docker):
    func = docker.run("pihole-FTL -vv")
    assert func.rc == 0
    assert "Version:" in func.stdout


@pytest.mark.skipif(
    not os.environ.get("CIPLATFORM"),
    reason="CIPLATFORM environment variable not set, running locally",
)
def test_pihole_ftl_architecture(docker):
    func = docker.run("pihole-FTL -vv")
    assert func.rc == 0
    assert "Architecture:" in func.stdout
    # Get the expected architecture from CIPLATFORM environment variable
    platform = os.environ.get("CIPLATFORM")
    assert platform in func.stdout


# Wait for FTL to start up, then stop the container gracefully
# Finally, check the container logs to see if FTL was shut down cleanly
def test_pihole_ftl_starts_and_shuts_down_cleanly(docker):
    import subprocess
    import time

    # Get the container ID from the docker fixture
    container_id = docker.backend.name

    # Wait for FTL to fully start up by checking logs
    max_wait_time = 60  # Maximum wait time in seconds
    start_time = time.time()
    ftl_started = False

    while time.time() - start_time < max_wait_time:
        result = subprocess.run(
            ["docker", "logs", container_id], capture_output=True, text=True
        )

        if "########## FTL started" in result.stdout:
            ftl_started = True
            break

        time.sleep(1)  # Check every second

    assert ftl_started, f"FTL did not start within {max_wait_time} seconds"

    # Stop the container gracefully (sends SIGTERM)
    subprocess.run(["docker", "stop", container_id], check=True)

    # Get the container logs
    result = subprocess.run(
        ["docker", "logs", container_id], capture_output=True, text=True
    )

    # Check for clean shutdown messages in the logs
    assert "INFO: ########## FTL terminated after" in result.stdout
    assert "(code 0)" in result.stdout


def test_cronfile_valid(docker):
    func = docker.run("""
        /usr/bin/crontab /crontab.txt
        crond -d 8 -L /cron.log
        grep 'parse error' /cron.log
    """)
    assert "parse error" not in func.stdout


================================================
FILE: test/tox.ini
================================================
[tox]
envlist = py3

[testenv:py3]
allowlist_externals = docker
deps = -rrequirements.txt
passenv = CIPLATFORM
setenv =
    COLUMNS=120
    PY_COLORS=1
commands =  # Build the Docker image for testing depending on the architecture, fall back to 'local' if not set
            # This allows us to run the tests on the host architecture if not on CI
            docker buildx build --load --platform={env:CIPLATFORM:local} --progress plain -f ../src/Dockerfile -t pihole:CI_container ../src/
            # run the tests
            # # Not using > 1 cores as it causes random issues with the emulated architectures
            pytest {posargs:-vv} ./tests/
Download .txt
gitextract_w4fbfg1b/

├── .codespellignore
├── .editorconfig
├── .github/
│   ├── CODEOWNERS
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   └── config.yml
│   ├── PULL_REQUEST_TEMPLATE.md
│   ├── dco.yml
│   ├── dependabot.yml
│   ├── release.yml
│   └── workflows/
│       ├── build-and-publish.yml
│       ├── build-and-test.yml
│       ├── codespell.yml
│       ├── dockerhub-description.yml
│       ├── editorconfig.yml
│       ├── housekeeping.yml
│       ├── merge-conflict.yml
│       ├── stale.yml
│       ├── stale_pr.yml
│       └── sync-back-to-dev.yml
├── .gitignore
├── .gitmodules
├── CHANGELOG.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── build.sh
├── examples/
│   ├── Caddyfile
│   └── docker-compose-caddy-proxy.yml
├── src/
│   ├── .dockerignore
│   ├── Dockerfile
│   ├── bash_functions.sh
│   ├── crontab.txt
│   └── start.sh
└── test/
    ├── TESTING.md
    ├── requirements.txt
    ├── tests/
    │   ├── __init__.py
    │   ├── conftest.py
    │   ├── test_bash_functions.py
    │   └── test_general.py
    └── tox.ini
Download .txt
SYMBOL INDEX (12 symbols across 3 files)

FILE: test/tests/conftest.py
  function run_bash (line 10) | def run_bash(self, command, *args, **kwargs):
  function docker (line 28) | def docker(request):

FILE: test/tests/test_bash_functions.py
  function test_ftlconf_webserver_port (line 6) | def test_ftlconf_webserver_port(docker):
  function test_ftlconf_dns_upstreams (line 20) | def test_ftlconf_dns_upstreams(docker):
  function test_random_password_assigned_fresh_start (line 33) | def test_random_password_assigned_fresh_start(docker):
  function test_password_set_by_envvar (line 41) | def test_password_set_by_envvar(docker):

FILE: test/tests/test_general.py
  function test_pihole_uid_env_var (line 7) | def test_pihole_uid_env_var(docker):
  function test_pihole_gid_env_var (line 19) | def test_pihole_gid_env_var(docker):
  function test_pihole_ftl_version (line 29) | def test_pihole_ftl_version(docker):
  function test_pihole_ftl_architecture (line 39) | def test_pihole_ftl_architecture(docker):
  function test_pihole_ftl_starts_and_shuts_down_cleanly (line 50) | def test_pihole_ftl_starts_and_shuts_down_cleanly(docker):
  function test_cronfile_valid (line 88) | def test_cronfile_valid(docker):
Condensed preview — 40 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (84K chars).
[
  {
    "path": ".codespellignore",
    "chars": 5,
    "preview": "padd\n"
  },
  {
    "path": ".editorconfig",
    "chars": 346,
    "preview": "# EditorConfig is awesome: https://editorconfig.org/\n\n# top-most EditorConfig file\nroot = true\n\n# Unix-style newlines wi"
  },
  {
    "path": ".github/CODEOWNERS",
    "chars": 306,
    "preview": "# see https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repositor"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 2635,
    "preview": "---\nname: Bug report\nabout: Create a report to help us improve\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n<!-- Provide a g"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "chars": 426,
    "preview": "blank_issues_enabled: false\ncontact_links:\n  - name: Questions and Configurations\n    url: https://discourse.pi-hole.net"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "chars": 1311,
    "preview": "`{Please select 'base: development' as target branch above! (you can delete this line)}`\n\n<!--- Provide a general summar"
  },
  {
    "path": ".github/dco.yml",
    "chars": 26,
    "preview": "require:\n  members: false\n"
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 600,
    "preview": "version: 2\nupdates:\n  # Maintain dependencies for GitHub Actions\n  - package-ecosystem: \"github-actions\"\n    directories"
  },
  {
    "path": ".github/release.yml",
    "chars": 127,
    "preview": "changelog:\n  exclude:\n    labels:\n      - internal\n      - dependencies\n    authors:\n      - dependabot\n      - github-a"
  },
  {
    "path": ".github/workflows/build-and-publish.yml",
    "chars": 5813,
    "preview": "name: Build Image and Publish\non:\n  schedule:\n    - cron: \"0 5 * * *\"\n  push:\n    branches:\n      - development\n  releas"
  },
  {
    "path": ".github/workflows/build-and-test.yml",
    "chars": 1402,
    "preview": "name: Build Image and Test\non:\n  pull_request:\n\npermissions:\n  contents: read\n\njobs:\n  build-and-test:\n    runs-on: ${{ "
  },
  {
    "path": ".github/workflows/codespell.yml",
    "chars": 537,
    "preview": "name: Codespell\non:\n  pull_request:\n    types: [opened, synchronize, reopened, ready_for_review]\n\npermissions:\n  content"
  },
  {
    "path": ".github/workflows/dockerhub-description.yml",
    "chars": 673,
    "preview": "name: Update Docker Hub Description\npermissions:\n  contents: read\non:\n  push:\n    branches:\n      - master\n    paths:\n  "
  },
  {
    "path": ".github/workflows/editorconfig.yml",
    "chars": 585,
    "preview": "name: Editorconfig-Checker\non:\n  pull_request:\n    types: [opened, synchronize, reopened, ready_for_review]\n\npermissions"
  },
  {
    "path": ".github/workflows/housekeeping.yml",
    "chars": 754,
    "preview": "name: Remove untagged images from registry\non:\n    workflow_dispatch:\n    schedule:\n        - cron: \"0 0 * * *\"\n\npermiss"
  },
  {
    "path": ".github/workflows/merge-conflict.yml",
    "chars": 915,
    "preview": "name: \"Check for merge conflicts\"\non:\n  # So that PRs touching the same files as the push are updated\n  push:\n  # So tha"
  },
  {
    "path": ".github/workflows/stale.yml",
    "chars": 1876,
    "preview": "name: Mark stale issues\n\non:\n  schedule:\n    - cron: '0 8 * * *'\n  workflow_dispatch:\n  issue_comment:\n\npermissions:\n  i"
  },
  {
    "path": ".github/workflows/stale_pr.yml",
    "chars": 1229,
    "preview": "name: Close stale PR\n# This action will add a `stale` label and close immediately every PR that meets the following cond"
  },
  {
    "path": ".github/workflows/sync-back-to-dev.yml",
    "chars": 568,
    "preview": "name: Sync Back to Development\n\non:\n  push:\n    branches:\n      - master\n\npermissions:\n  contents: write\n  pull-requests"
  },
  {
    "path": ".gitignore",
    "chars": 254,
    "preview": "*.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\net"
  },
  {
    "path": ".gitmodules",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "CHANGELOG.md",
    "chars": 410,
    "preview": "# Docker Pi-Hole changelog\n\nNotes about releases will be documented on [docker-pi-hole's github releases page](https://g"
  },
  {
    "path": "CONTRIBUTING.md",
    "chars": 563,
    "preview": "# Pull Request Guidelines\n\nPlease review the following before opening a pull request (PR) to help your PR go smoothly:\n\n"
  },
  {
    "path": "LICENSE",
    "chars": 14202,
    "preview": "Copyright (C) 2017 Pi-hole, LLC (https://pi-hole.net)\nPi-hole Core\n\nThis software is licensed under the European Union P"
  },
  {
    "path": "README.md",
    "chars": 6188,
    "preview": "# Docker Pi-hole\n\n[![Build Status](https://github.com/pi-hole/docker-pi-hole/workflows/Build%20Image%20and%20Test/badge."
  },
  {
    "path": "build.sh",
    "chars": 4810,
    "preview": "#!/bin/bash\n\n# Usage function\nusage() {\n    echo \"Usage: $0 [-l] [-f <ftl_branch>] [-c <core_branch>] [-w <web_branch>] "
  },
  {
    "path": "examples/Caddyfile",
    "chars": 79,
    "preview": "pihole-dev.lab {\n  tls internal\n  redir / /admin\n  reverse_proxy pihole:8081\n}\n"
  },
  {
    "path": "examples/docker-compose-caddy-proxy.yml",
    "chars": 2564,
    "preview": "services:\n\n  # Caddy example derived from Caddy's own example at https://hub.docker.com/_/caddy\n  caddy:\n    container_n"
  },
  {
    "path": "src/.dockerignore",
    "chars": 42,
    "preview": "**/*.sw*\n.tox\n.git\n**/__pycache__\n.pipenv\n"
  },
  {
    "path": "src/Dockerfile",
    "chars": 5791,
    "preview": "# syntax=docker/dockerfile:1\nARG FTL_SOURCE=remote\n# Pull Stable images\nFROM alpine:3.23.3@sha256:25109184c71bdad752c831"
  },
  {
    "path": "src/bash_functions.sh",
    "chars": 11363,
    "preview": "#!/bin/bash\n\n# Some of the bash_functions use utilities from Pi-hole's utils.sh\n# shellcheck disable=SC2154\n# shellcheck"
  },
  {
    "path": "src/crontab.txt",
    "chars": 309,
    "preview": "59 1  * * 0 PATH=\"$PATH:/usr/sbin:/usr/local/bin/\" pihole updateGravity >/var/log/pihole/pihole_updateGravity.log || cat"
  },
  {
    "path": "src/start.sh",
    "chars": 4851,
    "preview": "#!/bin/bash\n\nif [ ! -x /bin/sh ]; then\n    echo \"Executable test for /bin/sh failed. Your Docker version is too old to r"
  },
  {
    "path": "test/TESTING.md",
    "chars": 263,
    "preview": "# Prerequisites\n\nMake sure you have `docker`, `python` and `tox` installed.\n\n# Running tests locally\n\n`tox -c test/tox.i"
  },
  {
    "path": "test/requirements.txt",
    "chars": 195,
    "preview": "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 pyte"
  },
  {
    "path": "test/tests/__init__.py",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "test/tests/conftest.py",
    "chars": 1966,
    "preview": "import pytest\nimport subprocess\nimport testinfra\nimport testinfra.backend.docker\nimport os\n\n\n# Monkeypatch sh to bash, i"
  },
  {
    "path": "test/tests/test_bash_functions.py",
    "chars": 1399,
    "preview": "import pytest\n\n\n# Adding 5 seconds sleep to give the emulated architecture time to run\n@pytest.mark.parametrize(\"docker\""
  },
  {
    "path": "test/tests/test_general.py",
    "chars": 2850,
    "preview": "import pytest\nimport os\n\n\n# Adding 5 seconds sleep to give the emulated architecture time to run\n@pytest.mark.parametriz"
  },
  {
    "path": "test/tox.ini",
    "chars": 655,
    "preview": "[tox]\nenvlist = py3\n\n[testenv:py3]\nallowlist_externals = docker\ndeps = -rrequirements.txt\npassenv = CIPLATFORM\nsetenv =\n"
  }
]

About this extraction

This page contains the full source code of the pi-hole/docker-pi-hole GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 40 files (77.0 KB), approximately 21.5k tokens, and a symbol index with 12 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!