├── .dockerignore ├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ ├── build.yml │ ├── cacheCleanup.yml │ ├── prepare-docker │ └── action.yml │ └── prepare-svn │ └── action.yml ├── .gitignore ├── Dockerfile-bookworm ├── Dockerfile-bullseye ├── Dockerfile-threaded-bookworm ├── Dockerfile-threaded-bullseye ├── LICENSE ├── README.md ├── docker-compose.yml ├── fhem-docker.code-workspace ├── migration.md ├── renovate.json5 ├── scripts ├── excluded_packages.txt ├── get-FHEMRepositorys.sh ├── get-Packages.pl ├── parse-METAJson.pl └── test-integration.sh └── src ├── FHEM └── 99_DockerImageInfo.pm ├── entry.sh ├── health-check.sh ├── ssh_known_hosts.txt └── tests └── bats ├── aptInstall.bats ├── entry.bats ├── health-check.bats ├── logfile.bats ├── network.bats ├── pidfile.bats └── setup_suite.bash /.dockerignore: -------------------------------------------------------------------------------- 1 | src/FHEM/trunk 2 | .github 3 | .vscode 4 | .cache 5 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at julian.pawlowski@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to the FHEM Docker image 2 | 3 | :+1::tada: First off, thanks for taking the time to contribute! :tada::+1: 4 | 5 | The following is a set of guidelines for contributing to the FHEM Docker image, which is hosted in the [FHEM Organization](https://github.com/fhem) on GitHub. These are mostly guidelines, not rules. Use your best judgment, and feel free to propose changes to this document in a pull request. 6 | 7 | #### Table Of Contents 8 | 9 | [Code of Conduct](CODE_OF_CONDUCT.md) 10 | [How to create a pull request](https://help.github.com/articles/creating-a-pull-request-from-a-fork/) 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fhem/fhem-docker/f0e2b1ce9df0e5cb1009fbda407b030fa63d5591/.github/PULL_REQUEST_TEMPLATE.md -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "docker" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | - package-ecosystem: "github-actions" # See documentation for possible values 13 | directory: "/" # Location of package manifests 14 | schedule: 15 | interval: "daily" 16 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: Build and Test 4 | 5 | # Controls when the action will run. 6 | on: 7 | # Triggers the workflow on push or pull request events but only for the dev branch 8 | push: 9 | branches: 10 | - "dev*" # Support wildcard matching 11 | - "master" 12 | 13 | pull_request: 14 | branches: [ dev ] 15 | 16 | release: 17 | types: 18 | - published 19 | - released 20 | 21 | # Allows you to run this workflow manually from the Actions tab 22 | workflow_dispatch: 23 | 24 | 25 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 26 | jobs: 27 | docker_lint: 28 | runs-on: ubuntu-latest 29 | steps: 30 | - name: Checkout this repository 31 | uses: actions/checkout@v4 32 | 33 | - name: Lint docker-compose.yml 34 | uses: sjafferali/docker-compose-lint-action@v0.1.2 35 | with: 36 | compose-file: './docker-compose.yml' 37 | 38 | 39 | 40 | get_dependencies: 41 | # Min Version for the prereq scanner to ignore core modules 42 | runs-on: ubuntu-latest 43 | steps: 44 | - name: Checkout this repository 45 | uses: actions/checkout@v4 46 | 47 | - name: Prepare SVN repository checkout and variables 48 | id: prepareSVN 49 | uses: ./.github/workflows/prepare-svn 50 | 51 | - name: Remove contrib from dependency scan 52 | run: | 53 | rm -r ./src/fhem/trunk/fhem/contrib || true 54 | 55 | - uses: shogo82148/actions-setup-perl@v1 56 | with: 57 | perl-version: "5.38" 58 | install-modules-with: cpanm 59 | install-modules-args: --notest 60 | install-modules: PPI Perl::PrereqScanner::NotQuiteLite File::Path File::Find::Rule List::MoreUtils CPAN::Meta Module::CPANfile CPAN::Meta::Merge Scalar::Util Module::CoreList 61 | 62 | - name: clone 3rdparty repositories at github 63 | run: | 64 | mapfile -t REPO_URLS < <( scripts/get-FHEMRepositorys.sh | awk '{print $4}' && printf '\0' ) 65 | mkdir ./3rdparty 66 | cd ./3rdparty 67 | printf "%s\n" "${REPO_URLS[@]}" | xargs -I {} -P3 sh -c 'echo "{}: $(basename $(dirname {}))/$(basename {})"; git clone --depth 1 "{}" "$(basename $(dirname {}))/$(basename {})"; ' 68 | 69 | - name: Init PPI Cache 70 | uses: actions/cache/restore@v4 71 | id: cache-ppi-restore 72 | with: 73 | path: .cache/PPI 74 | restore-keys: | 75 | PPI-SVN- 76 | key: PPI-SVN-${{ steps.prepareSVN.outputs.FHEM_REVISION_LATEST }} 77 | 78 | - name: "create private modules filter regex, to not install them from CPAN" 79 | run: | 80 | DYNAMIC_EXCLUDE=$(perl scripts/get-Packages.pl ./3rdparty ./src/fhem/trunk/fhem) 81 | STATIC_EXCLUDE=$(grep -v '^#' scripts/excluded_packages.txt | tr '\n' '|' | sed 's/^|//' | sed 's/|\{2,\}/|/g' | sed 's/.$//') 82 | echo "FHEM_MODULES=^(${STATIC_EXCLUDE}|${DYNAMIC_EXCLUDE})" >> "$GITHUB_ENV" 83 | 84 | - name: create cpanfile from local cloned 3rdparty repositories 85 | run: | 86 | scan-perl-prereqs-nqlite -save_cpanfile -exclude_core -suggests -private_re "$FHEM_MODULES" -ignore "fhem-streamdeck" ./3rdparty 87 | perl scripts/parse-METAJson.pl ./3rdparty 88 | echo "## 3rdparty cpanfile" >> $GITHUB_STEP_SUMMARY 89 | cat cpanfile >> $GITHUB_STEP_SUMMARY 90 | 91 | - name: Always Save ppi cache 92 | id: cache-ppi-save 93 | if: always() && steps.cache-ppi-restore.outputs.cache-hit != 'true' 94 | uses: actions/cache/save@v4 95 | with: 96 | key: ${{ steps.cache-ppi-restore.outputs.cache-primary-key }} 97 | path: .cache/PPI 98 | 99 | - uses: actions/upload-artifact@v4 100 | with: 101 | name: cpanfile-3rdParty 102 | path: cpanfile 103 | overwrite: true 104 | 105 | - name: create cpanfile from FHEM svn dependencies 106 | run: | 107 | rm cpanfile 108 | scan-perl-prereqs-nqlite -save_cpanfile -exclude_core -suggests -private_re "$FHEM_MODULES" ./src/fhem/trunk/fhem 109 | perl scripts/parse-METAJson.pl ./src/fhem/trunk/fhem 110 | echo "## FHEM svn cpanfile" >> $GITHUB_STEP_SUMMARY 111 | cat cpanfile >> $GITHUB_STEP_SUMMARY 112 | 113 | - uses: actions/upload-artifact@v4 114 | with: 115 | name: cpanfile-FHEM 116 | path: cpanfile 117 | overwrite: true 118 | 119 | base_build: 120 | strategy: 121 | matrix: 122 | dockerfile: [-bookworm, -threaded-bookworm] 123 | platform: [arm/v7, amd64, arm64, 386] 124 | runs-on: ubuntu-latest 125 | steps: 126 | - name: Checkout this repository 127 | uses: actions/checkout@v4 128 | 129 | - name: Prepare docker for build and publish 130 | id: prepareDOCKER 131 | uses: ./.github/workflows/prepare-docker 132 | with: 133 | DOCKER_HUB_USERNAME: ${{ secrets.DOCKER_HUB_USERNAME }} 134 | DOCKER_HUB_ACCESS_TOKEN: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} 135 | GHCR_OWNER: ${{ github.repository_owner }} 136 | GHCR_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} 137 | DOCKERFILE: ${{ matrix.dockerfile }} 138 | 139 | - name: Build for bats with fhem base layer ${{ matrix.platform }} 140 | uses: docker/build-push-action@v6 141 | with: 142 | context: . 143 | load: true 144 | file: ./Dockerfile${{ matrix.dockerfile }} 145 | platforms: linux/${{ matrix.platform }} 146 | push: false 147 | target: base 148 | cache-from: | 149 | type=gha,scope=base_linux/${{ matrix.platform }}${{ matrix.dockerfile }} 150 | cache-to: type=gha,mode=max,scope=base_linux/${{ matrix.platform }}${{ matrix.dockerfile }} 151 | tags: baseonly 152 | 153 | cpan_build: 154 | needs: [get_dependencies, base_build] 155 | runs-on: ubuntu-latest 156 | strategy: 157 | matrix: 158 | dockerfile: [-bookworm, -threaded-bookworm] 159 | platform: [arm/v7, arm64, 386] 160 | steps: 161 | - name: Checkout this repository 162 | uses: actions/checkout@v4 163 | with: 164 | fetch-depth: 0 165 | 166 | - name: Inject slug/short variables 167 | uses: rlespinasse/github-slug-action@v5.1.0 168 | 169 | - name: Get git vars 170 | shell: bash 171 | run: | 172 | echo "IMAGE_VERSION=$( git describe --tags --dirty --match "v[0-9]*")" >> $GITHUB_OUTPUT 173 | id: gitVars 174 | 175 | - name: Prepare SVN repository checkout and variables 176 | id: prepareSVN 177 | uses: ./.github/workflows/prepare-svn 178 | 179 | - uses: actions/download-artifact@v4 180 | with: 181 | name: cpanfile-FHEM 182 | 183 | - uses: actions/download-artifact@v4 184 | with: 185 | name: cpanfile-3rdParty 186 | path: 3rdParty 187 | 188 | - name: Prepare docker for build and publish 189 | id: prepareDOCKER 190 | uses: ./.github/workflows/prepare-docker 191 | with: 192 | DOCKER_HUB_USERNAME: ${{ secrets.DOCKER_HUB_USERNAME }} 193 | DOCKER_HUB_ACCESS_TOKEN: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} 194 | GHCR_OWNER: ${{ github.repository_owner }} 195 | GHCR_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} 196 | DOCKERFILE: ${{ matrix.dockerfile }} 197 | 198 | - name: Build cpan packages for ${{ matrix.platform }} 199 | uses: docker/build-push-action@v6 200 | with: 201 | context: . 202 | load: true 203 | file: ./Dockerfile${{ matrix.dockerfile }} 204 | platforms: linux/${{ matrix.platform }} 205 | push: false 206 | target: base-cpan 207 | cache-from: | 208 | type=gha,scope=base_linux/${{ matrix.platform }}${{ matrix.dockerfile }} 209 | type=gha,scope=base-cpan_linux/${{ matrix.platform }}${{ matrix.dockerfile }} 210 | cache-to: type=gha,mode=max,scope=base-cpan_linux/${{ matrix.platform }}${{ matrix.dockerfile }} 211 | tags: buildcpanonly 212 | 213 | 214 | test_build: 215 | # The type of runner that the job will run on 216 | needs: [get_dependencies, base_build] 217 | runs-on: ubuntu-latest 218 | strategy: 219 | matrix: 220 | dockerfile: [-bookworm, -threaded-bookworm] 221 | # Steps represent a sequence of tasks that will be executed as part of the job 222 | env: 223 | TAG_LATEST: ${{ (contains(matrix.dockerfile,'threaded') || github.event.release.prerelease == 1) && 'false' || 'auto' }} 224 | steps: 225 | - name: Checkout this repository 226 | uses: actions/checkout@v4 227 | with: 228 | fetch-depth: 0 229 | 230 | - name: Inject slug/short variables 231 | uses: rlespinasse/github-slug-action@v5.1.0 232 | 233 | - name: Get git vars 234 | shell: bash 235 | run: | 236 | echo "IMAGE_VERSION=$( git describe --tags --dirty --match "v[0-9]*")" >> $GITHUB_OUTPUT 237 | id: gitVars 238 | 239 | - name: Prepare SVN repository checkout and variables 240 | id: prepareSVN 241 | uses: ./.github/workflows/prepare-svn 242 | 243 | - uses: actions/download-artifact@v4 244 | with: 245 | name: cpanfile-FHEM 246 | 247 | - uses: actions/download-artifact@v4 248 | with: 249 | name: cpanfile-3rdParty 250 | path: 3rdParty 251 | 252 | - name: Prepare docker for build and publish 253 | id: prepareDOCKER 254 | uses: ./.github/workflows/prepare-docker 255 | with: 256 | DOCKER_HUB_USERNAME: ${{ secrets.DOCKER_HUB_USERNAME }} 257 | DOCKER_HUB_ACCESS_TOKEN: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} 258 | GHCR_OWNER: ${{ github.repository_owner }} 259 | GHCR_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} 260 | DOCKERFILE: ${{ matrix.dockerfile }} 261 | 262 | - name: Docker meta 263 | id: meta 264 | uses: docker/metadata-action@v5 265 | env: 266 | DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index 267 | with: 268 | images: | 269 | ghcr.io/${{ github.repository_owner }}/fhem-docker 270 | fhem/fhem 271 | flavor: | 272 | latest= ${{ env.TAG_LATEST }} 273 | tags: | 274 | type=semver,pattern={{version}},suffix=${{ matrix.dockerfile }} 275 | type=semver,pattern={{major}},enable=${{ github.event.release.prerelease == 0 }},suffix=${{ matrix.dockerfile }} 276 | type=ref,event=branch,suffix=${{ matrix.dockerfile }} 277 | type=ref,event=pr,suffix=${{ matrix.dockerfile }} 278 | 279 | - name: Build and cache fhem base layer 280 | uses: docker/build-push-action@v6 281 | id: docker_build_fhem 282 | with: 283 | context: . 284 | load: true 285 | file: ./Dockerfile${{ matrix.dockerfile }} 286 | platforms: linux/amd64 287 | push: false 288 | target: with-fhem-bats 289 | cache-from: | 290 | type=gha,scope=fhem_linux/amd64${{ matrix.dockerfile }} 291 | type=gha,scope=base_linux/amd64${{ matrix.dockerfile }} 292 | cache-to: type=gha,mode=max,scope=fhem_linux/amd64${{ matrix.dockerfile }} 293 | tags: with-fhem 294 | labels: ${{ steps.meta.outputs.labels }} 295 | build-args: | 296 | BUILD_DATE=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }} 297 | IMAGE_VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }} 298 | IMAGE_VCS_REF=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }} 299 | L_USAGE=${{ github.server_url }}/${{ github.repository }}/blob/${{ github.sha }}/README.md 300 | L_VCS_URL=${{ github.server_url }}/${{ github.repository }}/ 301 | L_AUTHORS=${{ github.server_url }}/${{ github.repository }}/graphs/contributors 302 | 303 | - name: Build for bats with fhem base layer 304 | uses: docker/build-push-action@v6 305 | id: docker_build_bats 306 | with: 307 | context: . 308 | load: true 309 | file: ./Dockerfile${{ matrix.dockerfile }} 310 | platforms: linux/amd64 311 | push: false 312 | target: with-fhem-bats 313 | cache-from: | 314 | type=gha,scope=fhem_linux/amd64${{ matrix.dockerfile }} 315 | tags: bats-withfhem 316 | labels: ${{ steps.meta.outputs.labels }} 317 | build-args: | 318 | BUILD_DATE=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }} 319 | IMAGE_VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }} 320 | IMAGE_VCS_REF=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }} 321 | L_USAGE=${{ github.server_url }}/${{ github.repository }}/blob/${{ github.sha }}/README.md 322 | L_VCS_URL=${{ github.server_url }}/${{ github.repository }}/ 323 | L_AUTHORS=${{ github.server_url }}/${{ github.repository }}/graphs/contributors 324 | 325 | - uses: Wandalen/wretry.action@v3.8.0 326 | name: Run bats unit and integration tests in bidge network mode 327 | with: 328 | attempt_limit: 3 329 | command: timeout 9m docker run --rm -e GITHUB_RUN_ID=$GITHUB_RUN_ID -v "${PWD}/src/tests/bats:/code" bats-withfhem:latest -T -t . --filter-tags '!hostMode,!extendedOnly' 330 | 331 | - uses: Wandalen/wretry.action@v3.8.0 332 | name: Run bats unit and integration tests in host network mode 333 | with: 334 | attempt_limit: 3 335 | command: timeout 1m docker run --rm --net=host -e GITHUB_RUN_ID=$GITHUB_RUN_ID -v "${PWD}/src/tests/bats:/code" bats-withfhem:latest -T -t . --filter-tags 'hostMode,!extendedOnly' 336 | 337 | - name: Build for test fhem, python and nodejs layer added for amd64 338 | uses: docker/build-push-action@v6 339 | id: docker_build 340 | with: 341 | context: . 342 | load: true 343 | file: ./Dockerfile${{ matrix.dockerfile }} 344 | platforms: linux/amd64 345 | push: false 346 | target: with-fhem-extended-python-nodejs 347 | cache-from: | 348 | type=gha,scope=full_linux/amd64${{ matrix.dockerfile }} 349 | type=gha,scope=fhem_linux/amd64${{ matrix.dockerfile }} 350 | cache-to: type=gha,mode=max,scope=full_linux/amd64${{ matrix.dockerfile }} 351 | tags: ${{ steps.meta.outputs.tags }} 352 | labels: ${{ steps.meta.outputs.labels }} 353 | build-args: | 354 | BUILD_DATE=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }} 355 | IMAGE_VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }} 356 | IMAGE_VCS_REF=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }} 357 | L_USAGE=${{ github.server_url }}/${{ github.repository }}/blob/${{ github.sha }}/README.md 358 | L_VCS_URL=${{ github.server_url }}/${{ github.repository }}/ 359 | L_AUTHORS=${{ github.server_url }}/${{ github.repository }}/graphs/contributors 360 | 361 | - name: Inspect and run integration tests 362 | run: | 363 | docker image inspect ${{ fromJSON(steps.meta.outputs.json).tags[0] }} 364 | ./scripts/test-integration.sh; 365 | 366 | - name: Run build in unittests 367 | run: | 368 | CONTAINER=$(docker run -d -ti --health-interval=10s --health-timeout=8s --health-start-period=10s --health-retries=5 ${{ fromJSON(steps.meta.outputs.json).tags[0] }} ) 369 | sleep 15; 370 | until [ "$(/usr/bin/docker inspect -f {{.State.Health.Status}} $CONTAINER)" == "healthy" ]; 371 | do sleep 1; 372 | echo -n "."; 373 | done; 374 | echo -e "\n" 375 | docker exec ${CONTAINER} /bin/bash -c "prove --exec 'perl fhem.pl -t' -I FHEM --recurse /opt/fhem/t/FHEM/" || true 376 | docker container rm $CONTAINER --force --volumes 377 | 378 | - name: Build for bats with fhem extended layer 379 | uses: docker/build-push-action@v6 380 | id: docker_build_bats_extended 381 | with: 382 | context: . 383 | load: true 384 | file: ./Dockerfile${{ matrix.dockerfile }} 385 | platforms: linux/amd64 386 | push: false 387 | target: with-fhem-bats-extended-python-nodejs 388 | cache-from: | 389 | type=gha,scope=fhem_linux/amd64${{ matrix.dockerfile }} 390 | type=gha,mode=max,scope=full_linux/amd64${{ matrix.dockerfile }} 391 | tags: bats-withfhem-extended 392 | labels: ${{ steps.meta.outputs.labels }} 393 | build-args: | 394 | BUILD_DATE=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }} 395 | IMAGE_VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }} 396 | IMAGE_VCS_REF=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }} 397 | L_USAGE=${{ github.server_url }}/${{ github.repository }}/blob/${{ github.sha }}/README.md 398 | L_VCS_URL=${{ github.server_url }}/${{ github.repository }}/ 399 | L_AUTHORS=${{ github.server_url }}/${{ github.repository }}/graphs/contributors 400 | 401 | - uses: Wandalen/wretry.action@v3.8.0 402 | name: Run bats unit and integration tests in host network mode 403 | with: 404 | attempt_limit: 3 405 | command: timeout 1m docker run --rm -e GITHUB_RUN_ID=$GITHUB_RUN_ID -v "${PWD}/src/tests/bats:/code" bats-withfhem-extended:latest -T -t . --filter-tags 'extendedOnly' 406 | 407 | published_build: 408 | runs-on: ubuntu-latest 409 | needs: [test_build, cpan_build] 410 | strategy: 411 | matrix: 412 | dockerfile: [-bookworm, -threaded-bookworm] 413 | env: 414 | TAG_LATEST: ${{ (contains(matrix.dockerfile,'threaded') || github.event.release.prerelease == 1) && 'false' || 'auto' }} 415 | # Steps represent a sequence of tasks that will be executed as part of the job 416 | steps: 417 | - name: Checkout this repository 418 | uses: actions/checkout@v4 419 | with: 420 | fetch-depth: 0 421 | 422 | - name: Inject slug/short variables 423 | uses: rlespinasse/github-slug-action@v5.1.0 424 | 425 | - name: Get git vars 426 | shell: bash 427 | run: | 428 | echo "IMAGE_VERSION=$( git describe --tags --dirty --match "v[0-9]*")" >> $GITHUB_OUTPUT 429 | id: gitVars 430 | 431 | - uses: actions/download-artifact@v4 432 | with: 433 | name: cpanfile-FHEM 434 | 435 | - uses: actions/download-artifact@v4 436 | with: 437 | name: cpanfile-3rdParty 438 | path: 3rdParty 439 | 440 | - name: Prepare docker for build and publish 441 | id: prepareDOCKER 442 | uses: ./.github/workflows/prepare-docker 443 | with: 444 | DOCKER_HUB_USERNAME: ${{ secrets.DOCKER_HUB_USERNAME }} 445 | DOCKER_HUB_ACCESS_TOKEN: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }} 446 | GHCR_OWNER: ${{ github.repository_owner }} 447 | GHCR_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} 448 | DOCKERFILE: ${{ matrix.dockerfile }} 449 | 450 | - name: Docker meta 451 | id: meta 452 | uses: docker/metadata-action@v5 453 | env: 454 | DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index 455 | with: 456 | images: | 457 | ghcr.io/${{ github.repository_owner }}/fhem-docker 458 | fhem/fhem 459 | flavor: | 460 | latest= ${{ env.TAG_LATEST }} 461 | tags: | 462 | type=semver,pattern={{version}},suffix=${{ matrix.dockerfile }} 463 | type=semver,pattern={{major}},enable=${{ github.event.release.prerelease == 0 }},suffix=${{ matrix.dockerfile }} 464 | type=ref,event=branch,suffix=${{ matrix.dockerfile }},enable=${{ github.event.release.prerelease == 0 && env.GITHUB_REF_SLUG != 'master' }} 465 | type=ref,event=pr,suffix=${{ matrix.dockerfile }} 466 | type=raw,enable=${{ env.GITHUB_REF_SLUG == 'master' }},priority=200,prefix=,suffix=${{ matrix.dockerfile }},value= 467 | 468 | - name: Build and push cross compiled fhem, python and nodejs layer on supported platforms 469 | uses: docker/build-push-action@v6 470 | id: docker_build 471 | with: 472 | context: . 473 | load: false 474 | file: ./Dockerfile${{ matrix.dockerfile }} 475 | platforms: linux/amd64,linux/arm/v7,linux/arm64,linux/386 476 | push: ${{ github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'publishImage') }} 477 | target: with-fhem-extended-python-nodejs 478 | cache-from: | 479 | type=gha,scope=base_linux/amd64${{ matrix.dockerfile }} 480 | type=gha,scope=base-cpan_linux/386${{ matrix.dockerfile }} 481 | type=gha,scope=base-cpan_linux/arm64${{ matrix.dockerfile }} 482 | type=gha,scope=base-cpan_linux/arm/v7${{ matrix.dockerfile }} 483 | type=gha,scope=fhem_linux/amd64${{ matrix.dockerfile }} 484 | type=gha,scope=full_linux/amd64${{ matrix.dockerfile }} 485 | type=gha,scope=full_linux/386${{ matrix.dockerfile }} 486 | type=gha,scope=full_linux/arm64${{ matrix.dockerfile }} 487 | type=gha,scope=full_linux/arm/v7${{ matrix.dockerfile }} 488 | 489 | #cache-to: type=gha,mode=max,scope=full_linux/cross${{ matrix.dockerfile }} 490 | tags: ${{ steps.meta.outputs.tags }} 491 | labels: ${{ steps.meta.outputs.labels }} 492 | annotations: ${{ steps.meta.outputs.annotations }} 493 | build-args: | 494 | BUILD_DATE=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }} 495 | IMAGE_VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }} 496 | IMAGE_VCS_REF=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }} 497 | L_USAGE=${{ github.server_url }}/${{ github.repository }}/blob/${{ github.sha }}/README.md 498 | L_VCS_URL=${{ github.server_url }}/${{ github.repository }}/ 499 | L_AUTHORS=${{ github.server_url }}/${{ github.repository }}/graphs/contributors 500 | L_DESCR=A full blown Docker image for FHEM house automation system, based on Debian Perl ${{ matrix.dockerfile }}. 501 | 502 | - name: Cache ARM v64 503 | uses: docker/build-push-action@v6 504 | with: 505 | context: . 506 | load: false 507 | file: ./Dockerfile${{ matrix.dockerfile }} 508 | platforms: linux/arm/v7 509 | push: false 510 | target: with-fhem-extended-python-nodejs 511 | cache-from: | 512 | type=gha,scope=full_linux/arm64${{ matrix.dockerfile }} 513 | cache-to: type=gha,mode=max,scope=full_linux/arm64${{ matrix.dockerfile }} 514 | tags: ${{ steps.meta.outputs.tags }} 515 | labels: ${{ steps.meta.outputs.labels }} 516 | annotations: ${{ steps.meta.outputs.annotations }} 517 | build-args: | 518 | BUILD_DATE=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }} 519 | IMAGE_VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }} 520 | IMAGE_VCS_REF=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }} 521 | L_USAGE=${{ github.server_url }}/${{ github.repository }}/blob/${{ github.sha }}/README.md 522 | L_VCS_URL=${{ github.server_url }}/${{ github.repository }}/ 523 | L_AUTHORS=${{ github.server_url }}/${{ github.repository }}/graphs/contributors 524 | L_DESCR=A full blown Docker image for FHEM house automation system, based on Debian Perl ${{ matrix.dockerfile }}. 525 | 526 | - name: Cache ARM v7 527 | uses: docker/build-push-action@v6 528 | with: 529 | context: . 530 | load: false 531 | file: ./Dockerfile${{ matrix.dockerfile }} 532 | platforms: linux/arm/v7 533 | push: false 534 | target: with-fhem-extended-python-nodejs 535 | cache-from: | 536 | type=gha,scope=full_linux/arm/v7${{ matrix.dockerfile }} 537 | cache-to: type=gha,mode=max,scope=full_linux/arm/v7${{ matrix.dockerfile }} 538 | tags: ${{ steps.meta.outputs.tags }} 539 | labels: ${{ steps.meta.outputs.labels }} 540 | annotations: ${{ steps.meta.outputs.annotations }} 541 | build-args: | 542 | BUILD_DATE=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }} 543 | IMAGE_VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }} 544 | IMAGE_VCS_REF=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }} 545 | L_USAGE=${{ github.server_url }}/${{ github.repository }}/blob/${{ github.sha }}/README.md 546 | L_VCS_URL=${{ github.server_url }}/${{ github.repository }}/ 547 | L_AUTHORS=${{ github.server_url }}/${{ github.repository }}/graphs/contributors 548 | L_DESCR=A full blown Docker image for FHEM house automation system, based on Debian Perl ${{ matrix.dockerfile }}. 549 | 550 | - name: Docker meta 551 | id: meta_base 552 | uses: docker/metadata-action@v5 553 | env: 554 | DOCKER_METADATA_ANNOTATIONS_LEVELS: manifest,index 555 | with: 556 | images: | 557 | ghcr.io/${{ github.repository_owner }}/fhem-minimal-docker 558 | fhem/fhem-minimal 559 | flavor: | 560 | latest= ${{ env.TAG_LATEST }} 561 | tags: | 562 | type=semver,pattern={{version}},suffix=${{ matrix.dockerfile }} 563 | type=semver,pattern={{major}},enable=${{ github.event.release.prerelease == 0 }},suffix=${{ matrix.dockerfile }} 564 | type=ref,event=branch,suffix=${{ matrix.dockerfile }},enable=${{ github.event.release.prerelease == 0 && env.GITHUB_REF_SLUG != 'master' }} 565 | type=ref,event=pr,suffix=${{ matrix.dockerfile }} 566 | type=raw,enable=${{ env.GITHUB_REF_SLUG == 'master' }},priority=200,prefix=,suffix=${{ matrix.dockerfile }},value= 567 | 568 | - name: Build and push cross compiled base image on supported platforms 569 | uses: docker/build-push-action@v6 570 | id: docker_build_base 571 | with: 572 | context: . 573 | load: false 574 | file: ./Dockerfile${{ matrix.dockerfile }} 575 | platforms: linux/386,linux/amd64,linux/arm/v7,linux/arm64,386 576 | push: ${{ github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'publishImage') }} 577 | target: with-fhem 578 | cache-from: | 579 | type=gha,scope=base_linux/amd64${{ matrix.dockerfile }} 580 | type=gha,scope=base-cpan_linux/386${{ matrix.dockerfile }} 581 | type=gha,scope=base-cpan_linux/arm64${{ matrix.dockerfile }} 582 | type=gha,scope=base-cpan_linux/arm/v7${{ matrix.dockerfile }} 583 | type=gha,scope=fhem_linux/amd64${{ matrix.dockerfile }} 584 | type=gha,scope=full_linux/amd64${{ matrix.dockerfile }} 585 | type=gha,scope=full_linux/386${{ matrix.dockerfile }} 586 | type=gha,scope=full_linux/arm64${{ matrix.dockerfile }} 587 | type=gha,scope=full_linux/arm/v7${{ matrix.dockerfile }} 588 | tags: ${{ steps.meta_base.outputs.tags }} 589 | annotations: ${{ steps.meta_base.outputs.annotations }} 590 | labels: ${{ steps.meta_base.outputs.labels }} 591 | build-args: | 592 | BUILD_DATE=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.created'] }} 593 | IMAGE_VERSION=${{ fromJSON(steps.meta_base.outputs.json).labels['org.opencontainers.image.version'] }} 594 | IMAGE_VCS_REF=${{ fromJSON(steps.meta_base.outputs.json).labels['org.opencontainers.image.revision'] }} 595 | L_USAGE=${{ github.server_url }}/${{ github.repository }}/blob/${{ github.sha }}/README.md 596 | L_VCS_URL=${{ github.server_url }}/${{ github.repository }}/ 597 | L_AUTHORS=${{ github.server_url }}/${{ github.repository }}/graphs/contributors 598 | L_DESCR=A minimal (perl) Docker image for FHEM house automation system, based on Debian Perl ${{ matrix.dockerfile }}. 599 | -------------------------------------------------------------------------------- /.github/workflows/cacheCleanup.yml: -------------------------------------------------------------------------------- 1 | name: cleanup caches by a branch / PR 2 | on: 3 | pull_request: 4 | types: 5 | - closed 6 | workflow_dispatch: 7 | 8 | jobs: 9 | cleanup: 10 | runs-on: ubuntu-latest 11 | permissions: 12 | # `actions:write` permission is required to delete caches 13 | # See also: https://docs.github.com/en/rest/actions/cache?apiVersion=2022-11-28#delete-a-github-actions-cache-for-a-repository-using-a-cache-id 14 | actions: write 15 | contents: read 16 | steps: 17 | - name: Cleanup 18 | run: | 19 | gh extension install actions/gh-actions-cache 20 | 21 | REPO=${{ github.repository }} 22 | BRANCH=refs/pull/${{ github.event.pull_request.number }}/merge 23 | 24 | echo "Fetching list of cache key" 25 | cacheKeysForPR=$(gh actions-cache list -R $REPO -B $BRANCH | cut -f 1 ) 26 | 27 | ## Setting this to not fail the workflow while deleting cache keys. 28 | set +e 29 | echo "Deleting caches..." 30 | for cacheKey in $cacheKeysForPR 31 | do 32 | gh actions-cache delete $cacheKey -R $REPO -B $BRANCH --confirm 33 | done 34 | echo "Done" 35 | env: 36 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 37 | -------------------------------------------------------------------------------- /.github/workflows/prepare-docker/action.yml: -------------------------------------------------------------------------------- 1 | name: Reusable composit workflow which prepares caching, docker build & login for later push 2 | description: build images and push them to registries 3 | outputs: 4 | VARIANT_FHEM: 5 | description: "Combined string, with FHEM Version and latest svn commit id" 6 | value: ${{ steps.svnVars.outputs.VARIANT_FHEM }} 7 | FHEM_REVISION_LATEST: 8 | description: "The revision Number pulled from SVN" 9 | value: ${{ steps.svnVars.outputs.FHEM_REVISION_LATEST }} 10 | inputs: 11 | DOCKER_HUB_USERNAME: 12 | description: "username on dockerhub to which the access token belongs" 13 | required: false 14 | DOCKER_HUB_ACCESS_TOKEN: 15 | description: "Personal access token from docker hub" 16 | required: false 17 | GHCR_ACCESS_TOKEN: 18 | description: "Personal access token from docker hub" 19 | required: false 20 | GHCR_OWNER: 21 | description: "ownername on github packages to which the access token belongs" 22 | required: false 23 | DOCKERFILE: 24 | description: "name of the dockerfile, used for identify the cache" 25 | required: false 26 | default: "Dockerfile" 27 | 28 | runs: 29 | using: "composite" 30 | steps: 31 | - name: Docker Setup QEMU 32 | uses: docker/setup-qemu-action@v3 33 | with: 34 | image: tonistiigi/binfmt:latest 35 | platforms: all 36 | 37 | - name: Set up Docker Buildx 38 | uses: docker/setup-buildx-action@v3 39 | 40 | - name: Login to DockerHub 41 | if: (github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'publishImage') ) && inputs.DOCKER_HUB_USERNAME != null && inputs.DOCKER_HUB_ACCESS_TOKEN != null 42 | uses: docker/login-action@v3 43 | with: 44 | username: ${{ inputs.DOCKER_HUB_USERNAME }} 45 | password: ${{ inputs.DOCKER_HUB_ACCESS_TOKEN }} 46 | 47 | - name: Login to GitHub Container Registry 48 | if: (github.event_name != 'pull_request' || contains(github.event.pull_request.labels.*.name, 'publishImage') ) && inputs.GHCR_OWNER != null && inputs.GHCR_ACCESS_TOKEN != null 49 | uses: docker/login-action@v3 50 | with: 51 | registry: ghcr.io 52 | username: ${{ inputs.GHCR_OWNER }} 53 | password: ${{ inputs.GHCR_ACCESS_TOKEN }} 54 | -------------------------------------------------------------------------------- /.github/workflows/prepare-svn/action.yml: -------------------------------------------------------------------------------- 1 | name: Reusable composit workflow which prepares svn 2 | description: pull svn remote, extract svn ref and latest tag 3 | outputs: 4 | VARIANT_FHEM: 5 | description: "Combined string, with FHEM Version and latest svn commit id" 6 | value: ${{ steps.svnVars.outputs.VARIANT_FHEM }} 7 | FHEM_REVISION_LATEST: 8 | description: "The revision Number pulled from SVN" 9 | value: ${{ steps.svnVars.outputs.FHEM_REVISION_LATEST }} 10 | 11 | 12 | runs: 13 | using: "composite" 14 | steps: 15 | - name: install svn 16 | shell: bash 17 | run: sudo apt-get install -y subversion 18 | - name: Get latest svn revision from remote server 19 | shell: bash 20 | id: svnRemote 21 | run: echo "LAST_SVN_REVISION=$( svn info --show-item revision https://svn.fhem.de/fhem/trunk )" >> $GITHUB_OUTPUT 22 | 23 | - name: Cache fhem 24 | id: cache-fhem 25 | uses: actions/cache@v4 26 | with: 27 | path: ./src/fhem/trunk 28 | key: ${{ runner.os }}-fhemsvn-${{ steps.svnRemote.outputs.LAST_SVN_REVISION }} 29 | restore-keys: | 30 | ${{ runner.os }}-fhemsvn- 31 | 32 | - name: Update or checkout fhem from svn 33 | shell: bash 34 | if: steps.cache-fhem.outputs.cache-hit != 'true' 35 | run: svn update ./src/fhem/trunk/ || svn co https://svn.fhem.de/fhem/trunk ./src/fhem/trunk; 36 | 37 | - name: prepare svn vars 38 | shell: bash 39 | id: svnVars 40 | working-directory: ./src/fhem/trunk 41 | run: | 42 | FHEM_REVISION_LATEST=$( svn info --show-item last-changed-revision) 43 | FHEM_VERSION=$( svn ls "^/tags" https://svn.fhem.de/fhem/ | grep "FHEM_" | sort | tail -n 1 | cut -d / -f 1 | cut -d " " -f 1 |cut -d _ -f 2- | sed s/_/./g ) 44 | echo "VARIANT_FHEM=$(echo "${FHEM_VERSION}-s${FHEM_REVISION_LATEST}")" >> $GITHUB_OUTPUT 45 | echo "FHEM_REVISION_LATEST=$(echo "$FHEM_REVISION_LATEST")" >> $GITHUB_OUTPUT 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | !.gitkeep 4 | fhem/backup/* 5 | fhem/cache/* 6 | fhem/log/* 7 | fhem/restoreDir/* 8 | .vscode/* 9 | src/FHEM/trunk/* 10 | .cache/* -------------------------------------------------------------------------------- /Dockerfile-bookworm: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1@sha256:9857836c9ee4268391bb5b09f9f157f3c91bb15821bb77969642813b0d00518d 2 | # 3 | # base layer with perl and some general preparations 4 | # 5 | FROM perl:5.38.4-slim-bookworm@sha256:c50d3fae25acf6d97ca1879ad183e3b43cb1610d2858fabd818c45e3993ade51 AS base 6 | 7 | ARG TARGETPLATFORM 8 | 9 | ENV LANG=en_US.UTF-8 \ 10 | LANGUAGE=en_US:en \ 11 | LC_ADDRESS=de_DE.UTF-8 \ 12 | LC_MEASUREMENT=de_DE.UTF-8 \ 13 | LC_MESSAGES=en_DK.UTF-8 \ 14 | LC_MONETARY=de_DE.UTF-8 \ 15 | LC_NAME=de_DE.UTF-8 \ 16 | LC_NUMERIC=de_DE.UTF-8 \ 17 | LC_PAPER=de_DE.UTF-8 \ 18 | LC_TELEPHONE=de_DE.UTF-8 \ 19 | LC_TIME=de_DE.UTF-8 \ 20 | TERM=xterm \ 21 | TZ=Europe/Berlin 22 | 23 | 24 | 25 | 26 | RUN </etc/locale.gen 37 | 38 | LC_ALL=C locale-gen 39 | ln -sf /usr/share/zoneinfo/Europe/Berlin /etc/localtime 40 | echo "Europe/Berlin" > /etc/timezone 41 | LC_ALL=C DEBIAN_FRONTEND=noninteractive dpkg-reconfigure tzdata 42 | LC_ALL=C apt-get autoremove -qqy && LC_ALL=C apt-get clean 43 | rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* ~/.[!.] ~/.??* ~/* 44 | 45 | EOF 46 | 47 | 48 | 49 | # 50 | # Install all CPAN Modules, needed from FHEM and standard modules 51 | # 52 | 53 | FROM perl:5.38.4-bookworm@sha256:bd67348c6dd49b17bc204141b62024e7489e02c92d3965f184e9295a9c79c3f6 AS build-cpan 54 | 55 | ARG TARGETPLATFORM 56 | 57 | COPY cpanfile /usr/src/app/core/cpanfile 58 | 59 | # Install Packages used for building cpan modules: 60 | RUN < /image_info 250 | 251 | VOLUME [ "/opt/fhem" ] 252 | 253 | EXPOSE 8083 254 | 255 | HEALTHCHECK --interval=20s --timeout=10s --start-period=60s --retries=5 CMD /health-check.sh 256 | 257 | WORKDIR "/opt/fhem" 258 | ENTRYPOINT [ "/entry.sh" ] 259 | CMD [ "start" ] 260 | 261 | 262 | 263 | # 264 | # Add additional Perl and System layers 265 | # 266 | 267 | FROM with-fhem AS with-fhem-extended 268 | 269 | ENV PERL5LIB=${PERL5LIB}:/usr/src/app/3rdparty/lib/perl5 270 | 271 | # Add extended system layer 272 | RUN </etc/locale.gen 35 | LC_ALL=C locale-gen 36 | ln -sf /usr/share/zoneinfo/Europe/Berlin /etc/localtime 37 | echo "Europe/Berlin" > /etc/timezone 38 | LC_ALL=C DEBIAN_FRONTEND=noninteractive dpkg-reconfigure tzdata 39 | LC_ALL=C apt-get autoremove -qqy && LC_ALL=C apt-get clean 40 | rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* ~/.[^.] ~/.??* ~/* 41 | EOF 42 | 43 | 44 | 45 | # 46 | # Install all CPAN Modules, needed from FHEM and standard modules 47 | # 48 | 49 | FROM perl:5.38.4-bullseye@sha256:5fb253440ea50106825caa8f2d5eb9b48396b3db89aaa8f88c574a93c6624f56 AS build-cpan 50 | 51 | COPY cpanfile /usr/src/app/core/cpanfile 52 | 53 | # Install Packages used for building cpan modules: 54 | RUN < /image_info 235 | 236 | VOLUME [ "/opt/fhem" ] 237 | 238 | EXPOSE 8083 239 | 240 | HEALTHCHECK --interval=20s --timeout=10s --start-period=60s --retries=5 CMD /health-check.sh 241 | 242 | WORKDIR "/opt/fhem" 243 | ENTRYPOINT [ "/entry.sh" ] 244 | CMD [ "start" ] 245 | 246 | 247 | 248 | # 249 | # Add additional Perl and System layers 250 | # 251 | 252 | FROM with-fhem AS with-fhem-extended 253 | 254 | ENV PERL5LIB=${PERL5LIB}:/usr/src/app/3rdparty/lib/perl5 255 | 256 | # Add extended system layer 257 | RUN </etc/locale.gen 37 | 38 | LC_ALL=C locale-gen 39 | ln -sf /usr/share/zoneinfo/Europe/Berlin /etc/localtime 40 | echo "Europe/Berlin" > /etc/timezone 41 | LC_ALL=C DEBIAN_FRONTEND=noninteractive dpkg-reconfigure tzdata 42 | LC_ALL=C apt-get autoremove -qqy && LC_ALL=C apt-get clean 43 | rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* ~/.[!.] ~/.??* ~/* 44 | 45 | EOF 46 | 47 | 48 | 49 | # 50 | # Install all CPAN Modules, needed from FHEM and standard modules 51 | # 52 | 53 | FROM perl:5.38.4-threaded-bookworm@sha256:b68f923c394df3108ce6760d6d052ef1f8d447275c8f8564f4ca0ad669c6a263 AS build-cpan 54 | 55 | ARG TARGETPLATFORM 56 | 57 | COPY cpanfile /usr/src/app/core/cpanfile 58 | 59 | # Install Packages used for building cpan modules: 60 | RUN < /image_info 250 | 251 | VOLUME [ "/opt/fhem" ] 252 | 253 | EXPOSE 8083 254 | 255 | HEALTHCHECK --interval=20s --timeout=10s --start-period=60s --retries=5 CMD /health-check.sh 256 | 257 | WORKDIR "/opt/fhem" 258 | ENTRYPOINT [ "/entry.sh" ] 259 | CMD [ "start" ] 260 | 261 | 262 | 263 | # 264 | # Add additional Perl and System layers 265 | # 266 | 267 | FROM with-fhem AS with-fhem-extended 268 | 269 | ENV PERL5LIB=${PERL5LIB}:/usr/src/app/3rdparty/lib/perl5 270 | 271 | # Add extended system layer 272 | RUN </etc/locale.gen 35 | LC_ALL=C locale-gen 36 | ln -sf /usr/share/zoneinfo/Europe/Berlin /etc/localtime 37 | echo "Europe/Berlin" > /etc/timezone 38 | LC_ALL=C DEBIAN_FRONTEND=noninteractive dpkg-reconfigure tzdata 39 | LC_ALL=C apt-get autoremove -qqy && LC_ALL=C apt-get clean 40 | rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* ~/.[^.] ~/.??* ~/* 41 | EOF 42 | 43 | 44 | 45 | # 46 | # Install all CPAN Modules, needed from FHEM and standard modules 47 | # 48 | 49 | FROM perl:5.38.4-threaded-bullseye@sha256:8be5b3c310dbf23136edd31a938ade38c2a4391eb8c3a6ad17eca091baa7b164 AS build-cpan 50 | 51 | COPY cpanfile /usr/src/app/core/cpanfile 52 | 53 | # Install Packages used for building cpan modules: 54 | RUN < /image_info 235 | 236 | VOLUME [ "/opt/fhem" ] 237 | 238 | EXPOSE 8083 239 | 240 | HEALTHCHECK --interval=20s --timeout=10s --start-period=60s --retries=5 CMD /health-check.sh 241 | 242 | WORKDIR "/opt/fhem" 243 | ENTRYPOINT [ "/entry.sh" ] 244 | CMD [ "start" ] 245 | 246 | 247 | 248 | # 249 | # Add additional Perl and System layers 250 | # 251 | 252 | FROM with-fhem as with-fhem-extended 253 | 254 | ENV PERL5LIB=${PERL5LIB}:/usr/src/app/3rdparty/lib/perl5 255 | 256 | # Add extended system layer 257 | RUN < 161 | LC_ALL=C apt-get autoremove -qqy && LC_ALL=C apt-get clean 162 | EOF 163 | 164 | RUN < 166 | EOF 167 | ``` 168 | 169 | See more examples in our docker-compose.yml file. 170 | 171 | Important: If you need additional Perl CPAN Modules, you must install them directly from CPAN and not via apt! 172 | 173 | #### till version 3 (deprecated) 174 | 175 | Don't do this unless you really know what this does! 176 | You may define several different types of packages to be installed automatically during initial start of the container by adding one of the following parameters to your container run command: 177 | 178 | * Debian APT packages: 179 | 180 | ```shell 181 | -e APT_PKGS="package1 package2" 182 | ``` 183 | 184 | * Perl CPAN modules: 185 | 186 | ```shell 187 | -e CPAN_PKGS="App::Name1 App::Name2" 188 | ``` 189 | 190 | * Python PIP packages: 191 | 192 | ```shell 193 | -e PIP_PKGS="package1 package2" 194 | ``` 195 | 196 | * Node.js NPM packages: 197 | 198 | ```shell 199 | -e NPM_PKGS="package1 package2" 200 | ``` 201 | 202 | ### Directory and file permissions 203 | 204 | For security and functional reasons, directory and file permissions for FHEM will be set during every container startup. 205 | That means that directories and files can only be opened by members of the [`$FHEM_GID`](#tweak-container-settings-using-environment-variables) user group or the [`$FHEM_UID`](#tweak-container-settings-using-environment-variables) user itself. Also, the execution bit for files is only kept for a limited set of file names and directories, which are: 206 | 207 | * files named `*.pl`, `*.py`, `*.sh` 208 | * every file that is stored in any directory named `bin` or `sbin` 209 | * every file that is stored in any directory containing the string `script` in its name 210 | 211 | Should you require any different permissions, you may read the next section to learn more about how to make any changes using custom pre start script `/pre-start.sh` or `/docker/pre-start.sh`. 212 | 213 | ### Make any other changes during container start 214 | 215 | In case you need to perform further changes to the container before it is ready for your FHEM instance to operate, there are a couple of entry points for your own scripts that will be run automatically if they are found at the right place. In order to achieve this, you need to mount the script file itself or a complete folder that contains that script to the respective destination inside your container. See Docker documentation about [Use volumes](https://docs.docker.com/storage/volumes/) and [Bind mounts](https://docs.docker.com/storage/bind-mounts/) to learn how to achieve this in general. 216 | 217 | If something needs to be done only once during the first start of a fresh container you just created, like after upgrading to a new version of the FHEM Docker Image, the `*-init.sh` scripts are the right place: 218 | 219 | * `/pre-init.sh`, `/docker/pre-init.sh` 220 | 221 | This script will be run at the very beginning of the initialization of the new container, even before any custom packages will be installed. 222 | 223 | * `/post-init.sh`, `/docker/post-init.sh` 224 | 225 | This script will be run at the very end of the initialization of the new container, also after your local FHEM configuration was checked and adjusted for compatibility with the container. Custom packages you defined using the environment variables mentioned above will be installed already at this point in time. This is likely the best place for you to do any final changes to the environment that need to be done only once for the lifetime of that container. 226 | 227 | If something needs to be done every time you (re)start your container, the `*-start.sh` scripts are the right place: 228 | 229 | * `/pre-start.sh`, `/docker/pre-start.sh` 230 | 231 | This script will be run every time the container starts, even before the FHEM Docker Image's own startup preparations. FHEM will not yet be running at this point in time. 232 | 233 | * `/post-start.sh`, `/docker/post-start.sh` 234 | 235 | This script will be run every time the container starts and after the FHEM process was already started. 236 | 237 | ### Role of the telnet device in FHEM 238 | 239 | #### since version 4 240 | 241 | There is no internal use of the telnet device anymore 242 | 243 | #### till version 3 (deprecated) 244 | 245 | The Docker container will need to communicate with FHEM to shutdown nicely instead of just killing the process. For this to work properly, a `telnet` device is of paramount importance. Unless you are using configDB, the container will try to automatically detect and adjust your telnet configuration for it to work. If for any reason that fails or you are using configDB, it is your own obligation to configure such `telnet` device (`define telnetPort telnet 7072`). It may listen on the standard port 7072 or can be any other port (see environment variable `TELNETPORT` to re-configure it). 246 | 247 | It is enough for the `telnet` device to only listen on the loopback device (aka localhost) but it _cannot_ have any password protection enabled for loopback connections. If you require your `telnet` instance to listen for external connections, it is usually best-practice to set a password for it. In that case, make sure that any `allowed` device you might have configured for this purpose only requires a password for non-loopback connections (e.g. using attribute `globalpassword` instead of `password` - also see [allowed commandref](https://fhem.de/commandref.html#allowed)). The same applies when using the deprecated attribute `password` for the `telnet` device itself (see [telnet commandref](https://fhem.de/commandref.html#telnet)). 248 | 249 | ### Docker health check control 250 | 251 | The image comes with a built-in script to check availability, which communicates with the DockerImageInfo Definition. 252 | 253 | If for whatever reason you want to disable checking a specific FHEMWEB instance, you may set the user attribute `DockerHealthCheck` to 0 on that particular FHEMWEB device. 254 | 255 | Note that the health check itself cannot be entirely disabled as it will ensure to notify you in case of failures, hindering proper shutdown of FHEM when triggered by Docker or OS shutdown procedure. 256 | 257 | ### Map USB devices to your container 258 | 259 | 1. Find out the USB device path/address from your Docker host machine first: 260 | 261 | ```console 262 | lsusb -v | grep -E '\<(Bus|iProduct|bDeviceClass|bDeviceProtocol)' 2>/dev/null 263 | ``` 264 | 265 | 2. You may then derive the device path from it and add the following parameter to your container run command: 266 | 267 | ```shell 268 | --device=/dev/bus/usb/001/002 269 | ``` 270 | 271 | ### Tweak container settings using environment variables 272 | 273 | * Change FHEM logfile format: 274 | To set a different logfile path and format (default is ./log/fhem-%Y-%m-%d.log): 275 | 276 | ```shell 277 | -e LOGFILE=./log/fhem-%Y-%m-%d.log 278 | ``` 279 | 280 | * Change FHEM local Telnet port for health check and container restart handling: (deprecated since v4) 281 | To set a different Telnet port for local connection during health check and container restart (default is 7072): 282 | 283 | ```shell 284 | -e TELNETPORT=7072 285 | ``` 286 | 287 | Note that this is of paramount importance if you are running more than one instance in host network mode on the same server, otherwise the instances will interfere each other with their restart behaviours. 288 | 289 | * Change FHEM system user ID: 290 | To set a different UID for the user `fhem` (default is 6061): 291 | 292 | ```shell 293 | -e FHEM_UID=6061 294 | ``` 295 | 296 | * Change FHEM group ID: 297 | To set a different GID for the group `fhem` (default is 6061): 298 | 299 | ```shell 300 | -e FHEM_GID=6061 301 | ``` 302 | 303 | * Change FHEM directory permissions: 304 | To set different directory permissions for `$FHEM_DIR` (default is 0750): 305 | 306 | ```shell 307 | -e FHEM_PERM_DIR=0750 308 | ``` 309 | 310 | * Change FHEM file permissions: 311 | To set different file permissions for `$FHEM_DIR` (default is 0640): 312 | 313 | ```shell 314 | -e FHEM_PERM_FILE=0640 315 | ``` 316 | 317 | * Change umask: 318 | To set a different umask for `FHEM_UID` (default is 0037): 319 | 320 | ```shell 321 | -e UMASK=0037 322 | 323 | * Change Bluetooth group ID: 324 | To set a different GID for the group `bluetooth` (default is 6001): 325 | 326 | ```shell 327 | -e BLUETOOTH_GID=6001 328 | ``` 329 | 330 | * Change GPIO group ID: 331 | To set a different GID for the group `gpio` (default is 6002): 332 | 333 | ```shell 334 | -e GPIO_GID=6002 335 | ``` 336 | 337 | * Change I2C group ID: 338 | To set a different GID for the group `i2c` (default is 6003): 339 | 340 | ```shell 341 | -e I2C_GID=6003 342 | ``` 343 | 344 | * Change shutdown timeout: 345 | To set a different setting for the timer during FHEM shutdown handling, you may add this environment variable: 346 | 347 | ```shell 348 | -e TIMEOUT=10 349 | ``` 350 | 351 | * Set locale: 352 | For maximum compatibility, standard locale is set to US english with some refinements towards the European standards and German defaults. This may be changed according to your needs (also see [Debian Wiki](https://wiki.debian.org/Locale) for more information): 353 | 354 | ```shell 355 | -e LANG=en_US.UTF-8 356 | -e LANGUAGE=en_US:en 357 | -e LC_ADDRESS=de_DE.UTF-8 358 | -e LC_MEASUREMENT=de_DE.UTF-8 359 | -e LC_MESSAGES=en_DK.UTF-8 360 | -e LC_MONETARY=de_DE.UTF-8 361 | -e LC_NAME=de_DE.UTF-8 362 | -e LC_NUMERIC=de_DE.UTF-8 363 | -e LC_PAPER=de_DE.UTF-8 364 | -e LC_TELEPHONE=de_DE.UTF-8 365 | -e LC_TIME=de_DE.UTF-8 366 | ``` 367 | 368 | * Set timezone: 369 | Set a specific timezone in [POSIX format](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones): 370 | 371 | ```shell 372 | -e TZ=Europe/Berlin 373 | ``` 374 | 375 | * Using configDB: 376 | Should you be using FHEM config type [`configDB`](https://fhem.de/commandref.html#configdb), you need to change the FHEM configuration source for correct startup by setting the following environment variable: 377 | 378 | ```shell 379 | -e CONFIGTYPE=configDB 380 | ``` 381 | 382 | Note that some essential global configuration that is affecting FHEM during startup is being enforced using FHEM\_GLOBALATTR environment variable (nofork=0 and updateInBackground=1; logfile and pidfilename accordingly, based on environment variables LOGFILE and PIDFILE). These settings cannot be changed during runtime in FHEM and any setting that might be in your configDB configuration will be overwritten the next time you save your configuration. It might happen that FHEM will show you some warnings as part of the "message of the day" (motd attribute), stating that an attribute is read-only. That's okay, just clear that message and save your FHEM configuration at least once so the configuration is back in sync. 383 | 384 | Only for v3 and lower: Last but not least you need to make sure the telnet device configuration [described above](#role-of-the-telnet-device-in-fhem) is correct. 385 | 386 | * Starting the demo: 387 | To start the demo environment: 388 | 389 | ```shell 390 | -e CONFIGTYPE=fhem.cfg.demo 391 | ``` 392 | 393 | * Set Docker host IPv4 address for host.docker.internal: 394 | 395 | ```shell 396 | -e DOCKER_HOST=172.17.0.1 397 | ``` 398 | 399 | If this variable is not present, host IP will automatically be detected based on the subnet network gateway (also see variable `DOCKER_GW` below). 400 | In case the container is running in network host network mode, host.docker.internal is set to 127.0.127.2 to allow loopback network connectivity. 401 | host.docker.internal will also be evaluated automatically for SSH connection on port 22 by adding the servers public key to `/opt/fhem/.ssh/known_hosts` so that unattended connectivity for scripts is available. 402 | 403 | * Set Docker gateway IPv4 address for gateway.docker.internal: 404 | 405 | ```shell 406 | -e DOCKER_GW=172.17.0.1 407 | ``` 408 | 409 | If this variable is not present, the gateway will automatically be detected. 410 | 411 | 412 | * Set FHEM startup timeout: 413 | Set a Timeout, how long the docker container waits until the FHEM process will finished starting. 414 | If the timeout is over, and FHEM is not started, the container is stopped. 415 | You will see an error like this in the container log, if starting wasn't finished early enough: 416 | `ERROR: Fatal: No message from FHEM since 60 seconds that server has started.` 417 | 418 | If you have a slow system and a module which blocks FHEM to be ready adjust this to a higher value. 419 | 420 | ```shell 421 | -e TIMEOUT_STARTING=60 422 | ``` 423 | 424 | If this variable is not present, the timeout will be 60 seconds. 425 | 426 | * Manipulating software in the container using their own environment variables: 427 | For security reasons, only allowed environment variables are passed to the FHEM user environment. To control certain behaviours of Perl, Node.js and Python, those language interpreters come with their own environment variables. Any variable that was set for the container and with a prefix of either PERL, NODE or PYTHON is exported to the FHEM user environment so it is available there during runtime of the fhem.pl main process and subsequently all its child processes. 428 | 429 | ## Further tweaks for your FHEM configuration 430 | 431 | ### Connect to Docker host from within container 432 | 433 | If you would like to connect to a service that is running on your Docker host itself or to a container that is running in host network mode, you may use the following DNS alias names that are automatically being added to /etc/hosts during container bootup: 434 | 435 | * gateway.docker.internal 436 | * host.docker.internal 437 | 438 | That is, if you did not configure those in your local DNS, of course. 439 | 440 | In case the container is running in host network mode, the host IP address will be set to 127.0.127.2 as an alias for 'localhost'. That means a service you would like to reach needs to listen on the loopback interface as well. If a service you would like to reach is only listening on a particular IP address or interface instead, you need to set the environment variable `DOCKER_HOST` to the respective IP address as there is no way for the FHEM Docker Image to automatically detect what you need. 441 | When running in host network mode, the gateway will reflect your actual network segment gateway IP address. 442 | 443 | Also, for host.docker.internal, the SSH host key will automatically be added and updated in `/opt/fhem/.ssh/known_hosts` so that FHEM modules and other scripts can automatically connect without any further configuration effort. Note that the SSH client keys that FHEM will use to authenticate itself are shown as readings in the DockerImageInfo device in FHEM. You may copy & paste those to the destination host into the respective destination user home directory with filename `~/.ssh/authorized_keys`. 444 | 445 | If for some reason the host details are not detected correctly, you may overwrite the IP addresses using environment variables (see `DOCKER_HOST` and `DOCKER_GW` above). 446 | 447 | ## Adding Git for version control of your Home Automation Docker containers 448 | 449 | Prerequisites on your Docker host: 450 | 451 | 1. Ensure docker-compose is installed: See [Install Docker Compose](https://docs.docker.com/compose/install/) 452 | 2. Ensure Git command is installed, e.g. run `sudo apt install git` 453 | 454 | Follow initial setup steps: 455 | 456 | 1. Put docker-compose.yml and .gitignore into an empty sub-folder, e.g. /docker/home 457 | 458 | ```console 459 | sudo mkdir -p /docker/home 460 | sudo curl -fsSL -o /docker/home/docker-compose.yml https://github.com/fhem/fhem-docker/raw/master/docker-compose.yml 461 | sudo curl -fsSL -o /docker/home/.gitignore https://github.com/fhem/fhem-docker/raw/master/.gitignore 462 | ``` 463 | 464 | Note that the sub-directory "home" will be the base prefix name for all your Docker containers (e.g. resulting in home_SERVICE_1). This will also help to run multiple instances of your Stack on the same host, e.g. to separate production environment in /docker/home from development in /docker/home-dev. 465 | 466 | 2. Being in /docker/home, run command to start your Docker stack: 467 | 468 | ```console 469 | cd /docker/home; sudo docker-compose up -d 470 | ``` 471 | 472 | All FHEM files including your individual configuration and changes will be stored in ./fhem/ . 473 | You may also put an existing FHEM installation into ./fhem/ before the initial start, it will be automatically updated for compatibility with fhem-docker. 474 | Note that if you are using configDB already, you need to ensure Docker compatibility before starting the container for the very first time (see `DOCKER_*` environment variables above). 475 | 476 | 3. Create a local Git repository and add all files as an initial commit: 477 | 478 | ```console 479 | cd /docker/home 480 | sudo git init 481 | sudo git add -A 482 | sudo git commit -m "Initial commit" 483 | ``` 484 | 485 | Run the following command whenever you would like to mark changes as permanent: 486 | 487 | ```console 488 | cd /docker/home; sudo git add -A; sudo git commit -m "FHEM update" 489 | ``` 490 | 491 | Note: This will also add any new files within your whole Docker Stack outside of the ./fhem/ folder. 492 | Please see Git documentation for details and further commands. 493 | 494 | 4. Optional - Add remote repository for external backup. It is strongly recommended to have your external repository set to _private_ before doing so: 495 | 496 | ```console 497 | sudo git remote add origin git@github.com:user/repo.git 498 | sudo git push --force --set-upstream origin master 499 | ``` 500 | 501 | Note that after updating your local repository as described above, you also want to push those changes to the remote server: 502 | 503 | ```console 504 | cd /docker/home; sudo git push 505 | ``` 506 | 507 | To restore your Docker Stack from remote Git backup on a fresh Docker host installation: 508 | 509 | ```console 510 | sudo mkdir -p /docker 511 | cd /docker; sudo git clone git@github.com:user/repo.git 512 | cd /docker/home; sudo docker-compose up -d 513 | ``` 514 | 515 | 516 | ## Testing the Image itself in a container 517 | 518 | Basic testing of the image is done in the pipeline. The pipeline will start a container and verify that the health check reports the container is alive. 519 | 520 | The bash scripts inside the container, are tested via bats: 521 | 522 | To run the test, build the image with the specific target: 523 | 524 | docker build --rm --load -f "Dockerfile-bullseye" -t fhemdocker:test --target with-fhem-bats "." 525 | 526 | Then this image, can be used to start a new container and running bats inside the container. 527 | docker run -it --rm -v "${PWD}/src/tests/bats:/code" fhemdocker:test . 528 | 529 | ## A needed perl module is missing 530 | 531 | If you are running a 3rd party module, advice the maintainer to this description: 532 | 533 | During docker build, repositorys are searched by topics and content in the readme.md file. 534 | If the build finds your repository, it will check automatically, what perl modules are needed. 535 | Modules wich are found will be installed via cpan in the resulting docker image. 536 | This allows users of the docker image to use your module. 537 | 538 | Add the topic 'fhem' and 'perl' and provide an instruction in your readme.md with 539 | instruction how to use update add / update all to install your module. 540 | 541 | 542 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | # This is an exmaple Docker Compose file to start your own Docker Stack 2 | 3 | version: '2.3' 4 | 5 | networks: 6 | net: 7 | driver: bridge 8 | # enable_ipv6: true 9 | ipam: 10 | driver: default 11 | config: 12 | - subnet: 172.27.0.0/28 13 | gateway: 172.27.0.1 14 | # - subnet: fd00:0:0:0:27::/80 15 | # gateway: fd00:0:0:0:27::1 16 | 17 | services: 18 | 19 | #### 20 | # HINT: use only ONE of the example "fhem:" service 21 | # definitions below ! 22 | # 23 | 24 | # Minimum example w/o any custom environment variables 25 | fhem_5_a: 26 | image: ghcr.io/fhem/fhem-docker:5-bookworm 27 | restart: always 28 | networks: 29 | - net 30 | ports: 31 | - "8083:8083" 32 | volumes: 33 | - "./fhem/:/opt/fhem/" 34 | 35 | # Example w/ custom environment variables 36 | fhem_5_b: 37 | image: ghcr.io/fhem/fhem-docker:5-bookworm 38 | restart: always 39 | networks: 40 | - net 41 | ports: 42 | - "8083:8083" 43 | volumes: 44 | - "./fhem/:/opt/fhem/" 45 | environment: 46 | FHEM_UID: 6061 47 | FHEM_GID: 6061 48 | TIMEOUT: 10 49 | RESTART: 1 50 | TELNETPORT: 7072 51 | TZ: Europe/Berlin 52 | # CONFIGTYPE: configDB 53 | 54 | # Example to connect USB to the container w/o 55 | # privileged mode (preferred method) 56 | fhem_5_c: 57 | image: ghcr.io/fhem/fhem-docker:5-bookworm 58 | restart: always 59 | networks: 60 | - net 61 | ports: 62 | - "8083:8083" 63 | volumes: 64 | - "./fhem/:/opt/fhem/" 65 | devices: 66 | - "/dev/ttyUSB0:/dev/ttyUSB0" 67 | environment: 68 | FHEM_UID: 6061 69 | FHEM_GID: 6061 70 | TIMEOUT: 10 71 | RESTART: 1 72 | TELNETPORT: 7072 73 | TZ: Europe/Berlin 74 | 75 | # Example to connect USB to the container w/ 76 | # privileged mode (not recommended for security reasons) 77 | fhem_5_d: 78 | image: ghcr.io/fhem/fhem-docker:5-bookworm 79 | restart: always 80 | privileged: true 81 | networks: 82 | - net 83 | ports: 84 | - "8083:8083" 85 | volumes: 86 | - "./fhem/:/opt/fhem/" 87 | - "/dev/ttyUSB0:/dev/ttyUSB0" 88 | environment: 89 | FHEM_UID: 6061 90 | FHEM_GID: 6061 91 | TIMEOUT: 10 92 | RESTART: 1 93 | TELNETPORT: 7072 94 | TZ: Europe/Berlin 95 | 96 | # Example for privileged container w/ 97 | # host network (not recommended for security reasons) 98 | fhem_5_e: 99 | image: ghcr.io/fhem/fhem-docker:5-bookworm 100 | restart: always 101 | privileged: true 102 | network_mode: "host" 103 | ports: 104 | - "8083:8083" 105 | volumes: 106 | - "./fhem/:/opt/fhem/" 107 | environment: 108 | FHEM_UID: 6061 109 | FHEM_GID: 6061 110 | TIMEOUT: 10 111 | RESTART: 1 112 | TELNETPORT: 7072 113 | TZ: Europe/Berlin 114 | 115 | 116 | # example with adding a cpan packages to your image 117 | fhem_5_f: 118 | build: 119 | context: . 120 | dockerfile_inline: | 121 | FROM ghcr.io/fhem/fhem-docker:5-bookworm 122 | RUN <> 124 | EOF 125 | pull_policy: build 126 | restart: always 127 | networks: 128 | - net 129 | ports: 130 | - "8083:8083" 131 | volumes: 132 | - "./fhem/:/opt/fhem/" 133 | 134 | # example with extending debian packages to your image 135 | fhem_5_g: 136 | build: 137 | context: . 138 | dockerfile_inline: | 139 | FROM ghcr.io/fhem/fhem-docker:5-bookworm 140 | RUN < 143 | LC_ALL=C apt-get autoremove -qqy && LC_ALL=C apt-get clean 144 | EOF 145 | pull_policy: build 146 | restart: always 147 | networks: 148 | - net 149 | ports: 150 | - "8083:8083" 151 | volumes: 152 | - "./fhem/:/opt/fhem/" 153 | 154 | # example with extending npm packages to your image 155 | fhem_5_h: 156 | build: 157 | context: . 158 | dockerfile_inline: | 159 | FROM ghcr.io/fhem/fhem-docker:5-bookworm 160 | RUN < 162 | npm cache clean --force 163 | EOF 164 | pull_policy: build 165 | restart: always 166 | networks: 167 | - net 168 | ports: 169 | - "8083:8083" 170 | volumes: 171 | - "./fhem/:/opt/fhem/" 172 | 173 | 174 | # example with extending python packages to your image 175 | fhem_5_i: 176 | build: 177 | context: . 178 | dockerfile_inline: | 179 | FROM ghcr.io/fhem/fhem-docker:5-bookworm 180 | RUN < 182 | EOF 183 | pull_policy: build 184 | restart: always 185 | networks: 186 | - net 187 | ports: 188 | - "8083:8083" 189 | volumes: 190 | - "./fhem/:/opt/fhem/" 191 | 192 | 193 | 194 | 195 | #### 196 | # HINT: use only ONE of the example "fhem:" service 197 | # definitions below ! 198 | # 199 | 200 | # Minimum example w/o any custom environment variables 201 | fhem_4_a: 202 | image: ghcr.io/fhem/fhem-docker:4-bullseye 203 | restart: always 204 | networks: 205 | - net 206 | ports: 207 | - "8083:8083" 208 | volumes: 209 | - "./fhem/:/opt/fhem/" 210 | 211 | # Example w/ custom environment variables 212 | fhem_4_b: 213 | image: ghcr.io/fhem/fhem-docker:4-bullseye 214 | restart: always 215 | networks: 216 | - net 217 | ports: 218 | - "8083:8083" 219 | volumes: 220 | - "./fhem/:/opt/fhem/" 221 | environment: 222 | FHEM_UID: 6061 223 | FHEM_GID: 6061 224 | TIMEOUT: 10 225 | RESTART: 1 226 | TELNETPORT: 7072 227 | TZ: Europe/Berlin 228 | # CONFIGTYPE: configDB 229 | 230 | # Example to connect USB to the container w/o 231 | # privileged mode (preferred method) 232 | fhem_4_c: 233 | image: ghcr.io/fhem/fhem-docker:4-bullseye 234 | restart: always 235 | networks: 236 | - net 237 | ports: 238 | - "8083:8083" 239 | volumes: 240 | - "./fhem/:/opt/fhem/" 241 | devices: 242 | - "/dev/ttyUSB0:/dev/ttyUSB0" 243 | environment: 244 | FHEM_UID: 6061 245 | FHEM_GID: 6061 246 | TIMEOUT: 10 247 | RESTART: 1 248 | TELNETPORT: 7072 249 | TZ: Europe/Berlin 250 | 251 | # Example to connect USB to the container w/ 252 | # privileged mode (not recommended for security reasons) 253 | fhem_4_d: 254 | image: ghcr.io/fhem/fhem-docker:4-bullseye 255 | restart: always 256 | privileged: true 257 | networks: 258 | - net 259 | ports: 260 | - "8083:8083" 261 | volumes: 262 | - "./fhem/:/opt/fhem/" 263 | - "/dev/ttyUSB0:/dev/ttyUSB0" 264 | environment: 265 | FHEM_UID: 6061 266 | FHEM_GID: 6061 267 | TIMEOUT: 10 268 | RESTART: 1 269 | TELNETPORT: 7072 270 | TZ: Europe/Berlin 271 | 272 | # Example for privileged container w/ 273 | # host network (not recommended for security reasons) 274 | fhem_4_e: 275 | image: ghcr.io/fhem/fhem-docker:4-bullseye 276 | restart: always 277 | privileged: true 278 | network_mode: "host" 279 | ports: 280 | - "8083:8083" 281 | volumes: 282 | - "./fhem/:/opt/fhem/" 283 | environment: 284 | FHEM_UID: 6061 285 | FHEM_GID: 6061 286 | TIMEOUT: 10 287 | RESTART: 1 288 | TELNETPORT: 7072 289 | TZ: Europe/Berlin 290 | 291 | 292 | # example with adding a cpan packages to your image 293 | fhem_4_f: 294 | build: 295 | context: . 296 | dockerfile_inline: | 297 | FROM ghcr.io/fhem/fhem-docker:4-bullseye 298 | RUN <> 300 | EOF 301 | pull_policy: build 302 | restart: always 303 | networks: 304 | - net 305 | ports: 306 | - "8083:8083" 307 | volumes: 308 | - "./fhem/:/opt/fhem/" 309 | 310 | # example with extending debian packages to your image 311 | fhem_4_g: 312 | build: 313 | context: . 314 | dockerfile_inline: | 315 | FROM ghcr.io/fhem/fhem-docker:4-bullseye 316 | RUN < 319 | LC_ALL=C apt-get autoremove -qqy && LC_ALL=C apt-get clean 320 | EOF 321 | pull_policy: build 322 | restart: always 323 | networks: 324 | - net 325 | ports: 326 | - "8083:8083" 327 | volumes: 328 | - "./fhem/:/opt/fhem/" 329 | 330 | # example with extending npm packages to your image 331 | fhem_4_h: 332 | build: 333 | context: . 334 | dockerfile_inline: | 335 | FROM ghcr.io/fhem/fhem-docker:4-bullseye 336 | RUN < 338 | npm cache clean --force 339 | EOF 340 | pull_policy: build 341 | restart: always 342 | networks: 343 | - net 344 | ports: 345 | - "8083:8083" 346 | volumes: 347 | - "./fhem/:/opt/fhem/" 348 | 349 | 350 | # example with extending python packages to your image 351 | fhem_4_i: 352 | build: 353 | context: . 354 | dockerfile_inline: | 355 | FROM ghcr.io/fhem/fhem-docker:4-bullseye 356 | RUN < 358 | EOF 359 | pull_policy: build 360 | restart: always 361 | networks: 362 | - net 363 | ports: 364 | - "8083:8083" 365 | volumes: 366 | - "./fhem/:/opt/fhem/" 367 | -------------------------------------------------------------------------------- /fhem-docker.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": {} 8 | } -------------------------------------------------------------------------------- /migration.md: -------------------------------------------------------------------------------- 1 | ## Migration vom V3 to V4 version of the docker image 2 | 3 | 4 | Here are most common scenarios described, which needs migration. 5 | May you have a very special setup which is not covered here. If so, feel free to open a issue. 6 | 7 | 8 | ### General 9 | 10 | Installing packages in a running container isn't supported anymore, because this is a antipattern towards docker. 11 | If you are an expert, you can still do this on your own, but here are only standard docker procedures explained: 12 | 13 | 14 | You specified a custom Perl package via one of the environment variables xxxx_PKGS. 15 | 16 | 17 | There a multiple options available to overcome this. 18 | The ranked options are: 19 | 20 | 1. Open a issue, which package is missing. 21 | If this requirement comes from a 3rdparty repository available at github, there is a way to add this to future versions of the image. 22 | 2. Modify your [docker-comppose.yaml](https://github.com/fhem/fhem-docker/blob/docs-v4/docker-compose.yml#L117): 23 | Extend the FHEM image via a build in your compose file. 24 | Other options to archive this can be found in the docker documentation. 25 | You can extend the minimal or the full image. 26 | The example extends the full image: 27 | 28 | Remove the line with the image: 29 | 30 | image: ghcr.io/fhem/fhem-docker:4-bullseye 31 | 32 | 33 | And add these lines to build a new image which your custom extension in your compose file: 34 | 35 | 36 | fhem: 37 | build: 38 | context: . 39 | dockerfile_inline: | 40 | FROM ghcr.io/fhem/fhem-docker:dev-bullseye 41 | RUN <> 59 | 60 | 61 | 62 | ### Custom Linux package needed 63 | 64 | You specified a custom Perl package via the environment variable: 65 | 66 | environment: 67 | -e APT_PKGS="package1 package2" 68 | 69 | Insert this lines to your compose file right under # Here you can add your custom build commands, installing every software you want 70 | 71 | LC_ALL=C DEBIAN_FRONTEND=noninteractive apt-get update 72 | LC_ALL=C DEBIAN_FRONTEND=noninteractive apt-get install -qqy --no-install-recommends << DEBIAN PACKAGENAME >> 73 | LC_ALL=C apt-get autoremove -qqy && LC_ALL=C apt-get clean 74 | 75 | ### Additional Python Packages needed 76 | 77 | You specified a custom Perl package via the environment variable: 78 | 79 | environment: 80 | -e PIP_PKGS="package1 package2" 81 | 82 | Insert this line to your compose file right under # Here you can add your custom build commands, installing every software you want 83 | 84 | pip3 install --no-cache-dir 85 | 86 | 87 | ### Additional NodeJS Packages needed 88 | 89 | -e NPM_PKGS="package1 package2" 90 | 91 | 92 | Insert this lines to your compose file right under # Here you can add your custom build commands, installing every software you want 93 | 94 | npm install -g --unsafe-perm --production 95 | npm cache clean --force 96 | -------------------------------------------------------------------------------- /renovate.json5: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended", 5 | "docker:pinDigests", 6 | ":separateMultipleMajorReleases" 7 | ], 8 | 9 | "packageRules": [ 10 | { 11 | "groupName": "Docker Digest", 12 | "matchDatasources": ["docker"], 13 | "matchPackageNames": ["perl"], 14 | "matchUpdateTypes": ["patch", "pin", "digest"], 15 | "automerge": true, 16 | "commitMessageExtra": "to {{#if isPinDigest}}{{{newDigestShort}}}{{else}}{{#if isMinor}}{{prettyNewMajor}}{{else}}{{#if isSingleVersion}}{{prettyNewVersion}}{{else}}{{#if newValue}}{{{newValue}}}{{else}}{{{newDigestShort}}}{{/if}}{{/if}}{{/if}}{{/if}}" 17 | }, 18 | { 19 | "groupName": "perlImagesMinor36", 20 | "matchDatasources": ["docker"], 21 | "matchPackageNames": ["perl"], 22 | "minimumReleaseAge": "3 days", 23 | "automerge": true, 24 | "allowedVersions": "/^(5\\.)(36)(\\.\\d)?(.*)/", 25 | "versioning": "regex:^5\\.(?[1-9][02468])\\.(?\\d+)", 26 | "versionCompatibility": "^(?[^-]+)(?-.*)?$", 27 | }, 28 | { 29 | "groupName": "perlImagesMajor", 30 | "matchDatasources": ["docker"], 31 | "matchPackageNames": ["perl"], 32 | "automerge": false, 33 | "minimumReleaseAge": "14 days", 34 | "allowedVersions": "/^5\\.([1-9][02468])(\\.\\d)?(.*)/", 35 | "versioning": "regex:^5\\.(?[1-9][02468])\\.(?\\d+)", 36 | "versionCompatibility": "^(?[^-]+)(?-.*)?$" 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /scripts/excluded_packages.txt: -------------------------------------------------------------------------------- 1 | # Specify excluded packages which are not inclided from CPAN. 2 | # May they are not really a package but specidied like a package. So the automatic removal of these requiremens doesn't work. 3 | # Some packages are also not compatible, so we remove them here 4 | # Every line gets cobined in a regex wir or construct, so regex like instructions can also be applied here 5 | # Every statement must begin at beginning, because ^ is prepended before this lists 6 | 7 | # Remove anything which starts with "FHEM" 8 | FHEM 9 | # Meta isn't a package, it resided in main 10 | META$ 11 | meta$ 12 | Meta$ 13 | # Wrog specified in a SIGNALduino Module. Update in SVN outstanding 14 | Digest:CRC$ 15 | # We build only for linux, so we remove the WIN32, because they are not bound to a feature 16 | Win32:: 17 | # FHEM has a local lib dir, we remove this 18 | lib$ 19 | # YAF seems not to be updated for a long time and makes a problem with IMAGE::Magick 20 | YAF$ 21 | # These are all module wich run in main and not their namespace: 22 | OW$ 23 | RTypes$ 24 | RRDs$ 25 | Slim:: 26 | HM485d::HM485_Protocol$ 27 | lib::HM485:: 28 | lib::OWNet$ 29 | longer$ 30 | myCtrlHAL$ 31 | fhconverter$ 32 | TradfriUtils$ 33 | encode$ 34 | fhwebsocket$ 35 | Device::LIFX 36 | ABFALL_ 37 | SetExtensions$ 38 | HttpUtils$ 39 | UPnP::ControlPoint$ 40 | FritzBoxUtils$ 41 | configDB$ 42 | RESIDENTStk$ 43 | SHC_datafields$ 44 | TcpServerUtils$ 45 | Blocking$ 46 | uConv$ 47 | ZWLib$ 48 | UpNp:Common 49 | HttpUtils$ 50 | Unit$ 51 | DevIo$ 52 | AttrTemplate$ 53 | ProtoThreads$ 54 | # CARP is core, here is something wrong: 55 | carp$ 56 | # Pcakage does not exists (anymore?) on metacpan 57 | JSON::backportPP$ -------------------------------------------------------------------------------- /scripts/get-FHEMRepositorys.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # GitHub API-URL für die Suche nach FHEM-Repositorys 4 | BASE_URL="https://api.github.com/search/repositories" 5 | TOPIC="topic:FHEM" 6 | README='"update+all+https%3A%2F%2Fraw.githubusercontent.com"+OR++in%3Areadme+"update+add+https%3A%2F%2Fraw.githubusercontent.com"' 7 | 8 | # Anzahl der Ergebnisse, die du anzeigen möchtest 9 | PER_PAGE=2000 10 | 11 | in%3Areadme+"update+all+https%3A%2F%2Fraw.githubusercontent.com"+OR++in%3Areadme+"update+add+https%3A%2F%2Fraw.githubusercontent.com" 12 | # API-Anfrage an GitHub 13 | response=$(curl -s "$BASE_URL?q=${TOPIC}+archived:false+${README}&sort=stars&order=desc&per_page=$PER_PAGE") 14 | 15 | 16 | # Verarbeite die Antwort 17 | if [[ $response == *"items"* ]]; then 18 | while IFS= read -r repo; do 19 | repo_name=$(echo "$repo" | jq -r '.name') 20 | repo_url=$(echo "$repo" | jq -r '.html_url') 21 | #stars=$(echo "$repo" | jq -r '.stargazers_count') 22 | [[ ! $repo_name =~ (mirror|docker) ]] && echo "Repository: $repo_name - $repo_url" 23 | done <<< "$(echo "$response" | jq -c '.items[]')" 24 | else 25 | echo "Keine Repositorys gefunden." 26 | fi -------------------------------------------------------------------------------- /scripts/get-Packages.pl: -------------------------------------------------------------------------------- 1 | #!/bin/perl 2 | use strict; 3 | use warnings; 4 | use PPI; 5 | use File::Find::Rule; 6 | use List::MoreUtils qw(uniq); 7 | # use Data::Dumper; 8 | use File::Path qw(make_path); 9 | 10 | my $PPICacheDir; 11 | BEGIN { 12 | $PPICacheDir = '.cache/PPI'; 13 | unless(-d $PPICacheDir) { 14 | make_path($PPICacheDir) or die qq[Directory $PPICacheDir coldn't be created: $!]; 15 | } 16 | }; 17 | 18 | use PPI::Cache path => $PPICacheDir; 19 | 20 | my @directories = @ARGV; 21 | 22 | 23 | my %seen; # Hash, um Duplikate zu verfolgen 24 | my @unique_package_names; 25 | 26 | # Alle Perl-Moduldateien im Verzeichnisbaum finden 27 | foreach my $directory (@directories) { 28 | 29 | my @files = File::Find::Rule->file()->name('*.pm')->in($directory); 30 | 31 | foreach my $file (@files) { 32 | 33 | my $document = PPI::Document->new($file, readonly => 1); 34 | next unless $document; 35 | # Alle package-Anweisungen in der Datei finden 36 | my $package_statements = $document->find('PPI::Statement::Package'); 37 | 38 | # Nur Dateien mit mindestens einer package-Anweisung verarbeiten 39 | next unless $package_statements; 40 | 41 | foreach my $package_statement (@$package_statements) { 42 | my $package_name = $package_statement->namespace; 43 | 44 | next if $seen{$package_name}; 45 | $seen{$package_name} = 1; 46 | 47 | push @unique_package_names, $package_name; 48 | 49 | # print "Paketname: $package_name\n"; 50 | } 51 | } 52 | } 53 | # Paketnamen mit | getrennt ausgeben 54 | my $package_string = join '$|', @unique_package_names; 55 | print "$package_string\$"; 56 | 57 | # Example: 58 | # FHEM_MODULES=$(./scripts/get-Packages.pl src/fhem/trunk/fhem) 59 | -------------------------------------------------------------------------------- /scripts/parse-METAJson.pl: -------------------------------------------------------------------------------- 1 | #!/bin/perl 2 | use strict; 3 | use warnings; 4 | use CPAN::Meta; 5 | use Module::CPANfile; 6 | use Data::Dumper; 7 | use CPAN::Meta::Merge; 8 | use Scalar::Util qw/blessed/; 9 | use File::Find::Rule; 10 | use JSON; 11 | use Perl::PrereqScanner::NotQuiteLite::App; 12 | use Module::CoreList; 13 | 14 | my @directories = @ARGV; 15 | 16 | #my $filename = @ARGV # path must be provided to our script 17 | my @JSONlines; 18 | my $jsonString; 19 | #my $line; 20 | ## Load existing requirements from cpanfile 21 | my $cpanfile = Module::CPANfile->load('cpanfile'); 22 | 23 | sub merge_hashes { 24 | my ($hash1, $hash2) = @_; 25 | for my $key (keys %$hash2) { 26 | if (ref $hash2->{$key} eq 'HASH' && ref $hash1->{$key} eq 'HASH') { 27 | merge_hashes($hash1->{$key}, $hash2->{$key}); 28 | } else { 29 | $hash1->{$key} = $hash2->{$key}; 30 | } 31 | } 32 | return $hash1; 33 | } 34 | 35 | sub filter_nested_hashref { 36 | my $hashref = shift; 37 | my $filter_value = shift; 38 | 39 | foreach my $key (keys %{$hashref}) { 40 | #print "verify $key \n"; 41 | 42 | if (ref $hashref->{$key} eq 'HASH') { 43 | #print "$key->"; 44 | $hashref->{$key} = filter_nested_hashref($hashref->{$key}, $filter_value); 45 | 46 | #print Dumper $hashref->{$key}; 47 | } elsif ( $key =~ $filter_value || Module::CoreList->is_core( $key,undef,5.36) ) 48 | { 49 | #print "\n Deleting $key"; 50 | delete $hashref->{$key}; 51 | #print "... successfull " if ( !exists $hashref->{$key} ) 52 | } 53 | } 54 | return $hashref; 55 | } 56 | 57 | #my $newCPANFile; 58 | # Alle Perl-Moduldateien im Verzeichnisbaum finden 59 | #print Dumper \%ENV; 60 | my $FHEM_MODULES = $ENV{'FHEM_MODULES'} // ""; 61 | my $regex=qr/$FHEM_MODULES/; 62 | print $regex; 63 | foreach my $directory (@directories) { 64 | 65 | 66 | my @files = File::Find::Rule->file()->name('*.pm')->in($directory); 67 | 68 | foreach my $filename (@files) { 69 | $jsonString=""; 70 | @JSONlines=(); 71 | 72 | print qq[\n try processing file $filename ...]; 73 | open(my $fh, '<', $filename) or die "can not open file: $!"; 74 | my $inside_for = 0; 75 | 76 | while (<$fh>) { 77 | if (/^=for :application\/json;q=META.json/) { 78 | $inside_for = 1; 79 | next; 80 | } 81 | if ($inside_for) { 82 | last if /^=end :application\/json;q=META.json/; # Ende des =for-Abschnitts 83 | $_ =~ s/\R//g; 84 | 85 | push @JSONlines, $_; 86 | 87 | } 88 | } 89 | close($fh); 90 | 91 | my $module_requirements; 92 | 93 | if (!@JSONlines) 94 | { 95 | print "aborting, no META.json found\n"; 96 | next; 97 | 98 | # my $app = Perl::PrereqScanner::NotQuiteLite::App->new( 99 | # parsers => [qw/:installed/], 100 | # suggests => 1, 101 | # # recommends => 1, 102 | # # perl_minimum_version => 1, 103 | # exclude_core => 1, 104 | # private_re => $regex, 105 | # ); 106 | # my $scannedprereqs = $app->run($filename); 107 | # $module_requirements = $scannedprereqs->{'prereqs'}; 108 | 109 | } else { 110 | $jsonString = join '', @JSONlines; 111 | ## Script breaks here, because we may have no version field which is requred to pass here 112 | 113 | my $MetaHash; 114 | eval { 115 | $MetaHash = from_json($jsonString) ; 116 | 1; 117 | } or do { 118 | print q[[ failed ]]. $@; 119 | next; 120 | }; 121 | # requirements from the processed file 122 | $module_requirements = filter_nested_hashref($MetaHash->{'prereqs'}, $regex); 123 | } 124 | 125 | 126 | # fix missing version information 127 | 128 | my $cpanfile_requirements = $cpanfile->prereq_specs; # requirements from our cpanfile 129 | 130 | # print Dumper $module_requirements; 131 | # print Dumper $cpanfile_requirements; 132 | # print Dumper $module_requirements; 133 | 134 | # merge requirements together 135 | my $struct = merge_hashes($cpanfile_requirements, $module_requirements); 136 | print "struct: "; 137 | print Dumper $struct; 138 | 139 | $cpanfile = Module::CPANfile->from_prereqs( $struct ); # update cpanfile object 140 | print qq[$filename was processed successfull\n]; 141 | } 142 | } 143 | 144 | # save our new cpanfile 145 | if (defined $cpanfile) 146 | { 147 | #print Dumper $cpanfile; 148 | $cpanfile->save('cpanfile'); 149 | print qq[\n\nResult: cpanfile was saved\n]; 150 | } 151 | 152 | 153 | # example usage 154 | # perl scripts/parse-METAJson.pl src/FHEM/trunk/fhem/FHEM/ 3rdParty/ 155 | # -------------------------------------------------------------------------------- /scripts/test-integration.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cd "$(readlink -f "$(dirname "$BASH_SOURCE")")"/.. 4 | 5 | echo -e "\n\n" 6 | docker images 7 | echo -e "\n\n" 8 | rm -rf ./failed_variants 9 | RETURNCODE=0 10 | 11 | for ID in $( docker images | grep '^fhem/*' | grep -v "" | grep -P ' +[0-9]+\.[0-9]+.+' | awk '{print $3}' | uniq ); do 12 | echo "Booting up container for variant $ID ..." 13 | CONTAINER=$( docker run -d -ti --health-interval=60s --health-timeout=10s --health-start-period=150s --health-retries=5 $ID ) 14 | docker container ls | grep 'fhem/.*' 15 | 16 | echo -ne "Waiting for container ..." 17 | sleep 3 18 | bootstate="created" 19 | until [ $bootstate != "created" ]; do 20 | bootstate=$( docker inspect --format="{{json .State}}" $CONTAINER 2>/dev/null | jq -r .Status ) 21 | echo -n " ." 22 | sleep 3 23 | done 24 | if [ -z "$bootstate" ]; then 25 | RETURNCODE=$(( RETURNCODE+3 )) 26 | status="undefined-error" 27 | elif [ "$bootstate" != "running" ]; then 28 | RETURNCODE=$(( RETURNCODE+2 )) 29 | status=$bootstate 30 | fi 31 | 32 | if [ -z "$status" ]; then 33 | echo -ne "\nWaiting for health status report ..." 34 | healthstate="starting" 35 | until [ $healthstate != "starting" ]; do 36 | healthstate=$( docker inspect --format="{{json .State}}" $CONTAINER 2>/dev/null | jq -r .Health.Status ) 37 | echo -n " ." 38 | sleep 3 39 | done 40 | if [ -n "$healthstate" ] && [ "$healthstate" == "healthy" ]; then 41 | status="OK" 42 | elif [ -n "$healthstate" ] && [ "$healthstate" != "null" ]; then 43 | status=$healthstate 44 | else 45 | echo " not defined" 46 | status="OK" 47 | fi 48 | fi 49 | 50 | if [ "$status" != "OK" ]; then 51 | echo -e "\nImage $ID did come up with unexpected state "$status". Integration test FAILED!\n\n" 52 | docker logs $CONTAINER 53 | docker container rm $CONTAINER --force --volumes 2>&1>/dev/null 54 | docker rmi $ID >/dev/null 55 | echo "$ID $status" >> ./failed_variants 56 | (( RETURNCODE++ )) 57 | else 58 | echo -e "\nImage $ID integration test PASSED.\n\n" 59 | docker container rm $CONTAINER --force --volumes 2>&1>/dev/null 60 | fi 61 | done 62 | 63 | exit $RETURNCODE 64 | -------------------------------------------------------------------------------- /src/FHEM/99_DockerImageInfo.pm: -------------------------------------------------------------------------------- 1 | package main; 2 | 3 | use strict; 4 | use warnings; 5 | use FHEM::Meta; 6 | use List::Util qw( first ); 7 | 8 | sub DockerImageInfo_Initialize { 9 | my ($hash) = @_; 10 | 11 | $hash->{NOTIFYDEV} = q[global]; # limit calls to notify 12 | $hash->{DefFn} = \&DockerImageInfo_Define; 13 | $hash->{NotifyFn} = \&DockerImageInfo_Notify; 14 | $hash->{UndefFn} = \&DockerImageInfo_Undefine; 15 | $hash->{AttrList} = $readingFnAttributes; 16 | 17 | return FHEM::Meta::InitMod( __FILE__, $hash ); 18 | } 19 | 20 | ################################### 21 | sub DockerImageInfo_Define { 22 | my ( $hash, $def ) = @_; 23 | my @a = split( "[ \t][ \t]*", $def ); 24 | my $name = $hash->{NAME}; 25 | 26 | return q[Wrong syntax: use define DockerImageInfo] 27 | if ( int(@a) != 2 ); 28 | 29 | return q[This module may only be defined once, existing device: ]. $modules{ $hash->{TYPE} }{defptr}{NAME} 30 | if ( defined( $modules{ $hash->{TYPE} }{defptr} ) 31 | && $init_done 32 | && !defined( $hash->{OLDDEF} ) ); 33 | 34 | # Initialize the module and the device 35 | return $@ unless ( FHEM::Meta::SetInternals($hash) ); 36 | 37 | # create global unique device definition 38 | $modules{ $hash->{TYPE} }{defptr} = $hash; 39 | 40 | $hash->{INFO_DIR} = q[/tmp]; 41 | #$hash->{INFO_DIR} = "/fhem-docker/var"; # TODO: Clean up dumping all files in the container root 42 | $hash->{URL_FILE} = qq[$hash->{INFO_DIR}/health-check.urls]; 43 | $hash->{RESULT_FILE} = qq[$hash->{INFO_DIR}/health-check.result]; 44 | 45 | if ( $init_done && !defined( $hash->{OLDDEF} ) ) { 46 | 47 | # presets for FHEMWEB 48 | $attr{$name}{alias} = 'Docker Image Info'; 49 | $attr{$name}{devStateIcon} = q[^ok.*:security@green Initialized:system_fhem_reboot@orange .*:message_attention@red]; 50 | $attr{$name}{group} = 'Update'; 51 | $attr{$name}{icon} = 'docker'; 52 | $attr{$name}{room} = 'System'; 53 | } 54 | 55 | if ( -e '/.dockerenv' ) { 56 | unlink( $hash->{URL_FILE}); 57 | $hash->{STATE} = "Initialized"; 58 | DockerImageInfo_GetImageInfo( $hash); 59 | } 60 | else { 61 | $hash->{STATE} = q[ERROR: Host is not a container]; 62 | } 63 | 64 | return undef; 65 | } 66 | 67 | 68 | sub DockerImageInfo_Notify 69 | { 70 | my ($hash,$dev) = @_; 71 | 72 | return if($dev->{NAME} ne q[global]); 73 | 74 | my $events = deviceEvents($dev,1); 75 | if( defined first { $events->[$_] =~ /^INITIALIZED|REREADCFG$/ || $events->[$_] =~ /^ATTR\s.*\s(?:HTTPS|webname|DockerHealthCheck)\s.+/ } 0..$#{$events} ) { 76 | RemoveInternalTimer($hash); # Stop Timer because we start one again 77 | 78 | # Update available infos 79 | DockerImageInfo_GetImageInfo( $hash); 80 | 81 | 82 | foreach ( devspec2array(q[TYPE=FHEMWEB:FILTER=TEMPORARY!=1]) ) { 83 | # add userattr to FHEMWEB devices to control healthcheck 84 | addToDevAttrList( $_, q[DockerHealthCheck:0,1] ); 85 | } 86 | 87 | my $urlFile = $hash->{URL_FILE}; 88 | my $urlFileHdl; 89 | if(!open($urlFileHdl, ">$urlFile")) { 90 | my $msg = q[WriteStatefile: Cannot open $urlFile: $!]; 91 | Log3 $hash->{NAME}, 1, $msg; 92 | return $msg; 93 | } 94 | binmode($urlFileHdl, ':encoding(UTF-8)') if($unicodeEncoding); 95 | foreach ( devspec2array(q[TYPE=FHEMWEB:FILTER=TEMPORARY!=1:FILTER=DockerHealthCheck!=0]) ) { 96 | # build url and write it to healthcheck file 97 | my $webHash = $defs{$_}; 98 | my $port = $webHash->{PORT}; 99 | my $webname = AttrVal( $_, 'webname', 'fhem'); 100 | my $https = AttrVal( $_, 'HTTPS', '0'); 101 | my $proto = ($https) ? 'https' : 'http'; 102 | print $urlFileHdl qq[$proto://localhost:$port/$webname/healthcheck\n]; 103 | } 104 | close($urlFileHdl); 105 | 106 | InternalTimer(gettimeofday()+30, \&DockerImageInfo_GetStatus, $hash); 107 | } 108 | 109 | return undef; 110 | } 111 | 112 | 113 | sub DockerImageInfo_Undefine { 114 | my ( $hash, $def ) = @_; 115 | unlink( $hash->{URL_FILE}); 116 | delete $modules{'DockerImageInfo'}{defptr}; 117 | } 118 | 119 | 120 | sub DockerImageInfo_GetStatus { 121 | my ( $hash ) = @_; 122 | 123 | InternalTimer(gettimeofday()+30, \&DockerImageInfo_GetStatus, $hash); 124 | 125 | my $resultFile = $hash->{RESULT_FILE}; 126 | my $resultFileHdl; 127 | if(!open($resultFileHdl, "<$resultFile")) { 128 | my $msg = qq[Read result file: Cannot open $resultFile: $!]; 129 | Log3 $hash->{NAME}, 1, $msg; 130 | $hash->{STATE} = $msg; 131 | return undef; 132 | } 133 | $hash->{STATE} = do { local $/; <$resultFileHdl> }; 134 | close( $resultFileHdl); 135 | 136 | return undef; 137 | } 138 | 139 | 140 | sub DockerImageInfo_GetImageInfo { 141 | my ($hash) = @_; 142 | 143 | readingsBeginUpdate( $hash ); 144 | 145 | my $NAME; 146 | my $VAL; 147 | my @LINES = split( "\n", `sort -k1,1 -t'=' --stable --unique /image_info.* /image_info` ); 148 | 149 | foreach my $LINE (@LINES) { 150 | next unless ( $LINE =~ /^org\.opencontainers\..+=.+$/i ); 151 | my @NV = split( "=", $LINE ); 152 | $NAME = shift @NV; 153 | $NAME =~ s/^org\.opencontainers\.//i; 154 | $VAL = join( "=", @NV ); 155 | next if ( $NAME eq "image.authors" ); 156 | readingsBulkUpdateIfChanged( $hash, $NAME, $VAL ); 157 | } 158 | 159 | $VAL = '[ '; 160 | @LINES = split( "\n", `sort --stable --unique /etc/sudoers.d/fhem*` ); 161 | foreach my $LINE (@LINES) { 162 | $VAL .= ', ' unless ( $VAL eq '[ ' ); 163 | $LINE =~ s/"/\\"/g; 164 | $VAL .= "\"$LINE\""; 165 | } 166 | $VAL .= ' ]'; 167 | readingsBulkUpdateIfChanged( $hash, 'sudoers', $VAL ); 168 | 169 | my $ID = `id`; 170 | if ( $ID =~ m/^uid=(\d+)\((\w+)\)\s+gid=(\d+)\((\w+)\)\s+groups=((?:\d+\(\w+\),)*(?:\d+\(\w+\)))$/i ) 171 | { 172 | readingsBulkUpdateIfChanged( $hash, 'id.uid', $1 ); 173 | readingsBulkUpdateIfChanged( $hash, 'id.uname', $2 ); 174 | readingsBulkUpdateIfChanged( $hash, 'id.gid', $3 ); 175 | readingsBulkUpdateIfChanged( $hash, 'id.gname', $4 ); 176 | 177 | $VAL = '[ '; 178 | foreach my $group ( split( ',', $5 ) ) { 179 | if ( $group =~ m/^(\d+)\((\w+)\)$/ ) { 180 | $VAL .= ', ' unless ( $VAL eq '[ ' ); 181 | $VAL .= "\"$2\": $1"; 182 | } 183 | } 184 | $VAL .= ' ]'; 185 | 186 | readingsBulkUpdateIfChanged( $hash, 'id.groups', $VAL ); 187 | } 188 | 189 | readingsBulkUpdateIfChanged( $hash, q[ssh-id_ed25519.pub], `cat ./.ssh/id_ed25519.pub` ); 190 | readingsBulkUpdateIfChanged( $hash, q[ssh-id_rsa.pub], `cat ./.ssh/id_rsa.pub` ); 191 | readingsBulkUpdateIfChanged( $hash, q[container.hostname], `cat /etc/hostname` ); 192 | readingsBulkUpdateIfChanged( $hash, q[container.cap.e], `cat /docker.container.cap.e` ); 193 | readingsBulkUpdateIfChanged( $hash, q[container.cap.p], `cat /docker.container.cap.p` ); 194 | readingsBulkUpdateIfChanged( $hash, q[container.cap.i], `cat /docker.container.cap.i` ); 195 | readingsBulkUpdateIfChanged( $hash, q[container.id], `cat /docker.container.id` ); 196 | readingsBulkUpdateIfChanged( $hash, q[container.privileged], `cat /docker.privileged` ); 197 | readingsBulkUpdateIfChanged( $hash, q[container.hostnetwork], `cat /docker.hostnetwork` ); 198 | 199 | readingsEndUpdate( $hash, 1 ); 200 | } 201 | 202 | 1; 203 | 204 | =pod 205 | =encoding utf8 206 | =item helper 207 | =item summary Kommunikationsmodul zwischen Docker Umgebung und FHEM 208 | =item summary_DE Commumnication between docker environment and FHEM 209 | 210 | =begin html 211 | 212 | 213 |

DockerImageInfo

214 |
    215 | 216 | Show infos about the Docker image FHEM is running in and allows the configuration of the WEB definitions used for healthcheck. 217 | Only works together with the fhem-docker image from https://github.com/fhem/fhem-docker/ . 218 |

    219 | 220 | 221 | Define 222 |
      223 | define <name> DockerImageInfo 224 |

      225 | 226 | Example: 227 |
        228 | define DockerImageInfo DockerImageInfo 229 |
      230 |
    231 |
    232 | 233 | 234 | attr 235 |
      236 | attr <name> <attribute> <value> 237 |

      238 | See commandref#attr for more info about 239 | the attr command. 240 |

      241 | Attributes: 242 |
        243 |
      • DockerHealthCheck 0|1
        244 | Attribute is available in all definitions of type WEB. 245 | Default is, every definition is used for the healthcheck. 246 | The behaviuor can be diabled with this attribute. 247 |
      • 248 |
      249 |
    250 | 251 |
252 | 253 | =end html 254 | 255 | =begin html_DE 256 | 257 | 258 |

DockerImageInfo

259 |
    260 | 261 | Zeigt Informationen über das Docker Image, in dem FHEM gerade läft und ermöglicht die Konfiguration der WEB Definitionen für den Healthcheck 262 | Funktioniert mit dem fhem-docker image von https://github.com/fhem/fhem-docker . 263 |

    264 | 265 | 266 | Define 267 |
      268 | define <name> DockerImageInfo 269 |

      270 | 271 | Example: 272 |
        273 | define DockerImageInfo DockerImageInfo 274 |
      275 |
    276 | 277 | 278 | attr 279 |
      280 | attr <name> <attribute> <value> 281 |

      282 | See commandref#attr für mehr Information zum attr Kommando. 283 |

      284 | Attributes: 285 |
        286 |
      • DockerHealthCheck 0|1
        287 | Das Attribute wird in allen Definition des Typs WEB bereitgestellt. 288 | Der Standardwert ist, dass jede WEB Definition für den Healthcheck verwendet wird. 289 | Mit diesem Attribut, kann der Healthcheck auf eine Webdefinition deaktiviert werden. 290 |
      • 291 |
      292 |
    293 | 294 |
    295 | 296 |
297 | 298 | =end html_DE 299 | 300 | =for :application/json;q=META.json 99_DockerImageInfo.pm 301 | { 302 | "version": "v1.0.0", 303 | "x_release_date": "2023-11-09", 304 | "name": "99_DockerImageInfo.pm", 305 | "release_status": "stable", 306 | "license": [ 307 | "MIT" 308 | ], 309 | "abstract": "Shows information about the FHEM Docker Image in use and the running container", 310 | "description": "This is a companion FHEM module and built-in to the official FHEM Docker Image on Docker Hub.", 311 | "x_lang": { 312 | "de": { 313 | "abstract": "Zeigt Informationen über das aktuell verwendete FHEM Docker Image und den laufenden Container", 314 | "description": "Dies ist ein begeleitendes FHEM Modul und fest im FHEM Docker Image Docker Hub eingebaut." 315 | } 316 | }, 317 | "author": [ 318 | "Julian Pawlowski " 319 | ], 320 | "x_fhem_maintainer": [ 321 | "loredo" 322 | ], 323 | "x_fhem_maintainer_github": [ 324 | "sidey79" 325 | ], 326 | "prereqs": { 327 | "runtime": { 328 | "requires": { 329 | "strict": 0, 330 | "warnings": 0, 331 | "FHEM::Meta": 0.001006, 332 | "List::Util" : 1.18 333 | }, 334 | "recommends": { 335 | }, 336 | "suggests": { 337 | } 338 | } 339 | }, 340 | "resources": { 341 | "license": [ 342 | "https://github.com/fhem/fhem-docker/blob/master/LICENSE" 343 | ], 344 | "homepage": "https://github.com/fhem/fhem-docker/", 345 | "x_support_community" : { 346 | "board" : "Server - Linux", 347 | "boardId" : 33, 348 | "cat" : "FHEM - Hardware", 349 | "description" : "FHEM auf Linux Servern", 350 | "forum" : "FHEM Forum", 351 | "language" : "de", 352 | "rss" : "https://forum.fhem.de/index.php?action=.xml;type=rss;board=33", 353 | "title" : "FHEM Forum: Server - Linux", 354 | "web" : "https://forum.fhem.de/index.php/board,33.0.html" 355 | }, 356 | "bugtracker": { 357 | "web": "https://github.com/fhem/fhem-docker/issues", 358 | "x_web_title": "Github Issues for fhem/fhem-docker" 359 | }, 360 | "repository": { 361 | "type": "git", 362 | "url": "https://github.com/fhem/fhem-docker.git", 363 | "web": "https://github.com/fhem/fhem-docker/blob/master/src/99_DockerImageInfo.pm", 364 | "x_branch": "master", 365 | "x_filepath": "src/", 366 | "x_raw": "https://github.com/fhem/fhem-docker/raw/master/src/99_DockerImageInfo.pm", 367 | "x_dev": { 368 | "type": "git", 369 | "url": "https://github.com/fhem/fhem-docker.git", 370 | "web": "https://github.com/fhem/fhem-docker/blob/dev/src/99_DockerImageInfo.pm", 371 | "x_branch": "dev", 372 | "x_filepath": "src/", 373 | "x_raw": "https://github.com/fhem/fhem-docker/raw/dev/src/99_DockerImageInfo.pm" 374 | } 375 | } 376 | } 377 | } 378 | =end :application/json;q=META.json 379 | 380 | =cut 381 | -------------------------------------------------------------------------------- /src/health-check.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | #--- Constants ------------------------------------------------------------------------------------------------------- 5 | 6 | declare -r PID_FILE="/var/run/health-check.pid" 7 | declare -r URL_FILE="/tmp/health-check.urls" 8 | declare -r RESULT_FILE="/tmp/health-check.result" 9 | 10 | 11 | #--- Internal global ------------------------------------------------------------------------------------------------- 12 | 13 | declare -i gRetVal=0; 14 | declare gRetMessage=""; 15 | declare -i gSuccessCnt=0; 16 | declare -i gFailedCnt=0; 17 | 18 | 19 | #====================================================================================================================- 20 | #--- Functions ------------------------------------------------------------------------------------------------------- 21 | 22 | # Handler function for actually stopping this script. Called either by 23 | # - "exit" was called somewhere in the script 24 | # - SIGTERM is received 25 | # 26 | # Usage: trapExitHandler 27 | # Global vars: gRetMessage 28 | # gSuccessCnt 29 | # gFailedCnt 30 | # RESULT_FILE 31 | # PID_FILE 32 | # 33 | function trapExitHandler() { 34 | local -i exitVal=$? # when "exit" was called, this holds the return value 35 | trap - SIGTERM EXIT # Avoid multiple calls to handler 36 | echo "$gRetMessage" 37 | if (($exitVal == 0)) ; then 38 | echo -n "ok ($gSuccessCnt successful, $gFailedCnt failed)" > $RESULT_FILE 39 | else 40 | echo -n "ERROR ($gSuccessCnt successful, $gFailedCnt failed)" > $RESULT_FILE 41 | fi 42 | exit $exitVal 43 | } 44 | 45 | 46 | #====================================================================================================================- 47 | #--- Main script ----------------------------------------------------------------------------------------------------- 48 | 49 | 50 | ( 51 | trap trapExitHandler SIGTERM EXIT 52 | 53 | # Wait 3 seconds for lock on $PID_FILE (fd 212), exit on failure 54 | flock -x -w 3 212 || { echo "Instance already running, aborting another one" ; exit 1; } 55 | 56 | [ -e $URL_FILE ] || { gRetMessage="Cannot read url file $URL_FILE" ; exit 1; } 57 | while IFS= read -r fhemUrl; do 58 | fhemwebState=$( curl \ 59 | --connect-timeout 5 \ 60 | --max-time 8 \ 61 | --silent \ 62 | --insecure \ 63 | --output /dev/null \ 64 | --write-out "%{http_code}" \ 65 | --user-agent 'FHEM-Docker/1.0 Health Check' \ 66 | "${fhemUrl}" ) 67 | if [ $? -ne 0 ] || 68 | [ -z "${fhemwebState}" ] || 69 | [ "${fhemwebState}" == "000" ] || 70 | [ "${fhemwebState:0:1}" == "5" ]; then 71 | gRetMessage="$gRetMessage $fhemUrl: FAILED ($fhemwebState);" 72 | gRetVal=1 73 | ((gFailedCnt++)) 74 | else 75 | gRetMessage="$gRetMessage $fhemUrl: OK;" 76 | ((gSuccessCnt++)) 77 | fi 78 | done < $URL_FILE 79 | 80 | exit $gRetVal 81 | ) 212>${PID_FILE} 82 | 83 | -------------------------------------------------------------------------------- /src/ssh_known_hosts.txt: -------------------------------------------------------------------------------- 1 | # ssh-keyscan -p 58824 fhem-va.fhem.de 2>/dev/null 2 | [fhem-va.fhem.de]:58824,[88.99.31.202]:58824,[2a01:4f8:10a:806::f2]:58824 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCcTyjPHgeBFgxtOmq8aZWOXyJU1K57cGyllhV1YhIbzM8MAjdhLV54vTcDKyoDszjug24luyU+OnCHfgyeo9mFZdM93vJI0LrB8fQWSHXD2tjBhDwxGhxm0ksqlDFP3h3ZFP6HoXzrOP69ucqLSKv8/cfkvpp2kfxRMxHGjsfroNHOHmwUtBy80wh/XNcUikOBqQ7aZiCQWGkdJLHEFWgTT1VQ9P7ZkNe33bG+TICc4LF05DqDIZbD4zqhfKj/oNCgmm+vNQU2GQc/FZBjxO6+qtkcO7Ne0kksI3L2xcEyvkJOm1GUvwB0tDJQSiNfbc6lEzEZx6MOUL3SY00WtJHd 3 | -------------------------------------------------------------------------------- /src/tests/bats/aptInstall.bats: -------------------------------------------------------------------------------- 1 | 2 | #!/usr/bin/env bats 3 | 4 | setup() { 5 | load '/opt/bats/test_helper/bats-support/load.bash' 6 | load '/opt/bats/test_helper/bats-assert/load.bash' 7 | load '/opt/bats/test_helper/bats-file/load.bash' 8 | load '/opt/bats/test_helper/bats-mock/load.bash' 9 | } 10 | 11 | 12 | setup_file() { 13 | [ -z ${GITHUB_RUN_ID+x} ] || echo '::group::aptInstall Tests' >&3 14 | 15 | export BATS_TEST_TIMEOUT=60 16 | export LOG_FILE="${BATS_SUITE_TMPDIR}/log" 17 | 18 | set -a 19 | source /entry.sh 20 | set +a 21 | } 22 | 23 | teardown_file() { 24 | sleep 0 25 | [ -z ${GITHUB_RUN_ID+x} ] || echo '::endgroup::' >&3 26 | 27 | } 28 | 29 | 30 | 31 | teardown() { 32 | DEBIAN_FRONTEND=noninteractive apt-get remove dummydroid -y && apt-get autoremove -y # cleanup 33 | } 34 | 35 | # bats test_tags=integrationTest 36 | @test "check aptInstall() new package" { 37 | bats_require_minimum_version 1.5.0 38 | 39 | gAptUpdateHasRun=0 40 | local installLog=${BATS_TEST_TMPDIR}/aptInstall.log 41 | run -0 aptInstall "test message" ${installLog} dummydroid 42 | 43 | cat ${installLog} 44 | assert_file_contains ${installLog} "Get:" grep 45 | assert_file_contains ${installLog} "Fetched " grep 46 | assert_file_contains ${installLog} "Setting up dummydroid" grep 47 | assert_output --partial "test message" 48 | 49 | } 50 | 51 | # bats test_tags=integrationTest 52 | @test "check aptInstall() already installed package" { 53 | bats_require_minimum_version 1.5.0 54 | 55 | gAptUpdateHasRun=0 56 | local installLog=${BATS_TEST_TMPDIR}/aptInstall.log 57 | run -0 aptInstall "test message2" ${installLog} grep 58 | cat ${installLog} 59 | assert_file_contains ${installLog} "grep is already the newest version" grep 60 | assert_output --partial "test message2" 61 | } 62 | 63 | # bats test_tags=integrationTest 64 | @test "check aptInstall() twice executed" { 65 | bats_require_minimum_version 1.5.0 66 | 67 | # remove package lists 68 | rm -rf /var/lib/apt/lists/* 69 | 70 | # First Update 71 | gAptUpdateHasRun=0 72 | export gAptUpdateHasRun 73 | local installLog=${BATS_TEST_TMPDIR}/aptInstall.log 74 | run -0 aptInstall "test message2" ${installLog} dummydroid 75 | cat ${installLog} 76 | assert_file_contains ${installLog} "Get:" grep # Packagelist is downloaded 77 | assert_file_not_contains ${installLog} "Hit:" grep 78 | 79 | # Second Update 80 | gAptUpdateHasRun=0 81 | export gAptUpdateHasRun 82 | local installLog=${BATS_TEST_TMPDIR}/aptInstall2.log 83 | run -0 aptInstall "test message3" ${installLog} dummydroid 84 | assert_file_contains ${installLog} "Hit:" grep # Packagelist was already there 85 | assert_file_not_contains ${installLog} "Get:" grep 86 | assert_output --partial "test message3" 87 | 88 | # Update is skipped 89 | gAptUpdateHasRun=1 90 | export gAptUpdateHasRun 91 | local installLog=${BATS_TEST_TMPDIR}/aptInstall3.log 92 | run -0 aptInstall "test message4" ${installLog} dummydroid 93 | assert_file_not_contains ${installLog} "Hit:" grep # no update command was run 94 | assert_file_not_contains ${installLog} "Get:" grep # no update command was run 95 | assert_output --partial "test message4" 96 | } -------------------------------------------------------------------------------- /src/tests/bats/entry.bats: -------------------------------------------------------------------------------- 1 | 2 | #!/usr/bin/env bats 3 | 4 | setup() { 5 | load '/opt/bats/test_helper/bats-support/load.bash' 6 | load '/opt/bats/test_helper/bats-assert/load.bash' 7 | load '/opt/bats/test_helper/bats-file/load.bash' 8 | load '/opt/bats/test_helper/bats-mock/load.bash' 9 | 10 | # Sometimes perl or grep does not terminate, we will clean up 11 | pkill tail || true 12 | pkill grep || true 13 | } 14 | 15 | 16 | setup_file() { 17 | [ -z ${GITHUB_RUN_ID+x} ] || echo '::group::function tests' >&3 18 | export BATS_TEST_TIMEOUT=60 19 | export LOG_FILE="${BATS_SUITE_TMPDIR}/log" 20 | 21 | set -a 22 | source /entry.sh 23 | set +a 24 | } 25 | 26 | teardown_file() { 27 | sleep 0 28 | 29 | rm -f /tmp/log 30 | rm -rf /opt/fhem/* 31 | [ -z ${GITHUB_RUN_ID+x} ] || echo '::endgroup::' >&3 32 | } 33 | 34 | 35 | 36 | teardown() { 37 | # cat /opt/fhem/fhem.cfg 38 | rm -rf /opt/fhem/* 39 | rm -rf /usr/src/fhem # why is no cleanup in entry.sh? 40 | 41 | mkdir -p /fhem/FHEM 42 | cp /tmp/fhem/FHEM/* /fhem/FHEM/ 43 | } 44 | 45 | 46 | # bats test_tags=unitTest 47 | @test "printf info tests" { 48 | run printfInfo 'test output' 49 | assert_output 'INFO: test output' 50 | } 51 | 52 | # bats test_tags=unitTest 53 | @test "printf debug tests" { 54 | declare -i gEnableDebug=1 55 | run printfDebug 'test output' 56 | assert_output 'DEBUG: bats_merge_stdout_and_stderr: test output' 57 | } 58 | 59 | # bats test_tags=unitTest 60 | @test "check prependFhemDirPath()" { 61 | 62 | run bash -c 'OUT=$(prependFhemDirPath "") ; echo $OUT' 63 | assert_output "/opt/fhem" 64 | 65 | run bash -c 'OUT=$(prependFhemDirPath "./logs/fhem-1-2-3.log") ; echo $OUT' 66 | assert_output "/opt/fhem/logs/fhem-1-2-3.log" 67 | run bash -c 'OUT=$(prependFhemDirPath "/opt/fhem/logs/fhem-1-2-3.log") ; echo $OUT' 68 | assert_output "/opt/fhem/logs/fhem-1-2-3.log" 69 | 70 | run bash -c 'OUT=$(prependFhemDirPath "/run/lock/fhem.pid") ; echo $OUT' 71 | assert_output "/opt/fhem/run/lock/fhem.pid" 72 | 73 | } 74 | 75 | # bats test_tags=unitTest 76 | @test "check fhemUpdateInstall()" { 77 | export FHEM_DIR=${BATS_TEST_TMPDIR}"/fhemUpdateInstall" 78 | mkdir -p ${FHEM_DIR}/FHEM 79 | 80 | run fhemUpdateInstall 81 | assert_output "INFO: Updating existing FHEM installation in ${FHEM_DIR}" 82 | assert_file_exists /fhem/FHEM/99_DockerImageInfo.pm 83 | assert_file_exists ${FHEM_DIR}/FHEM/99_DockerImageInfo.pm 84 | 85 | #rm -r ${FHEM_DIR} 86 | } 87 | 88 | # bats test_tags=unitTest 89 | @test "ceck tailFileToConsoleStop() Logfile monitoring" { 90 | # mock some functions 91 | LOGFILE="fhem-%Y-%m-%d.log" 92 | realLogFile="/tmp/$( date +"${LOGFILE}")" 93 | export gCurrentTailPid=0 94 | function getFhemPidNum() { 95 | echo "1" 96 | } 97 | 98 | tailFileToConsoleStart ${realLogFile} -b 99 | run tailFileToConsoleStop 100 | 101 | echo $gCurrentTailPid | assert_output "" 102 | } 103 | 104 | # bats test_tags=unitTest 105 | @test "ceck tailFileToConsoleStart() Logfile monitoring" { 106 | # mock some functions 107 | function getFhemPidNum() { 108 | echo "1" 109 | } 110 | 111 | export LOGFILE="fhem-%Y-%m-%d.log" 112 | export realLogFile="${BATS_TEST_TMPDIR}/$( date +"${LOGFILE}")" 113 | export gCurrentTailPid=0 114 | export TAIL_PID= 115 | 116 | #touch ${realLogFile} 117 | 118 | run bash -c 'tailFileToConsoleStart ${realLogFile} -b; sleep 1; tailFileToConsoleStop' 119 | assert_output "" 120 | 121 | echo "hello" > $realLogFile 122 | run bash -c 'tailFileToConsoleStart ${realLogFile} -b; sleep 1; tailFileToConsoleStop' 123 | assert_output "hello" 124 | 125 | 126 | run bash -c 'tailFileToConsoleStart ${realLogFile}; sleep 1; echo "again" >> $realLogFile; sleep 1; tailFileToConsoleStop' 127 | assert_output "again" 128 | refute_output "hello" 129 | } 130 | 131 | # bats test_tags=integrationTest 132 | @test "Setup clean install FHEM" { 133 | 134 | run fhemCleanInstall 135 | assert_output --partial "Installing FHEM to ${FHEM_DIR}" 136 | assert_file_exists ${FHEM_DIR}/fhem.pl 137 | assert_file_exists ${FHEM_DIR}/fhem.cfg.default 138 | assert_file_exists ${FHEM_DIR}/FHEM/99_DockerImageInfo.pm 139 | 140 | #assert_file_contains /opt/fhem/fhem.cfg attr global mseclog 1 grep 141 | 142 | } 143 | 144 | # bats test_tags=unitTest 145 | @test "verify is_absolutePath" { 146 | bats_require_minimum_version 1.5.0 147 | 148 | export -f is_absolutePath 149 | 150 | run -0 bash -c 'is_absolutePath /opt/fhem' 151 | run -0 bash -c 'is_absolutePath /run/lock/file' 152 | run -1 bash -c 'is_absolutePath ./log/' 153 | run -1 bash -c 'is_absolutePath ../log/' 154 | run -1 bash -c 'is_absolutePath .' 155 | run -1 bash -c 'is_absolutePath ' 156 | } 157 | 158 | 159 | # bats test_tags=unitTest 160 | @test "verify getFHEMRevision" { 161 | bats_require_minimum_version 1.5.0 162 | 163 | #export -f getFHEMRevision 164 | 165 | run fhemCleanInstall 166 | assert_file_exists ${FHEM_DIR}/fhem.pl 167 | 168 | assert [ "$(getFHEMRevision)" -gt 0 ] 169 | 170 | # Patch fixed revision id 171 | sed -i 's/[0-9]\+/20000/1' ${FHEM_DIR}/fhem.pl 172 | assert [ "$(getFHEMRevision)" -eq 20000 ] 173 | 174 | # Patch wrong revision id 175 | sed -i 's/[0-9]\+/ddd/1' ${FHEM_DIR}/fhem.pl 176 | assert [ "$(getFHEMRevision)" -eq 0 ] 177 | } 178 | 179 | 180 | # bats test_tags=unitTest 181 | @test "verify initialContainerSetup without error" { 182 | run initialContainerSetup 183 | assert_output --partial "Preparing initial container setup" 184 | assert_output --partial "Initial container setup done" 185 | refute_output --partial 'ERROR' 186 | } 187 | 188 | # bats test_tags=unitTest 189 | @test "verify initialContainerSetup with to old fhem installation" { 190 | fhemCleanInstall 191 | sed -i 's/[0-9]\+/20000/1' ${FHEM_DIR}/fhem.pl 192 | run initialContainerSetup 193 | 194 | assert_output --partial "is to old" 195 | assert_output --partial "Your container will soon be terminated" 196 | assert_output --partial 'ERROR' 197 | } 198 | 199 | 200 | @test "verify directory permissions fhem directory" { 201 | assert_not_file_owner fhem ${FHEM_DIR} 202 | 203 | run prepareFhemUser 204 | 205 | assert_file_owner fhem ${FHEM_DIR} 206 | } 207 | 208 | @test "verify directory permissions /dev/tty[0-9] directory" { 209 | DIR_TEST="/dev/tty5" 210 | mkdir -p "${DIR_TEST}" 211 | 212 | run prepareFhemUser 213 | 214 | assert_equal " tty" "$(printf "%s" "$(stat "-c %G" "${DIR_TEST}" )" )" 215 | } 216 | 217 | @test "verify directory permissions /dev/ttyACM directory" { 218 | DIR_TEST="/dev/ttyACM" 219 | mkdir -p "${DIR_TEST}" 220 | 221 | run prepareFhemUser 222 | 223 | assert_equal " dialout" "$(printf "%s" "$(stat "-c %G" "${DIR_TEST}" )" )" 224 | } 225 | 226 | @test "verify directory permissions /dev/gpio3 directory" { 227 | DIR_TEST="/dev/gpio3" 228 | mkdir -p "${DIR_TEST}" 229 | 230 | run prepareFhemUser 231 | 232 | assert_equal " gpio" "$(printf "%s" "$(stat "-c %G" "${DIR_TEST}" )" )" 233 | } -------------------------------------------------------------------------------- /src/tests/bats/health-check.bats: -------------------------------------------------------------------------------- 1 | 2 | #!/usr/bin/env bats 3 | 4 | setup() { 5 | load '/opt/bats/test_helper/bats-support/load.bash' 6 | load '/opt/bats/test_helper/bats-assert/load.bash' 7 | load '/opt/bats/test_helper/bats-file/load.bash' 8 | load '/opt/bats/test_helper/bats-mock/load.bash' 9 | } 10 | 11 | setup_file() { 12 | [ -z ${GITHUB_RUN_ID+x} ] || echo '::group::Healtcheck Tests' >&3 13 | export LOG_FILE="${BATS_SUITE_TMPDIR}/log" 14 | } 15 | 16 | teardown_file() { 17 | mkdir -p /fhem/FHEM 18 | cp /tmp/fhem/FHEM/* /fhem/FHEM/ 19 | [ -z ${GITHUB_RUN_ID+x} ] || echo '::endgroup::' >&3 20 | } 21 | 22 | teardown() { 23 | rm -rf ${FHEM_DIR}/* 24 | rm -rf /usr/src/fhem # why is no cleanup in entry.sh? 25 | 26 | # Sometimes perl or grep does not terminate, we will clean up 27 | pkill entry.sh || true 28 | pkill perl || true 29 | } 30 | 31 | # bats test_tags=unitTest 32 | @test "healthcheck without url file" { 33 | bats_require_minimum_version 1.5.0 34 | 35 | run -1 /health-check.sh 36 | assert_output --partial "Cannot read url file" 37 | } 38 | 39 | # bats test_tags=unitTest 40 | @test "healthcheck without running fhem" { 41 | bats_require_minimum_version 1.5.0 42 | 43 | echo "https://localhost:8083/fhem/" > /tmp/health-check.urls 44 | run -1 /health-check.sh 45 | assert_output --partial "https://localhost:8083/fhem/" 46 | assert_output --partial "FAILED" 47 | 48 | rm -r /tmp/health-check.urls 49 | } 50 | 51 | # bats test_tags=integrationTest 52 | @test "healthcheck with running fhem" { 53 | bats_require_minimum_version 1.5.0 54 | 55 | cd ${FHEM_DIR} && /entry.sh start &> ${LOG_FILE} & 56 | export ENTRY_PID=$! 57 | sleep 6 58 | 59 | 60 | while ! nc -vz localhost 8083 > /dev/null 2>&1 ; do 61 | # echo sleeping 62 | sleep 0.5 63 | ((c++)) && ((c==50)) && echo "# fhem did not start" && break 64 | done 65 | sleep 5 66 | #cat $FHEM_CFG_FILE 67 | cat ${LOG_FILE} 68 | assert_file_contains /tmp/health-check.urls "http://localhost:8083" 69 | 70 | run timeout 15 /health-check.sh 71 | assert_output --partial "http://localhost:8083/fhem/" 72 | assert_output --partial "OK" 73 | 74 | kill $ENTRY_PID # fail it the process already finished due to error! 75 | } -------------------------------------------------------------------------------- /src/tests/bats/logfile.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | setup() { 4 | load '/opt/bats/test_helper/bats-support/load.bash' 5 | load '/opt/bats/test_helper/bats-assert/load.bash' 6 | load '/opt/bats/test_helper/bats-file/load.bash' 7 | load '/opt/bats/test_helper/bats-mock/load.bash' 8 | } 9 | 10 | setup_file() { 11 | [ -z ${GITHUB_RUN_ID+x} ] || echo '::group::Logfile Tests' >&3 12 | 13 | export BATS_TEST_TIMEOUT=60 14 | export LOG_FILE="${BATS_SUITE_TMPDIR}/log" 15 | export CONFIGTYPE="fhem.cfg" 16 | 17 | set -a 18 | source /entry.sh 19 | set +a 20 | } 21 | 22 | teardown_file() { 23 | sleep 0 24 | rm -f /tmp/log 25 | [ -z ${GITHUB_RUN_ID+x} ] || echo '::endgroup::' >&3 26 | } 27 | 28 | teardown() { 29 | # cat /opt/fhem/fhem.cfg 30 | rm -rf ${FHEM_DIR}/* 31 | rm -rf /usr/src/fhem # why is no cleanup in entry.sh? 32 | 33 | # Sometimes perl or grep does not terminate, we will clean up 34 | pkill entry.sh || true 35 | pkill perl || true 36 | 37 | mkdir -p /fhem/FHEM 38 | cp /tmp/fhem/FHEM/* /fhem/FHEM/ 39 | } 40 | 41 | 42 | # bats test_tags=unitTest 43 | @test "check getGlobalAttr()" { 44 | bats_require_minimum_version 1.5.0 45 | 46 | run ! getGlobalAttr /tmp/test.cfg "logfile" 47 | assert_file_not_exists ${FHEM_DIR}/fhem.cfg 48 | 49 | run fhemCleanInstall 50 | 51 | assert_file_exists ${FHEM_DIR}/fhem.cfg 52 | run ! getGlobalAttr ${FHEM_DIR}/fhem.cfg "some" 53 | cat ${FHEM_DIR}/fhem.cfg > ${LOG_FILE} 54 | run -0 getGlobalAttr ${FHEM_DIR}/fhem.cfg "logfile" 55 | 56 | assert_file_contains ${FHEM_DIR}/fhem.cfg "attr global logfile" 57 | run -0 getGlobalAttr ${FHEM_DIR}/fhem.cfg "logfile" 58 | } 59 | 60 | # bats test_tags=unitTest 61 | @test "check setGlobal_LOGFILE from default" { 62 | 63 | run bash -c 'unset LOGFILE && setGlobal_LOGFILE && echo $LOGFILE' 64 | assert_output "${FHEM_DIR}/log/fhem-%Y-%m-%d.log" 65 | } 66 | 67 | 68 | # bats test_tags=unitTest 69 | @test "check setGlobal_LOGFILE from fhem.cfg" { 70 | export LOGFILE= 71 | 72 | fhemCleanInstall 73 | assert_file_exists ${FHEM_DIR}/fhem.cfg 74 | assert_file_contains ${FHEM_DIR}/fhem.cfg "attr global logfile ./log/fhem-%Y-%m.log" 75 | 76 | unset LOGFILE 77 | setGlobal_LOGFILE 78 | run echo $LOGFILE 79 | assert_output "${FHEM_DIR}/log/fhem-%Y-%m.log" 80 | 81 | mkdir -p "/fhem" 82 | run bash -c 'initialContainerSetup && echo $LOGFILE' 83 | assert_output --partial "${FHEM_DIR}/log/fhem-%Y-%m.log" 84 | 85 | assert_file_exists ${FHEM_DIR}/fhem.cfg 86 | cat ${FHEM_DIR}/fhem.cfg 87 | assert_file_contains ${FHEM_DIR}/fhem.cfg "define Logfile FileLog ./log/fhem-%Y-%m.log Logfile" grep 88 | } 89 | 90 | 91 | # bats test_tags=unitTest 92 | @test "check setGlobal_LOGFILE from environment" { 93 | export LOGFILE="/opt/log/fhem-%Y-%m-%d.log" 94 | 95 | run bash -c 'setGlobal_LOGFILE && echo $LOGFILE' 96 | assert_output "/opt/log/fhem-%Y-%m-%d.log" 97 | } 98 | 99 | 100 | # bats test_tags=unitTest 101 | @test "check Logfile definition from fhem.cfg" { 102 | run setGlobal_LOGFILE 103 | run fhemCleanInstall 104 | 105 | assert_file_exists ${FHEM_DIR}/fhem.cfg 106 | assert_file_contains ${FHEM_DIR}/fhem.cfg "define Logfile FileLog ./log/fhem-%Y-%m.log Logfile" 107 | } 108 | 109 | 110 | # bats test_tags=integrationTest 111 | @test "integration: default LOGFILE" { 112 | unset LOGFILE 113 | local logfile_FMT="./log/fhem-%Y-%m.log" 114 | 115 | # Container setup 116 | run bash -c 'cd $FHEM_DIR && initialContainerSetup > ${LOG_FILE}' 117 | assert_file_exists ${FHEM_CFG_FILE} 118 | 119 | # Prüfen ob LOGFILE korrekt angelegt wird 120 | /entry.sh start &>> ${LOG_FILE} & 121 | export ENTRY_PID=$! 122 | waitForTextInFile ${LOG_FILE} "Server started" 15 # wait max 15 seconds 123 | local realLogFile="$( date +"$logfile_FMT")" 124 | 125 | #cat ${LOG_FILE} 126 | assert_file_contains ${LOG_FILE} "From the FHEM_GLOBALATTR environment: attr global logfile ${logfile_FMT#./}" grep 127 | assert_file_exists "${FHEM_DIR}/$realLogFile" 128 | assert_file_contains ${FHEM_CFG_FILE} "$logfile_FMT" grep # logfile should be set in configfile because it's per default 129 | 130 | kill $ENTRY_PID # fail it the process already finished due to error! 131 | } 132 | 133 | # bats test_tags=integrationTest 134 | @test "integration: environment set LOGFILE relative" { 135 | export LOGFILE="log/fhem-%Y-%m-%d.log" 136 | local logfile_FMT="log/fhem-%Y-%m-%d.log" 137 | LOG_FILE="/tmp/log" 138 | # Container setup 139 | run bash -c 'cd $FHEM_DIR && initialContainerSetup > ${LOG_FILE}' 140 | cat ${LOG_FILE} 141 | 142 | assert_file_exists ${FHEM_CFG_FILE} 143 | 144 | # Prüfen ob LOGFILE korrekt angelegt wird 145 | /entry.sh start &>> ${LOG_FILE} & 146 | export ENTRY_PID=$! 147 | waitForTextInFile ${LOG_FILE} "Server started" 15 # wait max 15 seconds 148 | local realLogFile="$( date +"$logfile_FMT")" 149 | 150 | assert_file_contains ${LOG_FILE} "From the FHEM_GLOBALATTR environment: attr global logfile $logfile_FMT" grep 151 | assert_file_exists "${FHEM_DIR}/$realLogFile" 152 | assert_file_not_contains ${FHEM_CFG_FILE} "attr global logfile ./$logfile_FMT" grep # attr logfile should not be updated in configfile 153 | assert_file_contains ${FHEM_CFG_FILE} "define Logfile FileLog ./$logfile_FMT" grep # FileLog should be set by ENV Variable 154 | #cat ${FHEM_CFG_FILE} 3>& 155 | # Execute save via http and check confgfile 156 | echo -e "save" | fhemcl.sh 157 | cat ${FHEM_CFG_FILE} 158 | 159 | assert_file_contains ${FHEM_CFG_FILE} "attr global logfile $logfile_FMT" grep # save is executed! 160 | assert_file_contains ${FHEM_CFG_FILE} "define Logfile FileLog ./$logfile_FMT" grep # save is executed! 161 | 162 | kill $ENTRY_PID # fail it the process already finished due to error! 163 | } 164 | 165 | # bats test_tags=integrationTest 166 | @test "integration: environment set LOGFILE absolute" { 167 | export LOGFILE="/opt/log/fhem-%Y-%m-%d.log" 168 | local logfile_FMT="/opt/log/fhem-%Y-%m-%d.log" 169 | export LOG_FILE=${LOG_FILE:-/tmp/log} 170 | # Container setup 171 | run bash -c 'cd $FHEM_DIR && initialContainerSetup > ${LOG_FILE}' 172 | assert_file_exists ${FHEM_CFG_FILE} 173 | 174 | # Prüfen ob LOGFILE korrekt angelegt wird 175 | /entry.sh start &>> ${LOG_FILE} & 176 | export ENTRY_PID=$! 177 | waitForTextInFile ${LOG_FILE} "Server started" 15 # wait max 15 seconds 178 | local realLogFile="$( date +"$logfile_FMT")" 179 | 180 | assert_file_contains ${LOG_FILE} "From the FHEM_GLOBALATTR environment: attr global logfile $logfile_FMT" grep 181 | assert_file_exists "$realLogFile" 182 | assert_file_not_contains ${FHEM_CFG_FILE} "attr global logfile $logfile_FMT" grep # attr logfile should not be updated in configfile 183 | assert_file_contains ${FHEM_CFG_FILE} "define Logfile FileLog $logfile_FMT" grep # FileLog should be set by ENV Variable 184 | #cat ${FHEM_CFG_FILE} 3>& 185 | # Execute save via http and check confgfile 186 | echo -e "save" | fhemcl.sh 187 | cat ${FHEM_CFG_FILE} 188 | assert_file_contains ${FHEM_CFG_FILE} "attr global logfile $logfile_FMT" grep # save is executed! 189 | assert_file_contains ${FHEM_CFG_FILE} "define Logfile FileLog $logfile_FMT" grep # save is executed! 190 | 191 | kill $ENTRY_PID # fail it the process already finished due to error! 192 | } 193 | -------------------------------------------------------------------------------- /src/tests/bats/network.bats: -------------------------------------------------------------------------------- 1 | 2 | #!/usr/bin/env bats 3 | 4 | setup() { 5 | load '/opt/bats/test_helper/bats-support/load.bash' 6 | load '/opt/bats/test_helper/bats-assert/load.bash' 7 | load '/opt/bats/test_helper/bats-file/load.bash' 8 | load '/opt/bats/test_helper/bats-mock/load.bash' 9 | 10 | # Clean bevore every run 11 | declare -g DOCKER_GW= 12 | declare -g DOCKER_HOST= 13 | declare -g DOCKER_PRIVILEGED= 14 | 15 | # copy default hosts file before every test 16 | cp "${BATS_SUITE_TMPDIR}/hosts" "${HOSTS_FILE}" 17 | } 18 | 19 | 20 | setup_file() { 21 | export BATS_TEST_TIMEOUT=60 22 | [ -z ${GITHUB_RUN_ID+x} ] || echo '::group::Network Tests' >&3 23 | export LOG_FILE="${BATS_SUITE_TMPDIR}/log" 24 | 25 | set -a 26 | source /entry.sh 27 | set +a 28 | 29 | export CAP_E_FILE='/docker.container.cap.e' 30 | export CAP_P_FILE='/docker.container.cap.p' 31 | export CAP_I_FILE='/docker.container.cap.i' 32 | export HOSTNETWORK_FILE='/docker.hostnetwork' 33 | export PRIVILEDGED_FILE='/docker.privileged' 34 | 35 | } 36 | 37 | teardown_file() { 38 | sleep 0 39 | 40 | # Cleanup 41 | unset DOCKER_GW 42 | unset DOCKER_HOST 43 | unset DOCKER_PRIVILEGED 44 | cp "${BATS_SUITE_TMPDIR}/hosts" "${HOSTS_FILE}" 45 | [ -z ${GITHUB_RUN_ID+x} ] || echo '::endgroup::' >&3 46 | } 47 | 48 | 49 | 50 | teardown() { 51 | rm -f ${CAP_E_FILE} ${CAP_P_FILE} ${CAP_I_FILE} ${HOSTNETWORK_FILE} ${PRIVILEDGED_FILE} 52 | } 53 | 54 | # bats test_tags=unitTest 55 | @test "check collectDockerInfo() - check cap files" { 56 | bats_require_minimum_version 1.5.0 57 | collectDockerInfo 58 | 59 | assert_file_exists ${CAP_E_FILE} 60 | assert_file_exists ${CAP_P_FILE} 61 | assert_file_exists ${CAP_I_FILE} 62 | } 63 | 64 | # bats test_tags=unitTest 65 | @test "check collectDockerInfo() - HOSTNETWORK File in bridgeMode (default)" { 66 | collectDockerInfo 67 | 68 | assert_file_exists ${HOSTNETWORK_FILE} 69 | assert_file_contains ${HOSTNETWORK_FILE} "0" grep 70 | assert_file_not_contains ${HOSTNETWORK_FILE} "1" grep 71 | assert_equal ${DOCKER_HOSTNETWORK} '0' 72 | } 73 | 74 | # bats test_tags=hostMode,unitTest 75 | @test "check collectDockerInfo() - HOSTNETWORK File in hostMode" { 76 | collectDockerInfo 77 | 78 | assert_file_exists ${HOSTNETWORK_FILE} 79 | assert_file_contains ${HOSTNETWORK_FILE} "1" grep 80 | assert_file_not_contains ${HOSTNETWORK_FILE} "0" grep 81 | assert_equal ${DOCKER_HOSTNETWORK} '1' 82 | 83 | } 84 | 85 | 86 | # bats test_tags=unitTest 87 | @test "check collectDockerInfo() - PRIVILEDGED file " { 88 | collectDockerInfo 89 | 90 | assert_file_contains ${PRIVILEDGED_FILE} '0' grep 91 | assert_file_not_contains ${PRIVILEDGED_FILE} '1' grep 92 | assert_equal ${DOCKER_PRIVILEGED} '0' 93 | 94 | } 95 | 96 | # bats test_tags=hostMode,unitTest 97 | @test "check collectDockerInfo() - DOCKER_GW" { 98 | collectDockerInfo 99 | 100 | assert_equal ${DOCKER_GW} '' 101 | } 102 | 103 | # bats test_tags=hostMode,unitTest 104 | @test "check collectDockerInfo() - DOCKER_HOST" { 105 | collectDockerInfo 106 | 107 | assert_equal ${DOCKER_HOST} '127.0.0.1' 108 | } 109 | 110 | # bats test_tags=unitTest 111 | @test "check DOCKER_HOST in ${HOSTS_FILE}" { 112 | collectDockerInfo 113 | 114 | run addDockerHosts 115 | assert_output --partial "Adding" 116 | assert_file_contains ${HOSTS_FILE} "${DOCKER_HOST}" grep 117 | assert_file_contains ${HOSTS_FILE} "host.docker.internal" grep 118 | 119 | } 120 | 121 | 122 | # bats test_tags=unitTest 123 | @test "check DOCKER_GW in ${HOSTS_FILE}" { 124 | collectDockerInfo 125 | 126 | run addDockerHosts 127 | 128 | assert_file_contains ${HOSTS_FILE} "${DOCKER_GW}.*gateway.docker.internal" grep 129 | } 130 | 131 | # bats test_tags=hostMode,unitTest 132 | @test "check DOCKER_HOST in ${HOSTS_FILE} with hostMode" { 133 | collectDockerInfo 134 | 135 | run addDockerHosts 136 | assert_output --partial "Adding " 137 | cat ${HOSTS_FILE} 138 | assert_file_contains ${HOSTS_FILE} "${DOCKER_HOST}.*host.docker.internal" grep 139 | } 140 | 141 | 142 | # bats test_tags=hostMode,unitTest 143 | @test "check DOCKER_GW in ${HOSTS_FILE} with hostMode" { 144 | bats_require_minimum_version 1.5.0 145 | 146 | collectDockerInfo 147 | run -0 addDockerHosts 148 | 149 | assert_equal "${DOCKER_GW}" "" 150 | refute_output --partial "Adding gateway.docker.internal" 151 | 152 | cat "${HOSTS_FILE}" 153 | assert_file_not_contains ${HOSTS_FILE} "${DOCKER_GW}.*gateway.docker.internal" grep 154 | } -------------------------------------------------------------------------------- /src/tests/bats/pidfile.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | setup() { 4 | load '/opt/bats/test_helper/bats-support/load.bash' 5 | load '/opt/bats/test_helper/bats-assert/load.bash' 6 | load '/opt/bats/test_helper/bats-file/load.bash' 7 | load '/opt/bats/test_helper/bats-mock/load.bash' 8 | 9 | #export -f printfDebug 10 | #export -f printfInfo 11 | 12 | # Sometimes perl or grep does not terminate, we will clean up 13 | #pkill tail || true 14 | #pkill grep || true 15 | } 16 | 17 | setup_file() { 18 | [ -z ${GITHUB_RUN_ID+x} ] || echo '::group::aptInstall Tests' >&3 19 | export BATS_TEST_TIMEOUT=60 20 | export LOG_FILE="${BATS_SUITE_TMPDIR}/log" 21 | export CONFIGTYPE="fhem.cfg" 22 | 23 | set -a 24 | source /entry.sh 25 | set +a 26 | } 27 | 28 | teardown_file() { 29 | sleep 0 30 | rm -f /tmp/log 31 | [ -z ${GITHUB_RUN_ID+x} ] || echo '::endgroup::' >&3 32 | } 33 | 34 | teardown() { 35 | # cat /opt/fhem/fhem.cfg 36 | rm -rf /opt/fhem/* 37 | rm -rf /usr/src/fhem # why is no cleanup in entry.sh? 38 | 39 | # Sometimes perl or grep does not terminate, we will clean up 40 | pkill entry.sh || true 41 | pkill perl || true 42 | #pkill grep || true 43 | 44 | mkdir -p /fhem/FHEM 45 | cp /tmp/fhem/FHEM/* /fhem/FHEM/ 46 | } 47 | 48 | 49 | # bats test_tags=unitTest 50 | @test "verify setGlobal_PIDFILE default pidfile" { 51 | 52 | run bash -c 'unset PIDFILE; setGlobal_PIDFILE ; echo $PIDFILE' 53 | assert_output "/opt/fhem/log/fhem.pid" 54 | } 55 | 56 | # bats test_tags=unitTest 57 | @test "verify setGlobal_PIDFILE absolut pidfile" { 58 | export PIDFILE="/run/lock/fhem.pid" 59 | 60 | run bash -c 'setGlobal_PIDFILE ; echo $PIDFILE' 61 | assert_output "/run/lock/fhem.pid" 62 | } 63 | 64 | # bats test_tags=unitTest 65 | @test "verify setGlobal_PIDFILE relative pidfile" { 66 | export PIDFILE="./run/fhem.pid" 67 | 68 | run bash -c 'setGlobal_PIDFILE ; echo $PIDFILE' 69 | assert_output "/opt/fhem/run/fhem.pid" 70 | } 71 | 72 | 73 | # bats test_tags=integrationTest 74 | @test "integration: absoulte pidfile set in fhem.cfg" { 75 | # Container setup 76 | run bash -c 'cd $FHEM_DIR && initialContainerSetup > ${LOG_FILE}' 77 | assert_file_exists ${FHEM_DIR}/fhem.cfg 78 | 79 | # Pidfile in config schreiben! 80 | echo "attr global pidfilename /run/lock/fhem.pid" >> ${FHEM_CFG_FILE} 81 | 82 | # Prüfen ob PIDFILE korrekt angelegt wird 83 | /entry.sh start &>> ${LOG_FILE} & 84 | waitForTextInFile ${LOG_FILE} "Server started" 15 # wait max 15 seconds 85 | export ENTRY_PID=$! 86 | assert_file_contains ${FHEM_CFG_FILE} '/run/lock/fhem.pid' grep 87 | assert_file_exists /run/lock/fhem.pid 88 | 89 | # Execute save via http and check confgfile 90 | echo -e "save" | fhemcl.sh 91 | assert_file_contains ${FHEM_CFG_FILE} '/run/lock/fhem.pid' grep # No save is executed! 92 | 93 | kill $ENTRY_PID # fail it the process already finished due to error! 94 | assert_file_contains ${LOG_FILE} 'From the FHEM_GLOBALATTR environment: attr global pidfilename /run/lock/fhem.pid' grep 95 | } 96 | 97 | # bats test_tags=integrationTest 98 | @test "integration: absoulte pidfile set in environment" { 99 | # Container setup 100 | run bash -c 'cd $FHEM_DIR && initialContainerSetup > ${LOG_FILE}' 101 | assert_file_exists ${FHEM_CFG_FILE} 102 | 103 | # Pidfile in ENV schreiben! 104 | export PIDFILE=/var/run/lock/fhem.pid 105 | 106 | # Prüfen ob PIDFILE korrekt angelegt wird 107 | /entry.sh start &>> ${LOG_FILE} & 108 | export ENTRY_PID=$! 109 | waitForTextInFile ${LOG_FILE} "Server started" 15 # wait max 15 seconds 110 | 111 | assert_file_contains ${LOG_FILE} 'From the FHEM_GLOBALATTR environment: attr global pidfilename /var/run/lock/fhem.pid' grep 112 | assert_file_not_contains ${FHEM_CFG_FILE} 'attr global pidfilename /var/run/lock/fhem.pid' grep # pidfile should not be set in configfile 113 | assert_file_exists /var/run/lock/fhem.pid 114 | 115 | # Execute save via http and check confgfile 116 | echo -e "save" | fhemcl.sh 117 | assert_file_contains ${FHEM_CFG_FILE} '/var/run/lock/fhem.pid' grep # save is executed! 118 | 119 | kill $ENTRY_PID # fail it the process already finished due to error! 120 | } 121 | 122 | # bats test_tags=integrationTest 123 | @test "integration: default pidfile" { 124 | # Container setup 125 | run bash -c 'cd $FHEM_DIR && initialContainerSetup > ${LOG_FILE}' 126 | assert_file_exists ${FHEM_CFG_FILE} 127 | 128 | unset PIDFILE 129 | 130 | # Prüfen ob PIDFILE korrekt angelegt ist 131 | /entry.sh start &>> ${LOG_FILE} & 132 | waitForTextInFile ${LOG_FILE} "Server started" 15 # wait max 15 seconds 133 | export ENTRY_PID=$! 134 | 135 | assert_file_contains ${LOG_FILE} 'From the FHEM_GLOBALATTR environment: attr global pidfilename log/fhem.pid' 136 | assert_file_not_contains ${FHEM_CFG_FILE} 'fhem.pid' grep # pidfile should not be set in configfile 137 | assert_file_exists ${FHEM_DIR}/log/fhem.pid 138 | 139 | kill $ENTRY_PID # fail it the process already finished due to error! 140 | } 141 | 142 | -------------------------------------------------------------------------------- /src/tests/bats/setup_suite.bash: -------------------------------------------------------------------------------- 1 | setup_suite() { 2 | export FHEM_DIR="/opt/fhem" 3 | export FHEM_CFG_FILE="${FHEM_DIR}/fhem.cfg" 4 | export HOSTS_FILE='/etc/hosts' 5 | 6 | cp ${HOSTS_FILE} ${BATS_SUITE_TMPDIR}/hosts 7 | mkdir -p /tmp/fhem/FHEM 8 | cp -r /fhem/FHEM/* /tmp/fhem/FHEM/ 9 | 10 | wget https://raw.githubusercontent.com/heinz-otto/fhemcl/master/fhemcl.sh -O /usr/local/bin/fhemcl.sh 11 | chmod +x /usr/local/bin/fhemcl.sh 12 | } 13 | 14 | 15 | teardown_suite() { 16 | rm /usr/local/bin/fhemcl.sh 17 | rm -r /tmp/fhem 18 | } 19 | 20 | --------------------------------------------------------------------------------