├── .github ├── ISSUE_TEMPLATE │ └── bug_report.md ├── dependabot.yml └── workflows │ ├── build-and-push.yml │ ├── check-releases.yml │ └── lint.yml ├── .last_release ├── Dockerfile ├── LICENSE ├── README.md ├── docker-compose.build.yml ├── docker-compose.dev.yml ├── docker-compose.yml ├── src ├── config.sh ├── logging.sh └── process.sh └── start-photon.sh /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: bug 6 | assignees: rtuszik 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 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | ### **`photon-docker` configuration:** 20 | Please add your docker compose and environment variables. 21 | 22 | ### System Info 23 | - **Host OS:** 24 | - **Host Type:** [e.g., Bare-metal, LXC, VM, Synology] 25 | - **Hardware details:** 26 | - **CPU:** [e.g., Intel Core i7-9700K] 27 | - **Available RAM:** 28 | - **Storage Type:** [e.g., SSD, NVME, NFS, SAMBA] 29 | - **Storage Size:** 30 | 31 | 32 | **Debug Logs** 33 | Please provide any relevant logs. To get more detailed logs, you can set the `LOG_LEVEL` environment variable to `DEBUG` in your `docker-compose.yml` file. 34 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | 8 | - package-ecosystem: "docker" 9 | directory: "/" 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/workflows/build-and-push.yml: -------------------------------------------------------------------------------- 1 | name: Build and Publish Docker Image 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | release_tag: 7 | description: "Release version to build" 8 | required: true 9 | revision: 10 | description: "Revision number (optional)" 11 | required: false 12 | 13 | jobs: 14 | build-and-push: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout Repository 18 | uses: actions/checkout@v4 19 | 20 | - name: Set up Docker Buildx 21 | uses: docker/setup-buildx-action@v3 22 | 23 | - name: Login to DockerHub 24 | uses: docker/login-action@v3 25 | with: 26 | username: ${{ secrets.DOCKER_USERNAME }} 27 | password: ${{ secrets.DOCKER_PASSWORD }} 28 | 29 | - name: Login to GitHub Container Registry 30 | uses: docker/login-action@v3 31 | with: 32 | registry: ghcr.io 33 | username: ${{ github.repository_owner }} 34 | password: ${{ secrets.GITHUB_TOKEN }} 35 | 36 | - name: Determine tag 37 | id: determine_tag 38 | run: | 39 | revision="${{ github.event.inputs.revision }}" 40 | if [ "$revision" ]; then 41 | echo "RELEASE_TAG=${{ github.event.inputs.release_tag }}-rev.$revision" >> "$GITHUB_ENV" 42 | else 43 | echo "RELEASE_TAG=${{ github.event.inputs.release_tag }}" >> "$GITHUB_ENV" 44 | fi 45 | echo "PHOTON_VERSION=${{ github.event.inputs.release_tag }}" >> "$GITHUB_ENV" 46 | 47 | - name: Build and push Docker image 48 | uses: docker/build-push-action@v6 49 | with: 50 | build-args: | 51 | PHOTON_VERSION=${{ env.PHOTON_VERSION }} 52 | push: true 53 | tags: | 54 | rtuszik/photon-docker:${{ env.RELEASE_TAG }} 55 | rtuszik/photon-docker:latest 56 | ghcr.io/rtuszik/photon-docker:${{ env.RELEASE_TAG }} 57 | ghcr.io/rtuszik/photon-docker:latest 58 | platforms: linux/amd64,linux/arm64 59 | 60 | - name: Store the latest release 61 | run: echo ${{ env.RELEASE_TAG }} > .last_release 62 | if: success() 63 | continue-on-error: false 64 | -------------------------------------------------------------------------------- /.github/workflows/check-releases.yml: -------------------------------------------------------------------------------- 1 | name: Check for New Releases and Propose Update 2 | 3 | on: 4 | schedule: 5 | - cron: "0 * * * *" 6 | workflow_dispatch: 7 | 8 | jobs: 9 | check_and_propose_update: 10 | runs-on: ubuntu-latest 11 | permissions: 12 | contents: write 13 | pull-requests: write 14 | 15 | steps: 16 | - name: Checkout Repository 17 | uses: actions/checkout@v4 18 | 19 | - name: Check for new Photon release 20 | id: check_release 21 | run: | 22 | latest_release=$(curl -s https://github.com/komoot/photon/releases.atom | grep '' | sed -n '2p' | sed -E 's/.*Release ([0-9]+\.[0-9]+\.[0-9]+).*/\1/') 23 | if [ -z "$latest_release" ]; then 24 | echo "Error: Failed to fetch the latest Photon release version." 25 | exit 1 26 | else 27 | echo "Latest Photon release version: $latest_release" 28 | echo "latest_release_version=${latest_release}" >> "$GITHUB_ENV" 29 | fi 30 | 31 | - name: Get last processed release from file 32 | id: get_last_release 33 | run: | 34 | if [ -f .last_release ]; then 35 | current_version_in_file=$(cat .last_release) 36 | echo "Current version in .last_release file: $current_version_in_file" 37 | echo "last_processed_version=$current_version_in_file" >> "$GITHUB_ENV" 38 | else 39 | echo ".last_release file not found." 40 | exit 1 41 | fi 42 | 43 | - name: Determine if update is needed 44 | id: prepare_update 45 | run: | 46 | if [[ -n "${{ env.latest_release_version }}" && "${{ env.latest_release_version }}" != "${{ env.last_processed_version }}" ]]; then 47 | echo "New version found: ${{ env.latest_release_version }}. (Previous: ${{ env.last_processed_version }})" 48 | { 49 | echo "update_needed=true" 50 | echo "new_version=${{ env.latest_release_version }}" 51 | echo "new_branch_name=update-photon-${{ env.latest_release_version }}" 52 | } >> "$GITHUB_OUTPUT" 53 | else 54 | echo "No new Photon release detected or version is already up-to-date. Latest fetched: '${{ env.latest_release_version }}', last processed: '${{ env.last_processed_version }}'." 55 | { 56 | echo "update_needed=false" 57 | echo "new_version=${{ env.last_processed_version }}" 58 | } >> "$GITHUB_OUTPUT" 59 | fi 60 | 61 | - name: Update release file(s) locally 62 | if: steps.prepare_update.outputs.update_needed == 'true' 63 | run: | 64 | echo "Updating .last_release to ${{ steps.prepare_update.outputs.new_version }}" 65 | echo "${{ steps.prepare_update.outputs.new_version }}" > .last_release 66 | 67 | - name: Create Pull Request 68 | if: steps.prepare_update.outputs.update_needed == 'true' 69 | uses: peter-evans/create-pull-request@v6 70 | with: 71 | token: ${{ secrets.MY_PAT_TOKEN }} 72 | commit-message: | 73 | Update Photon version to ${{ steps.prepare_update.outputs.new_version }} 74 | 75 | Automated update of the .last_release file (and potentially other version files) 76 | to reflect the new Photon release: ${{ steps.prepare_update.outputs.new_version }}. 77 | committer: GitHub Actions <41898282+github-actions[bot]@users.noreply.github.com> 78 | author: GitHub Actions <41898282+github-actions[bot]@users.noreply.github.com> 79 | branch: ${{ steps.prepare_update.outputs.new_branch_name }} 80 | delete-branch: true 81 | title: "Update Photon to version ${{ steps.prepare_update.outputs.new_version }}" 82 | body: | 83 | A new version of Photon (${{ steps.prepare_update.outputs.new_version }}) has been released. 84 | 85 | This Pull Request proposes updating our tracked version. 86 | 87 | **Release File(s) Updated:** 88 | * `.last_release` has been updated to `${{ steps.prepare_update.outputs.new_version }}`. 89 | * (Mention any other files updated here, e.g., Dockerfile, if applicable) 90 | 91 | **Next Steps:** 92 | 1. Review the changes in this PR. 93 | 2. Merge this PR if everything looks good. 94 | 3. Build New Image: Merging this PR should (ideally) trigger the separate workflow responsible for building and publishing the new Docker image with Photon version `${{ steps.prepare_update.outputs.new_version }}`. 95 | 96 | --- 97 | Upstream release notes for Photon ${{ steps.prepare_update.outputs.new_version }}: https://github.com/komoot/photon/releases/tag/${{ steps.prepare_update.outputs.new_version }} 98 | labels: | 99 | update 100 | automated-pr 101 | 102 | - name: No update needed 103 | if: steps.prepare_update.outputs.update_needed == 'false' 104 | run: echo "No new Photon release was found or the version is already current. No action taken." 105 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | on: 3 | pull_request: 4 | workflow_dispatch: 5 | 6 | permissions: {} 7 | 8 | jobs: 9 | lint: 10 | runs-on: ubuntu-latest 11 | 12 | permissions: 13 | contents: read 14 | packages: read 15 | statuses: write 16 | 17 | steps: 18 | - name: Checkout Code 19 | uses: actions/checkout@v4 20 | with: 21 | fetch-depth: 0 22 | 23 | - name: Lint Code Base 24 | uses: github/super-linter/slim@v7 25 | env: 26 | VALIDATE_ALL_CODEBASE: true 27 | FIX_MARKDOWN_PRETTIER: true 28 | VALIDATE_MARKDOWN_PRETTIER: true 29 | VALIDATE_GITLEAKS: true 30 | VALIDATE_GITHUB_ACTIONS: true 31 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 32 | -------------------------------------------------------------------------------- /.last_release: -------------------------------------------------------------------------------- 1 | 0.7.0 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM eclipse-temurin:21.0.5_11-jre-noble 2 | 3 | ARG PHOTON_VERSION 4 | 5 | RUN apt-get update \ 6 | && apt-get -y install --no-install-recommends \ 7 | pbzip2 \ 8 | wget \ 9 | procps \ 10 | coreutils \ 11 | tree \ 12 | && rm -rf /var/lib/apt/lists/* 13 | 14 | WORKDIR /photon 15 | 16 | RUN mkdir -p /photon/photon_data 17 | 18 | ADD https://github.com/komoot/photon/releases/download/${PHOTON_VERSION}/photon-opensearch-${PHOTON_VERSION}.jar /photon/photon.jar 19 | 20 | COPY start-photon.sh ./start-photon.sh 21 | COPY src/ ./src/ 22 | RUN chmod +x start-photon.sh src/*.sh 23 | 24 | 25 | VOLUME /photon/photon_data 26 | EXPOSE 2322 27 | 28 | ENTRYPOINT ["/photon/start-photon.sh"] 29 | 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Docker Pulls](https://img.shields.io/docker/pulls/rtuszik/photon-docker) ![Docker Image Size](https://img.shields.io/docker/image-size/rtuszik/photon-docker) ![Docker Image Version](https://img.shields.io/docker/v/rtuszik/photon-docker) ![GitHub Release](https://img.shields.io/github/v/release/komoot/photon?label=Photon) ![Lint Status](https://github.com/rtuszik/photon-docker/actions/workflows/lint.yml/badge.svg) 2 | 3 | # Photon Docker Image 4 | 5 | ## Overview 6 | 7 | This is an _unofficial_ docker image for [Photon](https://github.com/komoot/photon) 8 | 9 | Photon is an open-source geocoding solution built for OpenStreetMap (OSM) data, providing features such as search-as-you-type and reverse geocoding. 10 | This repository offers a Docker image for running Photon locally, enhancing data privacy and integration capabilities with services like [Dawarich](https://github.com/Freika/dawarich). 11 | 12 | ## Important Notes 13 | 14 | ⚠️ **Warning: Large File Sizes** ⚠️ 15 | 16 | - The Photon index file is fairly large and growing steadily. As of beginning of 2025, around 200GB is needed for the full index. Growing 10-20GB per year. 17 | - Ensure you have sufficient disk space available before running the container. 18 | - The initial download and extraction process may take a considerable amount of time. Depending on your hardware, checksum verification and decompression may take multiple hours. 19 | 20 | ## Usage 21 | 22 | ### Configuration Options 23 | 24 | The container can be configured using the following environment variables: 25 | 26 | | Variable | Parameters | Default | Description | 27 | | ----------------- | ----------------------------------------- | ------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 28 | | `UPDATE_STRATEGY` | `PARALLEL`, `SEQUENTIAL`, `DISABLED` | `SEQUENTIAL` | Controls how index updates are handled. `PARALLEL` downloads new index in background then swaps with minimal downtime (requires 2x space). `SEQUENTIAL` stops Photon, deletes existing index, downloads new one, then restarts. `DISABLED` prevents automatic updates. | 29 | | `UPDATE_INTERVAL` | Time string (e.g., "24h", "60m", "3600s") | `24h` | How often to check for updates | 30 | | `COUNTRY_CODE` | Two-letter country code | - | Optional country code for smaller index ([available codes](https://download1.graphhopper.com/public/extracts/by-country-code/)). Only one country code is supported at a time. | 31 | | `LOG_LEVEL` | `DEBUG`, `INFO`, `ERROR` | `INFO` | Controls logging verbosity | 32 | | `FORCE_UPDATE` | `TRUE`, `FALSE` | `FALSE` | Forces index update on container startup, regardless of UPDATE_STRATEGY | 33 | | `BASE_URL` | Valid URL | `https://download1.graphhopper.com/public` | Custom base URL for index data downloads. Should point to parent directory of index files. | 34 | | `SKIP_MD5_CHECK` | `TRUE`, `FALSE` | `FALSE` | Optionally skip MD5 verification of downloaded index files | 35 | 36 | ### Example Docker Compose 37 | 38 | ```yaml 39 | services: 40 | photon: 41 | image: rtuszik/photon-docker:latest 42 | environment: 43 | - UPDATE_STRATEGY=PARALLEL 44 | - UPDATE_INTERVAL=24h 45 | # - COUNTRY_CODE=zw # Optional: country-specific index 46 | volumes: 47 | - photon_data:/photon/photon_data 48 | restart: unless-stopped 49 | ports: 50 | - "2322:2322" 51 | volumes: 52 | photon_data: 53 | ``` 54 | 55 | ```bash 56 | docker-compose up -d 57 | ``` 58 | 59 | ### Use with Dawarich 60 | 61 | This docker container for photon can be used as your reverse-geocoder for the [Dawarich Location History Tracker](https://github.com/Freika/dawarich) 62 | 63 | To connect dawarich to your photon instance, the following environment variables need to be set in your dawarich docker-compose.yml: 64 | 65 | ```yaml 66 | PHOTON_API_HOST={PHOTON-IP}:{PORT} 67 | PHOTON_API_USE_HTTPS=false 68 | ``` 69 | 70 | for example: 71 | 72 | ```yaml 73 | PHOTON_API_HOST=192.168.10.10:2322 74 | PHOTON_API_USE_HTTPS=false 75 | ``` 76 | 77 | - Do _not_ set PHOTON_API_USE_HTTPS to true unless your photon instance is available using HTTPS. 78 | - Only use the host address for your photon instance. Do not append `/api` 79 | 80 | ### Build and Run Locally 81 | 82 | ```bash 83 | docker compose -f docker-compose.build.yml build --build-arg PHOTON_VERSION=0.6.2 84 | ``` 85 | 86 | ### Accessing the API 87 | 88 | The Photon API is available at: 89 | 90 | ``` 91 | http://localhost:2322/api?q=Harare 92 | ``` 93 | 94 | ## Contributing 95 | 96 | Contributions are welcome. Please submit pull requests or open issues for suggestions and improvements. 97 | 98 | ## License 99 | 100 | This project is licensed under the Apache License, Version 2.0. 101 | 102 | ## Acknowledgments 103 | 104 | - [Photon](https://github.com/komoot/photon) 105 | - [Dawarich](https://github.com/Freika/dawarich) 106 | -------------------------------------------------------------------------------- /docker-compose.build.yml: -------------------------------------------------------------------------------- 1 | services: 2 | photon: 3 | build: 4 | context: . 5 | dockerfile: Dockerfile 6 | args: 7 | - PHOTON_VERSION=${PHOTON_VERSION} 8 | environment: 9 | - UPDATE_STRATEGY=SEQUENTIAL 10 | - UPDATE_INTERVAL=3m 11 | # - LOG_LEVEL=DEBUG 12 | # - FORCE_UPDATE=TRUE 13 | # - COUNTRY_CODE=zw 14 | # - BASE_URL=https://download1.graphhopper.com/public 15 | # - SKIP_MD5_CHECK=TRUE 16 | volumes: 17 | - photon_data:/photon/photon_data 18 | restart: unless-stopped 19 | ports: 20 | - "2322:2322" 21 | volumes: 22 | photon_data: 23 | -------------------------------------------------------------------------------- /docker-compose.dev.yml: -------------------------------------------------------------------------------- 1 | services: 2 | photon: 3 | build: 4 | context: . 5 | dockerfile: Dockerfile 6 | args: 7 | - PHOTON_VERSION=0.6.2 8 | image: rtuszik/photon-docker:dev 9 | environment: 10 | - UPDATE_STRATEGY=PARALLEL 11 | - UPDATE_INTERVAL=1m 12 | - LOG_LEVEL=DEBUG 13 | - COUNTRY_CODE=ad 14 | # - BASE_URL=https://download1.graphhopper.com/public 15 | 16 | volumes: 17 | - photon_data:/photon/photon_data 18 | restart: unless-stopped 19 | ports: 20 | - "2322:2322" 21 | volumes: 22 | photon_data: 23 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | photon: 3 | image: ghcr.io/rtuszik/photon-docker:latest 4 | container_name: "photon-docker" 5 | environment: 6 | - UPDATE_STRATEGY=PARALLEL 7 | - UPDATE_INTERVAL=24h 8 | - LOG_LEVEL=INFO # Options: DEBUG, INFO, ERROR 9 | # - COUNTRY_CODE=zw 10 | volumes: 11 | - photon_data:/photon/photon_data 12 | restart: unless-stopped 13 | ports: 14 | - "2322:2322" 15 | volumes: 16 | photon_data: 17 | -------------------------------------------------------------------------------- /src/config.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Fixed paths 4 | PHOTON_DIR="/photon" 5 | PHOTON_DATA_DIR="${PHOTON_DIR}/photon_data" 6 | PHOTON_JAR="${PHOTON_DIR}/photon.jar" 7 | OS_DATA_DIR="${PHOTON_DATA_DIR}/node_1" 8 | INDEX_DIR="${OS_DATA_DIR}" 9 | ES_DATA_DIR="${PHOTON_DATA_DIR}/elasticsearch" 10 | TEMP_DIR="${PHOTON_DATA_DIR}/temp" 11 | PID_FILE="${PHOTON_DIR}/photon.pid" 12 | 13 | # Environment variables with defaults 14 | UPDATE_STRATEGY=${UPDATE_STRATEGY:-SEQUENTIAL} 15 | UPDATE_INTERVAL=${UPDATE_INTERVAL:-24h} 16 | LOG_LEVEL=${LOG_LEVEL:-INFO} 17 | BASE_URL=${BASE_URL:-https://download1.graphhopper.com/public/experimental} 18 | FORCE_UPDATE=${FORCE_UPDATE:-FALSE} 19 | SKIP_MD5_CHECK=${SKIP_MD5_CHECK:-FALSE} 20 | 21 | # Validate UPDATE_STRATEGY 22 | if [[ ! "${UPDATE_STRATEGY}" =~ ^(SEQUENTIAL|PARALLEL|DISABLED)$ ]]; then 23 | echo "ERROR: Invalid UPDATE_STRATEGY: ${UPDATE_STRATEGY}" 24 | echo "Valid options are: SEQUENTIAL, PARALLEL, DISABLED" 25 | exit 1 26 | fi 27 | -------------------------------------------------------------------------------- /src/logging.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # ANSI color codes 4 | GREEN='\033[0;32m' 5 | RED='\033[0;31m' 6 | BLUE='\033[0;34m' 7 | NC='\033[0m' 8 | 9 | log_info() { 10 | if [[ "${LOG_LEVEL:-INFO}" != "ERROR" ]]; then 11 | echo -e "${GREEN}[INFO]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $*" 12 | fi 13 | } 14 | 15 | log_error() { 16 | echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $*" >&2 17 | } 18 | 19 | log_debug() { 20 | if [[ "${LOG_LEVEL:-INFO}" == "DEBUG" ]]; then 21 | echo -e "${BLUE}[DEBUG]${NC} $(date '+%Y-%m-%d %H:%M:%S') - $*" 22 | fi 23 | } 24 | -------------------------------------------------------------------------------- /src/process.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source "src/config.sh" 4 | source "src/logging.sh" 5 | 6 | stop_photon() { 7 | if [ -f "$PID_FILE" ]; then 8 | local pid 9 | pid=$(cat "$PID_FILE") 10 | 11 | if ps -p "$pid" > /dev/null; then 12 | log_info "Stopping Photon service (PID: $pid)" 13 | if ! kill -15 "$pid"; then 14 | log_error "Failed to stop Photon service gracefully, attempting force kill" 15 | kill -9 "$pid" || true 16 | fi 17 | 18 | # Wait for process to stop 19 | local count=0 20 | while ps -p "$pid" > /dev/null && [ $count -lt 10 ]; do 21 | sleep 1 22 | ((count++)) 23 | done 24 | 25 | if ps -p "$pid" > /dev/null; then 26 | log_error "Failed to stop Photon service" 27 | return 1 28 | fi 29 | else 30 | log_info "Photon service not running" 31 | fi 32 | rm -f "$PID_FILE" 33 | fi 34 | return 0 35 | } 36 | 37 | start_photon() { 38 | # Check if already running 39 | if [ -f "$PID_FILE" ]; then 40 | local pid 41 | pid=$(cat "$PID_FILE") 42 | if ps -p "$pid" > /dev/null; then 43 | log_info "Photon service already running with PID: $pid" 44 | return 0 45 | else 46 | log_info "Removing stale PID file" 47 | rm -f "$PID_FILE" 48 | fi 49 | fi 50 | 51 | log_info "Starting Photon service" 52 | java -jar "$PHOTON_JAR" -data-dir "$PHOTON_DIR" & 53 | local new_pid=$! 54 | echo $new_pid > "$PID_FILE" 55 | 56 | log_info "Photon service started successfully with PID: $new_pid" 57 | return 0 58 | } 59 | -------------------------------------------------------------------------------- /start-photon.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Source modules 4 | source "src/logging.sh" 5 | source "src/config.sh" 6 | source "src/process.sh" 7 | 8 | # Log environment variables 9 | log_info "Environment variables:" 10 | log_info "UPDATE_STRATEGY=$UPDATE_STRATEGY" 11 | log_info "UPDATE_INTERVAL=$UPDATE_INTERVAL" 12 | log_info "LOG_LEVEL=$LOG_LEVEL" 13 | log_info "BASE_URL=$BASE_URL" 14 | log_info "FORCE_UPDATE=$FORCE_UPDATE" 15 | log_info "SKIP_MD5_CHECK=$SKIP_MD5_CHECK" 16 | log_info "COUNTRY_CODE=${COUNTRY_CODE:-not set}" 17 | 18 | ES_UID="${ES_UID:-1000}" 19 | ES_GID="${ES_GID:-1000}" 20 | 21 | # Define DATA_DIR from config 22 | DATA_DIR="$PHOTON_DIR" 23 | 24 | # Error handling 25 | set -euo pipefail 26 | trap 'handle_error $? $LINENO "$BASH_COMMAND" $(printf "::%s" ${FUNCNAME[@]:-})' ERR 27 | 28 | 29 | handle_error() { 30 | local exit_code=$1 31 | local line_no=$2 32 | local last_command=$4 33 | local func_trace=$5 34 | log_error "Error $exit_code occurred on line $line_no: $last_command" 35 | log_error "Function trace: $func_trace" 36 | cleanup_and_exit 37 | } 38 | 39 | # Cleanup function 40 | cleanup_and_exit() { 41 | log_info "Cleaning up temporary files..." 42 | 43 | # Stop service if running 44 | stop_photon 45 | 46 | # Remove temporary files 47 | if [ -d "$TEMP_DIR" ]; then 48 | if ! rm -rf "${TEMP_DIR:?}"/*; then 49 | log_error "Failed to clean up temporary directory" 50 | fi 51 | fi 52 | 53 | # Remove PID file if it exists 54 | rm -f /photon/photon.pid 55 | 56 | exit 1 57 | } 58 | 59 | # Check available disk space against remote file size 60 | check_disk_space() { 61 | local url=$1 62 | local available 63 | local full_url="${url}.tar.bz2" 64 | 65 | log_debug "Checking disk space for URL: $full_url" 66 | log_debug "URL components - Protocol: ${full_url%%://*}, Host: ${full_url#*://}, Path: ${full_url#*://*/}" 67 | 68 | # Get remote file size using wget spider 69 | log_debug "Executing wget spider command: wget --spider --server-response \"$full_url\"" 70 | local wget_output 71 | wget_output=$(wget --spider --server-response "$full_url" 2>&1) 72 | local wget_status=$? 73 | log_debug "wget spider command output: $(echo "$wget_output" | head -20)" 74 | 75 | local remote_size 76 | if ! remote_size=$(echo "$wget_output" | grep "Content-Length" | awk '{print $2}' | tail -1); then 77 | log_error "Failed to execute wget spider command" 78 | log_debug "wget spider command failed with status: $wget_status" 79 | log_debug "Full wget output: $wget_output" 80 | return 1 81 | fi 82 | 83 | if [ -z "$remote_size" ]; then 84 | log_error "Failed to get remote file size" 85 | log_debug "No Content-Length found in wget output" 86 | return 1 87 | fi 88 | 89 | log_debug "Remote file size detected: $remote_size bytes" 90 | 91 | # Check available space in photon_data directory 92 | log_debug "Creating data directory structure at $DATA_DIR/photon_data" 93 | mkdir -p "$DATA_DIR/photon_data" 94 | log_debug "Directory created. Contents: $(ls -l $DATA_DIR/photon_data 2>/dev/null || echo '<none>')" 95 | available=$(df -B1 "$DATA_DIR/photon_data" | awk 'NR==2 {print $4}') 96 | log_debug "Available disk space: $available bytes" 97 | 98 | if [ "$available" -lt "$remote_size" ]; then 99 | log_error "Insufficient disk space. Required: ${remote_size}B , Available: ${available}B" 100 | return 1 101 | fi 102 | 103 | log_info "Sufficient disk space available. Required: ${remote_size}B, Available: ${available}B" 104 | } 105 | 106 | # Verify directory structure and index 107 | verify_structure() { 108 | local dir=$1 109 | log_debug "Verifying directory structure at: $dir/photon_data" 110 | if [ ! -d "$dir/photon_data/node_1" ]; then 111 | log_error "Directory structure failed verification. Existing paths: $(find "$dir" -maxdepth 3 -type d | tr '\n' ' ')" 112 | log_error "Invalid structure: missing index directory" 113 | return 1 114 | fi 115 | 116 | 117 | return 0 118 | } 119 | 120 | set_permissions() { 121 | 122 | local dir=$1 123 | log_info "Ensuring correct ownership and permissions for $dir" 124 | log_debug "Current state for $dir: $(stat -c 'Perms: %a, Owner: %U (%u), Group: %G (%g), Name: %n' "$dir" 2>/dev/null || echo "Path $dir not found or stat error")" 125 | 126 | # Change ownership 127 | # The -R flag makes it recursive. 128 | if ! chown -R "$ES_UID:$ES_GID" "$dir"; then 129 | log_info "WARNING: Failed to chown $dir to $ES_UID:$ES_GID. This might be due to host volume restrictions. Opensearch may encounter permission issues if not run as root or if host permissions are incorrect." 130 | else 131 | log_debug "Successfully changed ownership of $dir to $ES_UID:$ES_GID." 132 | fi 133 | 134 | # Change permissions 135 | # 755 means: Owner (opensearch user): Read, Write, Execute (rwx) 136 | # Group (opensearch group): Read, Execute (r-x) 137 | # Others: Read, Execute (r-x) 138 | 139 | if ! chmod -R 755 "$dir"; then 140 | log_info "WARNING: Failed to chmod $dir to 755. This might be due to host volume restrictions." 141 | else 142 | log_debug "Successfully changed permissions of $dir to 755." 143 | fi 144 | 145 | log_info "Post-permission state for $dir: $(stat -c 'Perms: %a, Owner: %U (%u), Group: %G (%g), Name: %n' "$dir" 2>/dev/null || echo "Path $dir not found or stat error after changes")" 146 | } 147 | 148 | 149 | # Check if remote index is newer than local 150 | check_remote_index() { 151 | local url=$1 152 | local full_url="${url}.tar.bz2" 153 | 154 | log_debug "Checking if remote index is newer than local" 155 | log_debug "Remote URL: $full_url" 156 | 157 | # If FORCE_UPDATE is TRUE, skip timestamp check 158 | if [ "${FORCE_UPDATE}" = "TRUE" ]; then 159 | log_info "Force update requested, skipping timestamp check" 160 | return 0 161 | fi 162 | 163 | local remote_time 164 | local wget_output 165 | 166 | # Get remote file timestamp using HEAD request 167 | log_debug "Executing: wget --spider -S \"$full_url\"" 168 | if ! wget_output=$(wget --spider -S "$full_url" 2>&1); then 169 | log_error "Failed to check remote index timestamp" 170 | log_debug "Full wget output:\n$wget_output" 171 | return 2 172 | fi 173 | 174 | log_debug "Full wget spider response:\n$wget_output" 175 | 176 | remote_time=$(echo "$wget_output" | grep -i "Last-Modified:" | cut -d' ' -f4-) 177 | if [ -z "$remote_time" ]; then 178 | log_error "Failed to get remote index timestamp" 179 | log_debug "No Last-Modified header found in wget output" 180 | return 2 181 | fi 182 | 183 | log_debug "Remote timestamp: $remote_time" 184 | 185 | # Convert remote time to epoch 186 | remote_epoch=$(date -d "$remote_time" +%s 2>/dev/null) 187 | 188 | # Get local index timestamp if it exists 189 | if [ -d "$INDEX_DIR" ]; then 190 | local_epoch=$(stat -c %Y "$INDEX_DIR" 2>/dev/null) 191 | 192 | log_debug "Remote index timestamp: $remote_time (${remote_epoch})" 193 | log_debug "Local index timestamp: $(date -d "@$local_epoch" 2>/dev/null) (${local_epoch})" 194 | 195 | # Compare timestamps with 1 day tolerance 196 | local time_diff=$((remote_epoch - local_epoch)) 197 | if [ "${time_diff#-}" -lt 86400 ]; then 198 | log_info "Local index is up to date (within 1 day tolerance)" 199 | return 1 200 | elif [ "$remote_epoch" -gt "$local_epoch" ]; then 201 | log_info "Remote index is newer than local index" 202 | return 0 203 | else 204 | log_info "Local index is up to date" 205 | return 1 206 | fi 207 | else 208 | log_info "No local index found" 209 | return 0 210 | fi 211 | } 212 | 213 | # Core utility functions 214 | download_file() { 215 | local url=$1 216 | local output_file=$2 217 | 218 | log_info "Downloading from $url" 219 | if ! wget --progress=bar:force:noscroll:giga -O "$output_file" "$url"; then 220 | log_error "Failed to download file from $url" 221 | return 1 222 | fi 223 | log_info "Index file downloaded successfully" 224 | return 0 225 | } 226 | 227 | verify_checksum() { 228 | local file=$1 229 | local md5_file=$2 230 | local dir 231 | dir=$(dirname "$file") 232 | 233 | log_info "Verifying MD5 checksum" 234 | if ! (cd "$dir" && md5sum -c <(cut -d' ' -f1 "$(basename "$md5_file")" > temp.md5 && echo "$(cat temp.md5) $(basename "$file")" && rm temp.md5)); then 235 | log_error "MD5 verification failed" 236 | return 1 237 | fi 238 | log_info "Checksum verification successful" 239 | return 0 240 | } 241 | 242 | extract_archive() { 243 | local archive=$1 244 | local extract_dir=$2 245 | 246 | log_debug "Creating extraction directory $extract_dir" 247 | mkdir -p "$extract_dir" 248 | 249 | log_info "Extracting $archive to $extract_dir" 250 | if ! pbzip2 -dc "$archive" | tar x -C "$extract_dir"; then 251 | log_error "Failed to extract files" 252 | return 1 253 | fi 254 | log_info: "Extraction completed successfully" 255 | return 0 256 | } 257 | 258 | stop_photon() { 259 | if [ -f /photon/photon.pid ]; then 260 | local pid 261 | pid=$(cat /photon/photon.pid) 262 | 263 | if ps -p "$pid" > /dev/null; then 264 | log_info "Stopping Photon service (PID: $pid)" 265 | if ! kill -15 "$pid"; then 266 | log_error "Failed to stop Photon service gracefully, attempting force kill" 267 | kill -9 "$pid" || true 268 | fi 269 | 270 | # Wait for process to stop 271 | local count=0 272 | while ps -p "$pid" > /dev/null && [ $count -lt 10 ]; do 273 | sleep 1 274 | ((count++)) 275 | done 276 | 277 | if ps -p "$pid" > /dev/null; then 278 | log_error "Failed to stop Photon service" 279 | return 1 280 | fi 281 | else 282 | log_info "Photon service not running" 283 | fi 284 | rm -f /photon/photon.pid 285 | fi 286 | return 0 287 | } 288 | 289 | move_index() { 290 | local source_dir=$1 291 | local target_dir=$2 292 | 293 | # Find opensearch directory recursively 294 | log_info "Searching for opensearch directory in: $source_dir" 295 | local es_dir 296 | es_dir=$(find "$source_dir" -type d -name "node_1" | head -n 1) 297 | log_info "Found opensearch candidates: $(find "$source_dir" -type d -name "node_1" | tr '\n' ' ')" 298 | 299 | if [ -n "$es_dir" ]; then 300 | log_info "Found opensearch directory at $es_dir" 301 | log_debug "Moving opensearch from $es_dir to $target_dir" 302 | log_info "Current target directory state: $(ls -ld "$target_dir" 2>/dev/null || echo '<not exists>')" 303 | mkdir -p "$(dirname "$target_dir")" 304 | log_info "Parent directory prepared. New state: $(ls -ld "$(dirname "$target_dir")" 2>/dev/null || echo '<not exists>')" 305 | log_info "Executing mv command: mv $es_dir $target_dir" 306 | mv -v "$es_dir" "$target_dir" | while read -r line; do log_debug "mv: $line"; done 307 | log_debug "Move completed. Target directory now contains: $(ls -l "$target_dir" | wc -l) items" 308 | return 0 309 | else 310 | log_error "Could not find opensearch directory in extracted files" 311 | return 1 312 | fi 313 | } 314 | 315 | cleanup_temp() { 316 | log_info "Cleaning up temporary directory" 317 | log_debug "Pre-cleanup temporary directory contents: $(tree -a $TEMP_DIR 2>/dev/null || echo '<empty>')" 318 | log_debug "Executing: rm -rf ${TEMP_DIR:?}" 319 | rm -rfv "${TEMP_DIR:?}" | while read -r line; do log_debug "rm: $line"; done 320 | log_info "Final photon_data directory structure:\n$(tree -L 2 $PHOTON_DATA_DIR 2>/dev/null || echo '<empty>')" 321 | } 322 | 323 | cleanup_stale_es() { 324 | # Remove old elasticsearch index 325 | if [ -d "$ES_DATA_DIR" ]; then 326 | log_info "Removing old elasticsearch directory at $ES_DATA_DIR" 327 | log_debug "Executing: rm -rf $ES_DATA_DIR" 328 | if ! rm -rf "$ES_DATA_DIR"; then 329 | log_error "Failed to remove old elasticsearch index" 330 | fi 331 | fi 332 | } 333 | 334 | # Prepare download URL based on country code or custom base URL 335 | prepare_download_url() { 336 | # Ensure BASE_URL doesn't have trailing slash 337 | local base_url="${BASE_URL%/}" 338 | local result_url 339 | 340 | if [[ -n "${COUNTRY_CODE:-}" ]]; then 341 | result_url="${base_url}/extracts/by-country-code/${COUNTRY_CODE}/photon-db-${COUNTRY_CODE}-latest" 342 | else 343 | result_url="${base_url}/photon-db-latest" 344 | fi 345 | 346 | echo "$result_url" 347 | } 348 | 349 | # Download and verify index 350 | download_index() { 351 | local url 352 | url=$(prepare_download_url) 353 | log_debug "Download URL: $url" 354 | log_debug "Full tar.bz2 URL: ${url}.tar.bz2" 355 | log_debug "Full MD5 URL: ${url}.tar.bz2.md5" 356 | 357 | mkdir -p "$TEMP_DIR" 358 | log_debug "Created temp directory: $TEMP_DIR" 359 | 360 | # Check disk space before downloading 361 | log_debug "Checking disk space for download" 362 | if ! check_disk_space "$url"; then 363 | log_error "Disk space check failed" 364 | cleanup_temp 365 | return 1 366 | fi 367 | 368 | # Download files 369 | local download_url="${url}.tar.bz2" 370 | log_info "Downloading index from ${download_url}" 371 | log_debug "Executing: wget --progress=bar:force:noscroll:giga -O \"$TEMP_DIR/photon-db.tar.bz2\" \"${download_url}\"" 372 | 373 | if ! wget --progress=bar:force:noscroll:giga -O "$TEMP_DIR/photon-db.tar.bz2" "${download_url}" 2>&1; then 374 | log_error "Failed to download index file from ${download_url}" 375 | cleanup_temp 376 | return 1 377 | fi 378 | 379 | log_debug "Index download successful. File size: $(du -h "$TEMP_DIR/photon-db.tar.bz2" | awk '{print $1}')" 380 | 381 | if [ "${SKIP_MD5_CHECK}" != "TRUE" ]; then 382 | log_debug "Downloading MD5 from ${url}.tar.bz2.md5" 383 | log_debug "Executing: wget -O \"$TEMP_DIR/photon-db.md5\" \"${url}.tar.bz2.md5\"" 384 | 385 | local md5_output 386 | md5_output=$(wget -O "$TEMP_DIR/photon-db.md5" "${url}.tar.bz2.md5" 2>&1) 387 | local md5_status=$? 388 | 389 | if [ $md5_status -ne 0 ]; then 390 | log_error "Failed to download MD5 file from ${url}.tar.bz2.md5" 391 | log_debug "wget exit status: $md5_status" 392 | log_debug "wget output: $(echo "$md5_output" | head -20)" 393 | cleanup_temp 394 | return 1 395 | fi 396 | 397 | log_debug "MD5 download successful. MD5 content: $(cat "$TEMP_DIR/photon-db.md5" | head -1)" 398 | 399 | # Verify checksum 400 | log_debug "Starting MD5 verification" 401 | if ! (cd "$TEMP_DIR" && md5sum -c <(awk '{print $1" photon-db.tar.bz2"}' photon-db.md5)); then 402 | log_error "MD5 verification failed" 403 | cleanup_temp 404 | return 1 405 | fi 406 | log_info "MD5 verification successful" 407 | log_debug "MD5 verification completed" 408 | 409 | # Extract archive 410 | log_info "Extracting archive, this may take some time..." 411 | else 412 | log_info "Skipping MD5 verification as requested" 413 | fi 414 | 415 | log_info "Extracting archive to $TEMP_DIR" 416 | # Extract archive in place 417 | if ! pbzip2 -dc "$TEMP_DIR/photon-db.tar.bz2" | tar x -C "$TEMP_DIR"; then 418 | log_error "Failed to extract files" 419 | cleanup_temp 420 | return 1 421 | fi 422 | 423 | log_info "Extraction completed successfully" 424 | return 0 425 | } 426 | 427 | # Strategy-specific update functions 428 | parallel_update() { 429 | log_info "Starting parallel update process" 430 | 431 | # Download and prepare new index while current one is running 432 | log_debug "Downloading new index while current service is running" 433 | if ! download_index; then 434 | log_error "Failed to download index" 435 | return 1 436 | fi 437 | 438 | # Verify the downloaded index 439 | log_info "Verifying downloaded index structure" 440 | if ! verify_structure "$TEMP_DIR"; then 441 | log_error "Downloaded index verification failed" 442 | cleanup_temp 443 | return 1 444 | fi 445 | 446 | 447 | # Stop service and swap indexes 448 | log_info "Stopping Photon service before swapping indexes" 449 | if ! stop_photon; then 450 | log_error "Failed to stop Photon service cleanly" 451 | cleanup_temp 452 | return 1 453 | fi 454 | 455 | # Wait a moment for process to fully stop 456 | sleep 5 457 | 458 | # Backup and swap 459 | if [ -d "$INDEX_DIR" ]; then 460 | log_debug "Backing up current index to $INDEX_DIR.old" 461 | if ! mv "$INDEX_DIR" "$INDEX_DIR.old"; then 462 | log_error "Failed to backup current index" 463 | cleanup_temp 464 | return 1 465 | fi 466 | fi 467 | 468 | log_debug "Moving new index from $TEMP_DIR to $INDEX_DIR" 469 | if ! move_index "$TEMP_DIR" "$INDEX_DIR"; then 470 | log_error "Failed to move index, attempting to restore backup" 471 | if [ -d "$INDEX_DIR.old" ]; then 472 | if ! mv "$INDEX_DIR.old" "$INDEX_DIR"; then 473 | log_error "Failed to restore backup index" 474 | fi 475 | fi 476 | cleanup_temp 477 | return 1 478 | fi 479 | 480 | set_permissions "$INDEX_DIR" # Set permissions on the final index directory 481 | 482 | # Clean up 483 | log_debug "Removing old index backup" 484 | 485 | 486 | rm -rf "$INDEX_DIR.old" 2>/dev/null || true 487 | 488 | 489 | cleanup_stale_es 490 | 491 | log_info "Parallel update completed successfully" 492 | 493 | cleanup_temp 494 | return 0 495 | } 496 | 497 | sequential_update() { 498 | log_info "Starting sequential update process" 499 | 500 | log_info "Stopping Photon service before update" 501 | if ! stop_photon; then 502 | log_error "Failed to stop Photon service cleanly" 503 | return 1 504 | fi 505 | 506 | # Wait a moment for process to fully stop 507 | sleep 2 508 | 509 | # Remove existing index 510 | if [ -d "$INDEX_DIR" ]; then 511 | log_info "Removing existing opensearch directory at $INDEX_DIR" 512 | log_debug "Executing: rm -rf $INDEX_DIR" 513 | if ! rm -rf "$INDEX_DIR"; then 514 | log_error "Failed to remove existing index" 515 | return 1 516 | fi 517 | fi 518 | 519 | cleanup_stale_es 520 | 521 | log_info "Downloading new index" 522 | if ! download_index; then 523 | log_error "Failed to download index" 524 | return 1 525 | fi 526 | 527 | log_info "Moving index from $TEMP_DIR to $INDEX_DIR" 528 | if ! move_index "$TEMP_DIR" "$INDEX_DIR"; then 529 | log_error "Failed to move index" 530 | cleanup_temp 531 | return 1 532 | fi 533 | 534 | set_permissions "$INDEX_DIR" # Set permissions on the final index directory 535 | 536 | log_info "Verifying new index structure" 537 | if ! verify_structure "$DATA_DIR"; then # verify_structure "$DATA_DIR" checks $INDEX_DIR 538 | log_error "Failed to verify new index structure" 539 | cleanup_temp 540 | return 1 541 | fi 542 | 543 | log_info "Sequential update completed successfully" 544 | cleanup_temp 545 | return 0 546 | } 547 | 548 | update_index() { 549 | case "$UPDATE_STRATEGY" in 550 | PARALLEL) 551 | parallel_update 552 | ;; 553 | SEQUENTIAL) 554 | sequential_update 555 | ;; 556 | DISABLED) 557 | log_info "Index updates are disabled" 558 | ;; 559 | *) 560 | log_error "Invalid UPDATE_STRATEGY: $UPDATE_STRATEGY" 561 | return 1 562 | ;; 563 | esac 564 | } 565 | 566 | start_photon() { 567 | # Check if already running 568 | if [ -f /photon/photon.pid ]; then 569 | local pid 570 | pid=$(cat /photon/photon.pid) 571 | if ps -p "$pid" > /dev/null; then 572 | log_info "Photon service already running with PID: $pid" 573 | return 0 574 | else 575 | log_info "Removing stale PID file" 576 | rm -f /photon/photon.pid 577 | fi 578 | fi 579 | 580 | if [ -d "$INDEX_DIR" ]; then 581 | set_permissions "$INDEX_DIR" # Ensure permissions before start 582 | else 583 | log_error "Cannot start Photon: Index directory $INDEX_DIR does not exist." 584 | return 1 585 | fi 586 | 587 | log_info "Starting Photon service" 588 | java -jar photon.jar -data-dir /photon & 589 | local new_pid=$! 590 | echo $new_pid > /photon/photon.pid 591 | 592 | log_info "Photon service started successfully with PID: $new_pid" 593 | return 0 594 | } 595 | 596 | interval_to_seconds() { 597 | local interval=$1 598 | local value=${interval%[smhd]} 599 | local unit=${interval##*[0-9]} 600 | 601 | case $unit in 602 | s) echo $((value)) ;; 603 | m) echo $((value * 60)) ;; 604 | h) echo $((value * 3600)) ;; 605 | d) echo $((value * 86400)) ;; 606 | *) echo $((value * 3600)) ;; # Default to hours 607 | esac 608 | } 609 | 610 | setup_index() { 611 | mkdir -p "$DATA_DIR" "$TEMP_DIR" 612 | 613 | if [ -d "$INDEX_DIR" ]; then 614 | if verify_structure "$DATA_DIR"; then # verify_structure "$DATA_DIR" checks $INDEX_DIR 615 | log_info "Found existing valid opensearch index" 616 | set_permissions "$INDEX_DIR" # Ensure permissions on existing valid index 617 | return 0 618 | else 619 | log_error "Found invalid index structure, downloading fresh index" 620 | rm -rf "$INDEX_DIR" 621 | fi 622 | else 623 | log_info "No opensearch index found, performing initial download" 624 | fi 625 | 626 | if ! sequential_update; then 627 | log_error "Failed to setup initial index" 628 | return 1 629 | fi 630 | 631 | return 0 632 | } 633 | 634 | main() { 635 | if ! setup_index; then 636 | exit 1 637 | fi 638 | 639 | # Only run FORCE_UPDATE once during container startup 640 | if [ "${FORCE_UPDATE}" = "TRUE" ]; then 641 | log_info "Performing forced update on startup" 642 | if ! update_index; then 643 | log_error "Forced update failed" 644 | exit 1 645 | fi 646 | # Disable FORCE_UPDATE after first run 647 | FORCE_UPDATE="FALSE" 648 | log_info "FORCE_UPDATE disabled after initial run" 649 | fi 650 | 651 | if ! start_photon; then 652 | log_error "Failed to start Photon service" 653 | exit 1 654 | fi 655 | 656 | if [ "$UPDATE_STRATEGY" != "DISABLED" ]; then 657 | local update_seconds 658 | update_seconds=$(interval_to_seconds "$UPDATE_INTERVAL") 659 | log_info "Update strategy: $UPDATE_STRATEGY" 660 | log_info "Update interval: $UPDATE_INTERVAL (${update_seconds} seconds)" 661 | 662 | while true; do 663 | log_info "Sleeping for ${update_seconds} seconds until next update" 664 | sleep "$update_seconds" 665 | log_info "Checking for index updates" 666 | 667 | local url 668 | url=$(prepare_download_url) 669 | 670 | if check_remote_index "$url"; then 671 | log_info "Performing scheduled index update" 672 | update_index 673 | # Restart service after update 674 | if ! start_photon; then 675 | log_error "Failed to restart Photon service after update" 676 | exit 1 677 | fi 678 | fi 679 | done 680 | else 681 | log_info "Update strategy is disabled, skipping update loop" 682 | fi 683 | 684 | # Wait for Photon process to finish 685 | wait 686 | } 687 | 688 | main "$@" 689 | --------------------------------------------------------------------------------