├── .github ├── dependabot.yml └── workflows │ ├── build_docker_images.yml │ └── push_readme.yml.disabled ├── .gitignore ├── .pre-commit-config.yaml ├── AUTHORS.md ├── LICENSE ├── README.md ├── desktop ├── Dockerfile └── README.md ├── docker-compose.yml ├── scripts ├── apt_repo.py ├── build-push-docker.sh ├── check_version.sh ├── get_docker_image_version.py └── get_ubuntu_qgis_package_version.py └── server ├── Dockerfile ├── README.md ├── conf ├── nginx-fcgi-sample.conf ├── pg_service.conf └── qgis-server-nginx.conf ├── start-xvfb-nginx.sh └── test └── data └── test_project ├── layers └── layer.gpkg ├── test_project.qgd └── test_project.qgs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "monthly" 7 | -------------------------------------------------------------------------------- /.github/workflows/build_docker_images.yml: -------------------------------------------------------------------------------- 1 | name: Auto build QGIS server Docker images 2 | 3 | on: 4 | schedule: 5 | # runs once a week 6 | - cron: '0 6 * * 0' 7 | # runs every day 8 | # - cron: '0 6 * * *' 9 | pull_request: 10 | branches: 11 | - main 12 | push: 13 | branches: 14 | - main 15 | workflow_dispatch: 16 | 17 | env: 18 | DEFAULT_UBUNTU_RELEASE: noble # for the default dist, no suffix to tag 19 | DOCKER_CLI_EXPERIMENTAL: enabled 20 | 21 | jobs: 22 | build-nightly: 23 | runs-on: ubuntu-latest 24 | name: build nightly 25 | strategy: 26 | fail-fast: false 27 | matrix: 28 | qgis_type: [ 'desktop', 'server' ] 29 | 30 | steps: 31 | - uses: actions/checkout@v4 32 | 33 | - uses: gautamkrishnar/keepalive-workflow@v2 34 | 35 | - name: Login to Docker Hub 36 | uses: docker/login-action@v3 37 | with: 38 | username: ${{ secrets.DOCKER_USERNAME }} 39 | password: ${{ secrets.DOCKER_PASSWORD }} 40 | 41 | - name: define tag 42 | run: | 43 | echo "DOCKER_TAG=$( [[ ${{ matrix.qgis_type }} = 'server' ]] && echo 'qgis-server' || echo 'qgis' )" >> $GITHUB_ENV 44 | 45 | - id: nightly 46 | name: building nightly 47 | run: docker build -t qgis/${{ env.DOCKER_TAG }}:nightly --build-arg repo=ubuntu-nightly -f ${{ matrix.qgis_type }}/Dockerfile . 48 | 49 | - name: test 50 | if: ${{ matrix.qgis_type == 'server' }} 51 | run: | 52 | docker run -d -v $(pwd)/${{ matrix.qgis_type }}/test/data:/io/data -p 8010:80 --name qgis-server qgis/qgis-server:nightly 53 | docker exec -i qgis-server dpkg -l qgis-server 54 | sleep 5 55 | curl -s 'http://localhost:8010/ogc/test_project?service=WMS&request=GetCapabilities' | grep -ivq exception 56 | 57 | - name: push 58 | if: ${{ github.event_name != 'pull_request' }} 59 | run: | 60 | docker push qgis/${{ env.DOCKER_TAG }}:nightly 61 | docker tag qgis/${{ env.DOCKER_TAG }}:nightly qgis/${{ env.DOCKER_TAG }}:latest 62 | docker push qgis/${{ env.DOCKER_TAG }}:latest 63 | 64 | build: 65 | if: ${{ github.event_name != 'pull_request' }} 66 | runs-on: ubuntu-latest 67 | name: build 68 | strategy: 69 | fail-fast: false 70 | matrix: 71 | qgis_type: ['desktop', 'server'] 72 | version: ['stable', 'ltr'] 73 | platform: 74 | - os: ubuntu 75 | release: jammy 76 | - os: ubuntu 77 | release: noble 78 | - os: debian 79 | release: bookworm 80 | 81 | steps: 82 | - uses: actions/checkout@v4 83 | 84 | - id: python_deps 85 | run: pip3 install packaging 86 | 87 | - id: determine 88 | name: determine QGIS and Docker versions 89 | env: 90 | GITHUB_EVENT_NAME: ${{ github.event_name }} 91 | run : | 92 | DOCKER=$(./scripts/get_docker_image_version.py --qgis=${{ matrix.qgis_type }} --dist=${{ matrix.platform.release }}) 93 | QGIS=$(./scripts/get_ubuntu_qgis_package_version.py --qgis=${{ matrix.qgis_type }} --os=${{ matrix.platform.os }} --dist ${{ matrix.platform.release }}) 94 | 95 | DOCKER_VERSION=$(echo "${DOCKER}" | jq ".${{ matrix.version }}") 96 | QGIS_VERSION=$(echo "${QGIS}" | jq ".${{ matrix.version }}") 97 | 98 | echo "Existing ${{ matrix.version }} docker: ${DOCKER_VERSION}" 99 | echo "Available ${{ matrix.version }} QGIS: ${QGIS_VERSION}" 100 | 101 | WILL_UPDATE=$(python3 -c "from packaging import version; print(1 if version.parse(${DOCKER_VERSION} or '0') < version.parse(${QGIS_VERSION}) else 0)") 102 | 103 | if [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" ]]; then 104 | echo "Force build on workflow dispatch." 105 | WILL_UPDATE=1 106 | fi 107 | 108 | if [[ ${WILL_UPDATE} == 1 ]]; then 109 | echo "--> ${{ matrix.version }} will be updated from ${DOCKER_VERSION} to ${QGIS_VERSION}." 110 | else 111 | echo "--> ${{ matrix.version }} is up to date (${QGIS_VERSION})." 112 | fi 113 | 114 | echo "will_update=${WILL_UPDATE}" >> $GITHUB_OUTPUT 115 | echo "qgis_version=${QGIS_VERSION//\"/}">> $GITHUB_OUTPUT 116 | 117 | - name: Set up QEMU 118 | if: ${{ steps.determine.outputs.will_update == 1 }} 119 | uses: docker/setup-qemu-action@v3 120 | with: 121 | image: tonistiigi/binfmt:latest 122 | platforms: arm64,arm 123 | 124 | - name: Set up Docker Buildx 125 | if: ${{ steps.determine.outputs.will_update == 1 }} 126 | uses: docker/setup-buildx-action@v3 127 | 128 | - name: Login to Docker Hub 129 | if: ${{ steps.determine.outputs.will_update == 1 }} 130 | uses: docker/login-action@v3 131 | with: 132 | username: ${{ secrets.DOCKER_USERNAME }} 133 | password: ${{ secrets.DOCKER_PASSWORD }} 134 | 135 | - id: build 136 | if: ${{ steps.determine.outputs.will_update == 1 }} 137 | name: build ubuntu image 138 | env: 139 | DOCKER_USERNAME: ${{ secrets.docker_username }} 140 | DOCKER_PASSWORD: ${{ secrets.docker_password }} 141 | run: ./scripts/build-push-docker.sh ${{ matrix.qgis_type }} ${{ matrix.version }} ${{steps.determine.outputs.qgis_version}} ${{ matrix.platform.os }} ${{ matrix.platform.release }} ${DEFAULT_UBUNTU_RELEASE} 142 | -------------------------------------------------------------------------------- /.github/workflows/push_readme.yml.disabled: -------------------------------------------------------------------------------- 1 | name: Push description (README.md) to Docker Hub 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - README.md 9 | - .github/workflows/push_readme.yml 10 | 11 | 12 | jobs: 13 | build-nightly: 14 | runs-on: ubuntu-latest 15 | name: push description to Docker Hub 16 | steps: 17 | - uses: actions/checkout@v2 18 | 19 | - name: Docker Hub Description 20 | uses: peter-evans/dockerhub-description@v2 21 | with: 22 | username: ${{ secrets.DOCKER_USERNAME }} 23 | password: ${{ secrets.DOCKER_PASSWORD }} 24 | repository: qgis/qgis-server 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.orig 2 | .idea 3 | __pycache__ 4 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | # Fix end of files 3 | - repo: https://github.com/pre-commit/pre-commit-hooks 4 | rev: v4.6.0 5 | hooks: 6 | - id: trailing-whitespace 7 | exclude: .*\.qgs 8 | - id: end-of-file-fixer 9 | exclude: .*\.qgs 10 | - id: mixed-line-ending 11 | exclude: .*\.qgs 12 | args: 13 | - '--fix=lf' 14 | 15 | # Remove unused imports/variables 16 | - repo: https://github.com/PyCQA/autoflake 17 | rev: v2.3.1 18 | hooks: 19 | - id: autoflake 20 | args: 21 | - "--in-place" 22 | - "--remove-all-unused-imports" 23 | - "--remove-unused-variables" 24 | - "--ignore-init-module-imports" 25 | 26 | # Sort imports 27 | - repo: https://github.com/pycqa/isort 28 | rev: "5.13.2" 29 | hooks: 30 | - id: isort 31 | args: ["--profile", "black"] 32 | 33 | # Black formatting 34 | - repo: https://github.com/psf/black 35 | rev: "24.4.2" 36 | hooks: 37 | - id: black 38 | args: ["--line-length=99"] 39 | 40 | # tool to automatically upgrade syntax for newer versions of the language 41 | - repo: https://github.com/asottile/pyupgrade 42 | rev: v3.16.0 43 | hooks: 44 | - id: pyupgrade 45 | args: [--py311-plus] 46 | 47 | # Lint files 48 | - repo: https://github.com/pycqa/flake8 49 | rev: "7.1.0" 50 | hooks: 51 | - id: flake8 52 | args: [ 53 | "--max-line-length=115", 54 | # ignore long comments (E501), as long lines are formatted by black 55 | "--ignore=E501,E203,W503", 56 | "--per-file-ignores=__init__.py:F401", 57 | ] 58 | 59 | # # Static type-checking with mypy 60 | # - repo: https://github.com/pre-commit/mirrors-mypy 61 | # rev: 'v1.5.1' 62 | # hooks: 63 | # - id: mypy 64 | # additional_dependencies: [types-pytz, types-Deprecated, types-PyYAML, types-requests, types-tabulate, types-jsonschema, django-stubs] 65 | # pass_filenames: false 66 | # entry: bash -c 'mypy -p docker-qgis -p docker-app "$@"' -- 67 | 68 | ci: 69 | autofix_prs: true 70 | autoupdate_schedule: quarterly 71 | -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | ## Authors 2 | 3 | - 2018 - 2020 Project sponsored by the [GEM Foundation](https://github.com/gem/) 4 | - 2020 - 2021 Project sponsored by the [GEM Foundation](https://github.com/gem/) and [OPENGIS.ch](https://github.com/opengisch/) 5 | - October 2021 Ownership transfer to OPENGIS.ch 6 | - 2021 - 2024 Project sponsored by [OPENGIS.ch](https://github.com/opengisch/) 7 | - 2024 - Donation to QGIS.org 8 | 9 | ## Contributors 10 | 11 | - Marco Bernasocchi [@mbernasocchi](https://github.com/mbernasocchi/) 12 | - Denis Rouzaud [@3nids](https://github.com/3nids) 13 | - Matthias Kuhn [@m-kuhn](https://github.com/m-kuhn) 14 | - Daniele Viganò [@daniviga](https://github.com/daniviga) 15 | - Matteo Nastasi [@nastasi-oq](https://github.com/nastasi-oq) 16 | - Antonio Ettorre [@vot4anto](https://github.com/vot4anto) 17 | - Alessandro Pasotti [@elpaso](https://github.com/elpaso) 18 | 19 | ## QGIS upstream project 20 | 21 | - https://github.com/qgis/QGIS/blob/master/doc/AUTHORS 22 | - https://github.com/qgis/QGIS/blob/master/doc/CONTRIBUTORS 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU AFFERO GENERAL PUBLIC LICENSE 2 | Version 3, 19 November 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU Affero General Public License is a free, copyleft license for 11 | software and other kinds of works, specifically designed to ensure 12 | cooperation with the community in the case of network server software. 13 | 14 | The licenses for most software and other practical works are designed 15 | to take away your freedom to share and change the works. By contrast, 16 | our General Public Licenses are intended to guarantee your freedom to 17 | share and change all versions of a program--to make sure it remains free 18 | software for all its users. 19 | 20 | When we speak of free software, we are referring to freedom, not 21 | price. Our General Public Licenses are designed to make sure that you 22 | have the freedom to distribute copies of free software (and charge for 23 | them if you wish), that you receive source code or can get it if you 24 | want it, that you can change the software or use pieces of it in new 25 | free programs, and that you know you can do these things. 26 | 27 | Developers that use our General Public Licenses protect your rights 28 | with two steps: (1) assert copyright on the software, and (2) offer 29 | you this License which gives you legal permission to copy, distribute 30 | and/or modify the software. 31 | 32 | A secondary benefit of defending all users' freedom is that 33 | improvements made in alternate versions of the program, if they 34 | receive widespread use, become available for other developers to 35 | incorporate. Many developers of free software are heartened and 36 | encouraged by the resulting cooperation. However, in the case of 37 | software used on network servers, this result may fail to come about. 38 | The GNU General Public License permits making a modified version and 39 | letting the public access it on a server without ever releasing its 40 | source code to the public. 41 | 42 | The GNU Affero General Public License is designed specifically to 43 | ensure that, in such cases, the modified source code becomes available 44 | to the community. It requires the operator of a network server to 45 | provide the source code of the modified version running there to the 46 | users of that server. Therefore, public use of a modified version, on 47 | a publicly accessible server, gives the public access to the source 48 | code of the modified version. 49 | 50 | An older license, called the Affero General Public License and 51 | published by Affero, was designed to accomplish similar goals. This is 52 | a different license, not a version of the Affero GPL, but Affero has 53 | released a new version of the Affero GPL which permits relicensing under 54 | this license. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | TERMS AND CONDITIONS 60 | 61 | 0. Definitions. 62 | 63 | "This License" refers to version 3 of the GNU Affero General Public License. 64 | 65 | "Copyright" also means copyright-like laws that apply to other kinds of 66 | works, such as semiconductor masks. 67 | 68 | "The Program" refers to any copyrightable work licensed under this 69 | License. Each licensee is addressed as "you". "Licensees" and 70 | "recipients" may be individuals or organizations. 71 | 72 | To "modify" a work means to copy from or adapt all or part of the work 73 | in a fashion requiring copyright permission, other than the making of an 74 | exact copy. The resulting work is called a "modified version" of the 75 | earlier work or a work "based on" the earlier work. 76 | 77 | A "covered work" means either the unmodified Program or a work based 78 | on the Program. 79 | 80 | To "propagate" a work means to do anything with it that, without 81 | permission, would make you directly or secondarily liable for 82 | infringement under applicable copyright law, except executing it on a 83 | computer or modifying a private copy. Propagation includes copying, 84 | distribution (with or without modification), making available to the 85 | public, and in some countries other activities as well. 86 | 87 | To "convey" a work means any kind of propagation that enables other 88 | parties to make or receive copies. Mere interaction with a user through 89 | a computer network, with no transfer of a copy, is not conveying. 90 | 91 | An interactive user interface displays "Appropriate Legal Notices" 92 | to the extent that it includes a convenient and prominently visible 93 | feature that (1) displays an appropriate copyright notice, and (2) 94 | tells the user that there is no warranty for the work (except to the 95 | extent that warranties are provided), that licensees may convey the 96 | work under this License, and how to view a copy of this License. If 97 | the interface presents a list of user commands or options, such as a 98 | menu, a prominent item in the list meets this criterion. 99 | 100 | 1. Source Code. 101 | 102 | The "source code" for a work means the preferred form of the work 103 | for making modifications to it. "Object code" means any non-source 104 | form of a work. 105 | 106 | A "Standard Interface" means an interface that either is an official 107 | standard defined by a recognized standards body, or, in the case of 108 | interfaces specified for a particular programming language, one that 109 | is widely used among developers working in that language. 110 | 111 | The "System Libraries" of an executable work include anything, other 112 | than the work as a whole, that (a) is included in the normal form of 113 | packaging a Major Component, but which is not part of that Major 114 | Component, and (b) serves only to enable use of the work with that 115 | Major Component, or to implement a Standard Interface for which an 116 | implementation is available to the public in source code form. A 117 | "Major Component", in this context, means a major essential component 118 | (kernel, window system, and so on) of the specific operating system 119 | (if any) on which the executable work runs, or a compiler used to 120 | produce the work, or an object code interpreter used to run it. 121 | 122 | The "Corresponding Source" for a work in object code form means all 123 | the source code needed to generate, install, and (for an executable 124 | work) run the object code and to modify the work, including scripts to 125 | control those activities. However, it does not include the work's 126 | System Libraries, or general-purpose tools or generally available free 127 | programs which are used unmodified in performing those activities but 128 | which are not part of the work. For example, Corresponding Source 129 | includes interface definition files associated with source files for 130 | the work, and the source code for shared libraries and dynamically 131 | linked subprograms that the work is specifically designed to require, 132 | such as by intimate data communication or control flow between those 133 | subprograms and other parts of the work. 134 | 135 | The Corresponding Source need not include anything that users 136 | can regenerate automatically from other parts of the Corresponding 137 | Source. 138 | 139 | The Corresponding Source for a work in source code form is that 140 | same work. 141 | 142 | 2. Basic Permissions. 143 | 144 | All rights granted under this License are granted for the term of 145 | copyright on the Program, and are irrevocable provided the stated 146 | conditions are met. This License explicitly affirms your unlimited 147 | permission to run the unmodified Program. The output from running a 148 | covered work is covered by this License only if the output, given its 149 | content, constitutes a covered work. This License acknowledges your 150 | rights of fair use or other equivalent, as provided by copyright law. 151 | 152 | You may make, run and propagate covered works that you do not 153 | convey, without conditions so long as your license otherwise remains 154 | in force. You may convey covered works to others for the sole purpose 155 | of having them make modifications exclusively for you, or provide you 156 | with facilities for running those works, provided that you comply with 157 | the terms of this License in conveying all material for which you do 158 | not control copyright. Those thus making or running the covered works 159 | for you must do so exclusively on your behalf, under your direction 160 | and control, on terms that prohibit them from making any copies of 161 | your copyrighted material outside their relationship with you. 162 | 163 | Conveying under any other circumstances is permitted solely under 164 | the conditions stated below. Sublicensing is not allowed; section 10 165 | makes it unnecessary. 166 | 167 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 168 | 169 | No covered work shall be deemed part of an effective technological 170 | measure under any applicable law fulfilling obligations under article 171 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 172 | similar laws prohibiting or restricting circumvention of such 173 | measures. 174 | 175 | When you convey a covered work, you waive any legal power to forbid 176 | circumvention of technological measures to the extent such circumvention 177 | is effected by exercising rights under this License with respect to 178 | the covered work, and you disclaim any intention to limit operation or 179 | modification of the work as a means of enforcing, against the work's 180 | users, your or third parties' legal rights to forbid circumvention of 181 | technological measures. 182 | 183 | 4. Conveying Verbatim Copies. 184 | 185 | You may convey verbatim copies of the Program's source code as you 186 | receive it, in any medium, provided that you conspicuously and 187 | appropriately publish on each copy an appropriate copyright notice; 188 | keep intact all notices stating that this License and any 189 | non-permissive terms added in accord with section 7 apply to the code; 190 | keep intact all notices of the absence of any warranty; and give all 191 | recipients a copy of this License along with the Program. 192 | 193 | You may charge any price or no price for each copy that you convey, 194 | and you may offer support or warranty protection for a fee. 195 | 196 | 5. Conveying Modified Source Versions. 197 | 198 | You may convey a work based on the Program, or the modifications to 199 | produce it from the Program, in the form of source code under the 200 | terms of section 4, provided that you also meet all of these conditions: 201 | 202 | a) The work must carry prominent notices stating that you modified 203 | it, and giving a relevant date. 204 | 205 | b) The work must carry prominent notices stating that it is 206 | released under this License and any conditions added under section 207 | 7. This requirement modifies the requirement in section 4 to 208 | "keep intact all notices". 209 | 210 | c) You must license the entire work, as a whole, under this 211 | License to anyone who comes into possession of a copy. This 212 | License will therefore apply, along with any applicable section 7 213 | additional terms, to the whole of the work, and all its parts, 214 | regardless of how they are packaged. This License gives no 215 | permission to license the work in any other way, but it does not 216 | invalidate such permission if you have separately received it. 217 | 218 | d) If the work has interactive user interfaces, each must display 219 | Appropriate Legal Notices; however, if the Program has interactive 220 | interfaces that do not display Appropriate Legal Notices, your 221 | work need not make them do so. 222 | 223 | A compilation of a covered work with other separate and independent 224 | works, which are not by their nature extensions of the covered work, 225 | and which are not combined with it such as to form a larger program, 226 | in or on a volume of a storage or distribution medium, is called an 227 | "aggregate" if the compilation and its resulting copyright are not 228 | used to limit the access or legal rights of the compilation's users 229 | beyond what the individual works permit. Inclusion of a covered work 230 | in an aggregate does not cause this License to apply to the other 231 | parts of the aggregate. 232 | 233 | 6. Conveying Non-Source Forms. 234 | 235 | You may convey a covered work in object code form under the terms 236 | of sections 4 and 5, provided that you also convey the 237 | machine-readable Corresponding Source under the terms of this License, 238 | in one of these ways: 239 | 240 | a) Convey the object code in, or embodied in, a physical product 241 | (including a physical distribution medium), accompanied by the 242 | Corresponding Source fixed on a durable physical medium 243 | customarily used for software interchange. 244 | 245 | b) Convey the object code in, or embodied in, a physical product 246 | (including a physical distribution medium), accompanied by a 247 | written offer, valid for at least three years and valid for as 248 | long as you offer spare parts or customer support for that product 249 | model, to give anyone who possesses the object code either (1) a 250 | copy of the Corresponding Source for all the software in the 251 | product that is covered by this License, on a durable physical 252 | medium customarily used for software interchange, for a price no 253 | more than your reasonable cost of physically performing this 254 | conveying of source, or (2) access to copy the 255 | Corresponding Source from a network server at no charge. 256 | 257 | c) Convey individual copies of the object code with a copy of the 258 | written offer to provide the Corresponding Source. This 259 | alternative is allowed only occasionally and noncommercially, and 260 | only if you received the object code with such an offer, in accord 261 | with subsection 6b. 262 | 263 | d) Convey the object code by offering access from a designated 264 | place (gratis or for a charge), and offer equivalent access to the 265 | Corresponding Source in the same way through the same place at no 266 | further charge. You need not require recipients to copy the 267 | Corresponding Source along with the object code. If the place to 268 | copy the object code is a network server, the Corresponding Source 269 | may be on a different server (operated by you or a third party) 270 | that supports equivalent copying facilities, provided you maintain 271 | clear directions next to the object code saying where to find the 272 | Corresponding Source. Regardless of what server hosts the 273 | Corresponding Source, you remain obligated to ensure that it is 274 | available for as long as needed to satisfy these requirements. 275 | 276 | e) Convey the object code using peer-to-peer transmission, provided 277 | you inform other peers where the object code and Corresponding 278 | Source of the work are being offered to the general public at no 279 | charge under subsection 6d. 280 | 281 | A separable portion of the object code, whose source code is excluded 282 | from the Corresponding Source as a System Library, need not be 283 | included in conveying the object code work. 284 | 285 | A "User Product" is either (1) a "consumer product", which means any 286 | tangible personal property which is normally used for personal, family, 287 | or household purposes, or (2) anything designed or sold for incorporation 288 | into a dwelling. In determining whether a product is a consumer product, 289 | doubtful cases shall be resolved in favor of coverage. For a particular 290 | product received by a particular user, "normally used" refers to a 291 | typical or common use of that class of product, regardless of the status 292 | of the particular user or of the way in which the particular user 293 | actually uses, or expects or is expected to use, the product. A product 294 | is a consumer product regardless of whether the product has substantial 295 | commercial, industrial or non-consumer uses, unless such uses represent 296 | the only significant mode of use of the product. 297 | 298 | "Installation Information" for a User Product means any methods, 299 | procedures, authorization keys, or other information required to install 300 | and execute modified versions of a covered work in that User Product from 301 | a modified version of its Corresponding Source. The information must 302 | suffice to ensure that the continued functioning of the modified object 303 | code is in no case prevented or interfered with solely because 304 | modification has been made. 305 | 306 | If you convey an object code work under this section in, or with, or 307 | specifically for use in, a User Product, and the conveying occurs as 308 | part of a transaction in which the right of possession and use of the 309 | User Product is transferred to the recipient in perpetuity or for a 310 | fixed term (regardless of how the transaction is characterized), the 311 | Corresponding Source conveyed under this section must be accompanied 312 | by the Installation Information. But this requirement does not apply 313 | if neither you nor any third party retains the ability to install 314 | modified object code on the User Product (for example, the work has 315 | been installed in ROM). 316 | 317 | The requirement to provide Installation Information does not include a 318 | requirement to continue to provide support service, warranty, or updates 319 | for a work that has been modified or installed by the recipient, or for 320 | the User Product in which it has been modified or installed. Access to a 321 | network may be denied when the modification itself materially and 322 | adversely affects the operation of the network or violates the rules and 323 | protocols for communication across the network. 324 | 325 | Corresponding Source conveyed, and Installation Information provided, 326 | in accord with this section must be in a format that is publicly 327 | documented (and with an implementation available to the public in 328 | source code form), and must require no special password or key for 329 | unpacking, reading or copying. 330 | 331 | 7. Additional Terms. 332 | 333 | "Additional permissions" are terms that supplement the terms of this 334 | License by making exceptions from one or more of its conditions. 335 | Additional permissions that are applicable to the entire Program shall 336 | be treated as though they were included in this License, to the extent 337 | that they are valid under applicable law. If additional permissions 338 | apply only to part of the Program, that part may be used separately 339 | under those permissions, but the entire Program remains governed by 340 | this License without regard to the additional permissions. 341 | 342 | When you convey a copy of a covered work, you may at your option 343 | remove any additional permissions from that copy, or from any part of 344 | it. (Additional permissions may be written to require their own 345 | removal in certain cases when you modify the work.) You may place 346 | additional permissions on material, added by you to a covered work, 347 | for which you have or can give appropriate copyright permission. 348 | 349 | Notwithstanding any other provision of this License, for material you 350 | add to a covered work, you may (if authorized by the copyright holders of 351 | that material) supplement the terms of this License with terms: 352 | 353 | a) Disclaiming warranty or limiting liability differently from the 354 | terms of sections 15 and 16 of this License; or 355 | 356 | b) Requiring preservation of specified reasonable legal notices or 357 | author attributions in that material or in the Appropriate Legal 358 | Notices displayed by works containing it; or 359 | 360 | c) Prohibiting misrepresentation of the origin of that material, or 361 | requiring that modified versions of such material be marked in 362 | reasonable ways as different from the original version; or 363 | 364 | d) Limiting the use for publicity purposes of names of licensors or 365 | authors of the material; or 366 | 367 | e) Declining to grant rights under trademark law for use of some 368 | trade names, trademarks, or service marks; or 369 | 370 | f) Requiring indemnification of licensors and authors of that 371 | material by anyone who conveys the material (or modified versions of 372 | it) with contractual assumptions of liability to the recipient, for 373 | any liability that these contractual assumptions directly impose on 374 | those licensors and authors. 375 | 376 | All other non-permissive additional terms are considered "further 377 | restrictions" within the meaning of section 10. If the Program as you 378 | received it, or any part of it, contains a notice stating that it is 379 | governed by this License along with a term that is a further 380 | restriction, you may remove that term. If a license document contains 381 | a further restriction but permits relicensing or conveying under this 382 | License, you may add to a covered work material governed by the terms 383 | of that license document, provided that the further restriction does 384 | not survive such relicensing or conveying. 385 | 386 | If you add terms to a covered work in accord with this section, you 387 | must place, in the relevant source files, a statement of the 388 | additional terms that apply to those files, or a notice indicating 389 | where to find the applicable terms. 390 | 391 | Additional terms, permissive or non-permissive, may be stated in the 392 | form of a separately written license, or stated as exceptions; 393 | the above requirements apply either way. 394 | 395 | 8. Termination. 396 | 397 | You may not propagate or modify a covered work except as expressly 398 | provided under this License. Any attempt otherwise to propagate or 399 | modify it is void, and will automatically terminate your rights under 400 | this License (including any patent licenses granted under the third 401 | paragraph of section 11). 402 | 403 | However, if you cease all violation of this License, then your 404 | license from a particular copyright holder is reinstated (a) 405 | provisionally, unless and until the copyright holder explicitly and 406 | finally terminates your license, and (b) permanently, if the copyright 407 | holder fails to notify you of the violation by some reasonable means 408 | prior to 60 days after the cessation. 409 | 410 | Moreover, your license from a particular copyright holder is 411 | reinstated permanently if the copyright holder notifies you of the 412 | violation by some reasonable means, this is the first time you have 413 | received notice of violation of this License (for any work) from that 414 | copyright holder, and you cure the violation prior to 30 days after 415 | your receipt of the notice. 416 | 417 | Termination of your rights under this section does not terminate the 418 | licenses of parties who have received copies or rights from you under 419 | this License. If your rights have been terminated and not permanently 420 | reinstated, you do not qualify to receive new licenses for the same 421 | material under section 10. 422 | 423 | 9. Acceptance Not Required for Having Copies. 424 | 425 | You are not required to accept this License in order to receive or 426 | run a copy of the Program. Ancillary propagation of a covered work 427 | occurring solely as a consequence of using peer-to-peer transmission 428 | to receive a copy likewise does not require acceptance. However, 429 | nothing other than this License grants you permission to propagate or 430 | modify any covered work. These actions infringe copyright if you do 431 | not accept this License. Therefore, by modifying or propagating a 432 | covered work, you indicate your acceptance of this License to do so. 433 | 434 | 10. Automatic Licensing of Downstream Recipients. 435 | 436 | Each time you convey a covered work, the recipient automatically 437 | receives a license from the original licensors, to run, modify and 438 | propagate that work, subject to this License. You are not responsible 439 | for enforcing compliance by third parties with this License. 440 | 441 | An "entity transaction" is a transaction transferring control of an 442 | organization, or substantially all assets of one, or subdividing an 443 | organization, or merging organizations. If propagation of a covered 444 | work results from an entity transaction, each party to that 445 | transaction who receives a copy of the work also receives whatever 446 | licenses to the work the party's predecessor in interest had or could 447 | give under the previous paragraph, plus a right to possession of the 448 | Corresponding Source of the work from the predecessor in interest, if 449 | the predecessor has it or can get it with reasonable efforts. 450 | 451 | You may not impose any further restrictions on the exercise of the 452 | rights granted or affirmed under this License. For example, you may 453 | not impose a license fee, royalty, or other charge for exercise of 454 | rights granted under this License, and you may not initiate litigation 455 | (including a cross-claim or counterclaim in a lawsuit) alleging that 456 | any patent claim is infringed by making, using, selling, offering for 457 | sale, or importing the Program or any portion of it. 458 | 459 | 11. Patents. 460 | 461 | A "contributor" is a copyright holder who authorizes use under this 462 | License of the Program or a work on which the Program is based. The 463 | work thus licensed is called the contributor's "contributor version". 464 | 465 | A contributor's "essential patent claims" are all patent claims 466 | owned or controlled by the contributor, whether already acquired or 467 | hereafter acquired, that would be infringed by some manner, permitted 468 | by this License, of making, using, or selling its contributor version, 469 | but do not include claims that would be infringed only as a 470 | consequence of further modification of the contributor version. For 471 | purposes of this definition, "control" includes the right to grant 472 | patent sublicenses in a manner consistent with the requirements of 473 | this License. 474 | 475 | Each contributor grants you a non-exclusive, worldwide, royalty-free 476 | patent license under the contributor's essential patent claims, to 477 | make, use, sell, offer for sale, import and otherwise run, modify and 478 | propagate the contents of its contributor version. 479 | 480 | In the following three paragraphs, a "patent license" is any express 481 | agreement or commitment, however denominated, not to enforce a patent 482 | (such as an express permission to practice a patent or covenant not to 483 | sue for patent infringement). To "grant" such a patent license to a 484 | party means to make such an agreement or commitment not to enforce a 485 | patent against the party. 486 | 487 | If you convey a covered work, knowingly relying on a patent license, 488 | and the Corresponding Source of the work is not available for anyone 489 | to copy, free of charge and under the terms of this License, through a 490 | publicly available network server or other readily accessible means, 491 | then you must either (1) cause the Corresponding Source to be so 492 | available, or (2) arrange to deprive yourself of the benefit of the 493 | patent license for this particular work, or (3) arrange, in a manner 494 | consistent with the requirements of this License, to extend the patent 495 | license to downstream recipients. "Knowingly relying" means you have 496 | actual knowledge that, but for the patent license, your conveying the 497 | covered work in a country, or your recipient's use of the covered work 498 | in a country, would infringe one or more identifiable patents in that 499 | country that you have reason to believe are valid. 500 | 501 | If, pursuant to or in connection with a single transaction or 502 | arrangement, you convey, or propagate by procuring conveyance of, a 503 | covered work, and grant a patent license to some of the parties 504 | receiving the covered work authorizing them to use, propagate, modify 505 | or convey a specific copy of the covered work, then the patent license 506 | you grant is automatically extended to all recipients of the covered 507 | work and works based on it. 508 | 509 | A patent license is "discriminatory" if it does not include within 510 | the scope of its coverage, prohibits the exercise of, or is 511 | conditioned on the non-exercise of one or more of the rights that are 512 | specifically granted under this License. You may not convey a covered 513 | work if you are a party to an arrangement with a third party that is 514 | in the business of distributing software, under which you make payment 515 | to the third party based on the extent of your activity of conveying 516 | the work, and under which the third party grants, to any of the 517 | parties who would receive the covered work from you, a discriminatory 518 | patent license (a) in connection with copies of the covered work 519 | conveyed by you (or copies made from those copies), or (b) primarily 520 | for and in connection with specific products or compilations that 521 | contain the covered work, unless you entered into that arrangement, 522 | or that patent license was granted, prior to 28 March 2007. 523 | 524 | Nothing in this License shall be construed as excluding or limiting 525 | any implied license or other defenses to infringement that may 526 | otherwise be available to you under applicable patent law. 527 | 528 | 12. No Surrender of Others' Freedom. 529 | 530 | If conditions are imposed on you (whether by court order, agreement or 531 | otherwise) that contradict the conditions of this License, they do not 532 | excuse you from the conditions of this License. If you cannot convey a 533 | covered work so as to satisfy simultaneously your obligations under this 534 | License and any other pertinent obligations, then as a consequence you may 535 | not convey it at all. For example, if you agree to terms that obligate you 536 | to collect a royalty for further conveying from those to whom you convey 537 | the Program, the only way you could satisfy both those terms and this 538 | License would be to refrain entirely from conveying the Program. 539 | 540 | 13. Remote Network Interaction; Use with the GNU General Public License. 541 | 542 | Notwithstanding any other provision of this License, if you modify the 543 | Program, your modified version must prominently offer all users 544 | interacting with it remotely through a computer network (if your version 545 | supports such interaction) an opportunity to receive the Corresponding 546 | Source of your version by providing access to the Corresponding Source 547 | from a network server at no charge, through some standard or customary 548 | means of facilitating copying of software. This Corresponding Source 549 | shall include the Corresponding Source for any work covered by version 3 550 | of the GNU General Public License that is incorporated pursuant to the 551 | following paragraph. 552 | 553 | Notwithstanding any other provision of this License, you have 554 | permission to link or combine any covered work with a work licensed 555 | under version 3 of the GNU General Public License into a single 556 | combined work, and to convey the resulting work. The terms of this 557 | License will continue to apply to the part which is the covered work, 558 | but the work with which it is combined will remain governed by version 559 | 3 of the GNU General Public License. 560 | 561 | 14. Revised Versions of this License. 562 | 563 | The Free Software Foundation may publish revised and/or new versions of 564 | the GNU Affero General Public License from time to time. Such new versions 565 | will be similar in spirit to the present version, but may differ in detail to 566 | address new problems or concerns. 567 | 568 | Each version is given a distinguishing version number. If the 569 | Program specifies that a certain numbered version of the GNU Affero General 570 | Public License "or any later version" applies to it, you have the 571 | option of following the terms and conditions either of that numbered 572 | version or of any later version published by the Free Software 573 | Foundation. If the Program does not specify a version number of the 574 | GNU Affero General Public License, you may choose any version ever published 575 | by the Free Software Foundation. 576 | 577 | If the Program specifies that a proxy can decide which future 578 | versions of the GNU Affero General Public License can be used, that proxy's 579 | public statement of acceptance of a version permanently authorizes you 580 | to choose that version for the Program. 581 | 582 | Later license versions may give you additional or different 583 | permissions. However, no additional obligations are imposed on any 584 | author or copyright holder as a result of your choosing to follow a 585 | later version. 586 | 587 | 15. Disclaimer of Warranty. 588 | 589 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 590 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 591 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 592 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 593 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 594 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 595 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 596 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 597 | 598 | 16. Limitation of Liability. 599 | 600 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 601 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 602 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 603 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 604 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 605 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 606 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 607 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 608 | SUCH DAMAGES. 609 | 610 | 17. Interpretation of Sections 15 and 16. 611 | 612 | If the disclaimer of warranty and limitation of liability provided 613 | above cannot be given local legal effect according to their terms, 614 | reviewing courts shall apply local law that most closely approximates 615 | an absolute waiver of all civil liability in connection with the 616 | Program, unless a warranty or assumption of liability accompanies a 617 | copy of the Program in return for a fee. 618 | 619 | END OF TERMS AND CONDITIONS 620 | 621 | How to Apply These Terms to Your New Programs 622 | 623 | If you develop a new program, and you want it to be of the greatest 624 | possible use to the public, the best way to achieve this is to make it 625 | free software which everyone can redistribute and change under these terms. 626 | 627 | To do so, attach the following notices to the program. It is safest 628 | to attach them to the start of each source file to most effectively 629 | state the exclusion of warranty; and each file should have at least 630 | the "copyright" line and a pointer to where the full notice is found. 631 | 632 | 633 | Copyright (C) 634 | 635 | This program is free software: you can redistribute it and/or modify 636 | it under the terms of the GNU Affero General Public License as published 637 | by the Free Software Foundation, either version 3 of the License, or 638 | (at your option) any later version. 639 | 640 | This program is distributed in the hope that it will be useful, 641 | but WITHOUT ANY WARRANTY; without even the implied warranty of 642 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 643 | GNU Affero General Public License for more details. 644 | 645 | You should have received a copy of the GNU Affero General Public License 646 | along with this program. If not, see . 647 | 648 | Also add information on how to contact you by electronic and paper mail. 649 | 650 | If your software can interact with users remotely through a computer 651 | network, you should also make sure that it provides a way for users to 652 | get its source. For example, if your program is a web application, its 653 | interface could display a "Source" link that leads users to an archive 654 | of the code. There are many ways you could offer source, and different 655 | solutions will be better for different programs; see section 13 for the 656 | specific requirements. 657 | 658 | You should also get your employer (if you work as a programmer) or school, 659 | if any, to sign a "copyright disclaimer" for the program, if necessary. 660 | For more information on this, and how to apply and follow the GNU AGPL, see 661 | . 662 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QGIS Docker Images 2 | 3 | This repository automates the build of QGIS desktop and QGIS server Docker images. 4 | 5 | ## Desktop 6 | 7 | [![Docker Hub](https://img.shields.io/docker/pulls/qgis/qgis)](https://hub.docker.com/r/qgis/qgis) 8 | 9 | - [Documentation](./desktop/README.md) 10 | 11 | ## Server 12 | 13 | [![Docker Hub](https://img.shields.io/docker/pulls/qgis/qgis-server)](https://hub.docker.com/r/qgis/qgis-server) 14 | 15 | - [Documentation](./server/README.md) 16 | -------------------------------------------------------------------------------- /desktop/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG os=ubuntu 2 | ARG release=noble 3 | 4 | FROM ${os}:${release} 5 | LABEL maintainer="OPENGIS.ch " 6 | 7 | ARG os 8 | ARG release 9 | ARG repo 10 | ARG qgis_version=master 11 | 12 | RUN apt update && apt install -y gnupg wget software-properties-common && \ 13 | wget -qO - https://qgis.org/downloads/qgis-2022.gpg.key | gpg --no-default-keyring --keyring gnupg-ring:/etc/apt/trusted.gpg.d/qgis-archive.gpg --import && \ 14 | chmod a+r /etc/apt/trusted.gpg.d/qgis-archive.gpg && \ 15 | # run twice because of https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1041012 16 | add-apt-repository "deb https://qgis.org/${repo} ${release} main" && \ 17 | add-apt-repository "deb https://qgis.org/${repo} ${release} main" && \ 18 | apt update && \ 19 | DEBIAN_FRONTEND=noninteractive apt-get install -y python3-pip qgis python3-qgis python3-qgis-common python3-venv \ 20 | python3-pytest python3-mock xvfb qttools5-dev-tools pyqt5-dev-tools && \ 21 | apt-get clean 22 | 23 | # Test version 24 | COPY scripts/check_version.sh /opt/check_version.sh 25 | RUN /opt/check_version.sh ${qgis_version} 26 | -------------------------------------------------------------------------------- /desktop/README.md: -------------------------------------------------------------------------------- 1 | # QGIS Desktop standalone 2 | 3 | [![Docker Hub](https://img.shields.io/docker/pulls/qgis/qgis)](https://hub.docker.com/r/qgis/qgis) 4 | 5 | A simple QGIS desktop Docker image, pushed on [Docker Hub](https://hub.docker.com/r/qgis/qgis). 6 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # vim: tabstop=4 shiftwidth=4 softtabstop=4 3 | # 4 | # oq-qgis-server 5 | # Copyright (C) 2018-2019 GEM Foundation 6 | # 7 | # oq-qgis-server is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU Affero General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # oq-qgis-server is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU Affero General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU Affero General Public License 18 | # along with this program. If not, see . 19 | 20 | version: '2' 21 | services: 22 | nginx: 23 | image: "nginx" 24 | volumes: 25 | - ./conf/nginx-fcgi-sample.conf:/etc/nginx/nginx.conf 26 | ports: 27 | - "127.0.0.1:8010:80" 28 | networks: 29 | - qgis-server-net 30 | restart: always 31 | depends_on: 32 | - qgis-server 33 | qgis-server: 34 | image: "qgis/qgis-server:stable" 35 | environment: 36 | # Do not run the embedded copy of nginx 37 | SKIP_NGINX: "true" 38 | # Improve rendering performance 39 | QGIS_SERVER_PARALLEL_RENDERING: "true" 40 | QGIS_SERVER_MAX_THREADS: 4 41 | # Limit the maximum size returned by a GetMap 42 | QGIS_SERVER_WMS_MAX_HEIGHT: 5000 43 | QGIS_SERVER_WMS_MAX_WIDTH: 5000 44 | networks: 45 | - qgis-server-net 46 | volumes: 47 | # Data should be mount RO when working 48 | # with GeoPackages and more than one QGIS container 49 | - ./test/data:/io/data:ro 50 | - ./plugins:/io/plugins 51 | - ./fonts:/usr/share/fonts 52 | - ./svg:/var/lib/qgis/.local/share/QGIS/QGIS3/profiles/default/svg 53 | # - ./conf/pg_service.conf:/etc/postgresql-common/pg_service.conf:ro 54 | restart: always 55 | 56 | networks: 57 | qgis-server-net: 58 | -------------------------------------------------------------------------------- /scripts/apt_repo.py: -------------------------------------------------------------------------------- 1 | # this is a modified version of https://github.com/brennerm/python-apt-repo/ 2 | # using requests (see https://github.com/brennerm/python-apt-repo/pull/5) 3 | 4 | import bz2 5 | import gzip 6 | import lzma 7 | import posixpath 8 | import re 9 | 10 | import requests 11 | 12 | 13 | def __download_raw(url): 14 | """ 15 | Downloads a binary file 16 | 17 | # Arguments 18 | url (str): URL to file 19 | """ 20 | return requests.get(url).content 21 | 22 | 23 | def _download(url): 24 | """ 25 | Downloads a UTF-8 encoded file 26 | 27 | # Arguments 28 | url (str): URL to file 29 | """ 30 | return __download_raw(url).decode("utf-8") 31 | 32 | 33 | def _download_compressed(base_url): 34 | """ 35 | Downloads a compressed file 36 | 37 | It tries out multiple compression algorithms by iterating through the according file suffixes. 38 | 39 | # Arguments 40 | url (str): URL to file 41 | """ 42 | decompress = { 43 | "": lambda c: c, 44 | ".xz": lambda c: lzma.decompress(c), 45 | ".gz": lambda c: gzip.decompress(c), 46 | ".bzip2": lambda c: bz2.decompress(c), 47 | } 48 | 49 | for suffix, method in decompress.items(): 50 | url = base_url + suffix 51 | 52 | try: 53 | req = requests.get(url) 54 | except requests.error.URLError: 55 | continue 56 | 57 | return method(req.content).decode("utf-8") 58 | 59 | 60 | def _get_value(content, key): 61 | """ 62 | Extracts a value from a Packages,Release file 63 | 64 | # Arguments 65 | content (str): the content of the Packages/Release file 66 | key (str): the key to return the value for 67 | """ 68 | pattern = key + ": (.*)\n" 69 | match = re.search(pattern, content) 70 | try: 71 | return match.group(1) 72 | except AttributeError: 73 | raise KeyError(content, key) 74 | 75 | 76 | class ReleaseFile: 77 | """ 78 | Class that represents a Release file 79 | 80 | # Arguments 81 | content (str): the content of the Release file 82 | """ 83 | 84 | def __init__(self, content): 85 | self.__content = content.strip() 86 | 87 | @property 88 | def origin(self): 89 | return _get_value(self.__content, "Origin") 90 | 91 | @property 92 | def label(self): 93 | return _get_value(self.__content, "Label") 94 | 95 | @property 96 | def suite(self): 97 | return _get_value(self.__content, "Suite") 98 | 99 | @property 100 | def version(self): 101 | return _get_value(self.__content, "Version") 102 | 103 | @property 104 | def codename(self): 105 | return _get_value(self.__content, "Codename") 106 | 107 | @property 108 | def date(self): 109 | return _get_value(self.__content, "Date") 110 | 111 | @property 112 | def architectures(self): 113 | return _get_value(self.__content, "Architectures").split() 114 | 115 | @property 116 | def components(self): 117 | return _get_value(self.__content, "Components").split() 118 | 119 | @property 120 | def description(self): 121 | return _get_value(self.__content, "Description") 122 | 123 | 124 | class PackagesFile: 125 | """ 126 | Class that represents a Packages file 127 | 128 | # Arguments 129 | content (str): the content of the Packages file 130 | """ 131 | 132 | def __init__(self, content): 133 | self.__content = content.strip() 134 | 135 | @property 136 | def packages(self): 137 | """Returns all binary packages in this Packages files""" 138 | packages = [] 139 | for package_content in self.__content.split("\n\n"): 140 | if not package_content: 141 | continue 142 | 143 | packages.append(BinaryPackage(package_content)) 144 | 145 | return packages 146 | 147 | 148 | class BinaryPackage: 149 | """ 150 | Class that represents a binary Debian package 151 | 152 | 153 | # Arguments 154 | content (str): the section of the Packages file for this specific package 155 | """ 156 | 157 | def __init__(self, content): 158 | self.__content = content.strip() 159 | 160 | @property 161 | def package(self): 162 | return _get_value(self.__content, "Package") 163 | 164 | @property 165 | def version(self): 166 | return _get_value(self.__content, "Version") 167 | 168 | @property 169 | def filename(self): 170 | return _get_value(self.__content, "Filename") 171 | 172 | 173 | class APTRepository: 174 | """ 175 | Class that represents a single APT repository 176 | 177 | # Arguments 178 | url (str): the base URL of the repository 179 | dist (str): the target distribution 180 | components (list): the target components 181 | 182 | # Examples 183 | ```python 184 | APTRepository('http://archive.ubuntu.com/ubuntu', 'bionic', 'main') 185 | APTRepository('https://pkg.jenkins.io/debian/', 'binary') 186 | ``` 187 | """ 188 | 189 | def __init__(self, url, dist, components=[]): 190 | self.url = url 191 | self.dist = dist 192 | self.components = components 193 | 194 | def __getitem__(self, item): 195 | return self.get_packages_by_name(item) 196 | 197 | @staticmethod 198 | def from_sources_list_entry(entry): 199 | """ 200 | Instantiates a new APTRepository object out of a sources.list file entry 201 | 202 | # Examples 203 | ```python 204 | APTRepository.from_sources_list_entry('deb http://archive.ubuntu.com/ubuntu bionic main') 205 | ``` 206 | """ 207 | split_entry = entry.split() 208 | 209 | url = split_entry[1] 210 | dist = split_entry[2] 211 | try: 212 | components = split_entry[3:] 213 | except IndexError: 214 | # we assume that it is a flat repo https://wiki.debian.org/DebianRepository/Format#Flat_Repository_Format 215 | components = [] 216 | 217 | return APTRepository(url, dist, components) 218 | 219 | @property 220 | def all_components(self): 221 | """Returns the all components of this repository""" 222 | return self.release_file.components 223 | 224 | @property 225 | def release_file(self): 226 | """Returns the Release file of this repository""" 227 | url = posixpath.join(self.url, "dists", self.dist, "Release") 228 | 229 | release_content = _download(url) 230 | 231 | return ReleaseFile(release_content) 232 | 233 | @property 234 | def packages(self, arch="amd64"): 235 | """ 236 | Returns all binary packages of this repository 237 | 238 | # Arguments 239 | arch (str): the architecture to return packages for, default: 'amd64' 240 | """ 241 | packages = [] 242 | if len(self.components) == 0: 243 | packages.extend(self.get_binary_packages_by_component(None, arch)) 244 | for component in self.components: 245 | packages.extend(self.get_binary_packages_by_component(component, arch)) 246 | 247 | return packages 248 | 249 | def get_binary_packages_by_component(self, component, arch="amd64"): 250 | """ 251 | Returns all binary packages of this repository for a given component 252 | 253 | # Arguments 254 | component (str): the component to return packages for 255 | arch (str): the architecture to return packages for, default: 'amd64' 256 | """ 257 | if component is None: 258 | url = posixpath.join(self.url, self.dist, "Packages") 259 | else: 260 | url = posixpath.join( 261 | self.url, "dists", self.dist, component, "binary-" + arch, "Packages" 262 | ) 263 | 264 | packages_file = _download_compressed(url) 265 | 266 | return PackagesFile(packages_file).packages 267 | 268 | def get_package(self, name, version): 269 | """ 270 | Returns a single binary package 271 | 272 | # Arguments 273 | name (str): name of the package 274 | version (str): version of the package 275 | """ 276 | for package in self.packages: 277 | if package.package == name and package.version == version: 278 | return package 279 | 280 | raise KeyError(name, version) 281 | 282 | def get_package_url(self, name, version): 283 | """ 284 | Returns the URL for a single binary package 285 | 286 | # Arguments 287 | name (str): name of the package 288 | version (str): version of the package 289 | """ 290 | package = self.get_package(name, version) 291 | 292 | return posixpath.join(self.url, package.filename) 293 | 294 | def get_packages_by_name(self, name): 295 | """ 296 | Returns the list of available packages (and it's available versions) for a specific package name 297 | 298 | # Arguments 299 | name (str): name of the package 300 | """ 301 | 302 | packages = [] 303 | 304 | for package in self.packages: 305 | if package.package == name: 306 | packages.append(package) 307 | 308 | return packages 309 | 310 | 311 | class APTSources: 312 | """ 313 | Class that represents a collection of APT repositories 314 | 315 | # Arguments 316 | repositories (list): list of APTRepository objects 317 | """ 318 | 319 | def __init__(self, repositories): 320 | self.__repositories = repositories 321 | 322 | def __getitem__(self, item): 323 | return self.get_packages_by_name(item) 324 | 325 | @property 326 | def packages(self): 327 | """Returns all binary packages of all APT repositories""" 328 | packages = [] 329 | 330 | for repo in self.__repositories: 331 | packages.extend(repo.packages) 332 | 333 | return packages 334 | 335 | def get_package(self, name, version): 336 | """ 337 | Returns a single binary package 338 | 339 | # Arguments 340 | name (str): the name of the package 341 | version (str): the version of the package 342 | """ 343 | for repo in self.__repositories: 344 | try: 345 | return repo.get_package(name, version) 346 | except KeyError: 347 | pass 348 | 349 | raise KeyError(name, version) 350 | 351 | def get_package_url(self, name, version): 352 | """ 353 | Returns the URL of a single binary package 354 | 355 | # Arguments 356 | name (str): the name of the package 357 | version (str): the version of the package 358 | """ 359 | for repo in self.__repositories: 360 | try: 361 | return repo.get_package_url(name, version) 362 | except KeyError: 363 | pass 364 | 365 | raise KeyError(name, version) 366 | 367 | def get_packages_by_name(self, name): 368 | """ 369 | Returns the list of available packages (and it's available versions) for a specific package name 370 | 371 | # Arguments 372 | name (str): name of the package 373 | """ 374 | 375 | packages = [] 376 | 377 | for repo in self.__repositories: 378 | packages.extend(repo.get_packages_by_name(name)) 379 | 380 | return packages 381 | -------------------------------------------------------------------------------- /scripts/build-push-docker.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | QGIS_TYPE=$1 6 | RELEASE_TYPE=$2 7 | QGIS_VERSION=$3 8 | OS=$4 9 | OS_RELEASE=$5 10 | DEFAULT_OS_RELEASE=$6 11 | 12 | MAJOR_QGIS_VERSION=$(echo "${QGIS_VERSION}" | cut -d. -f1,2) 13 | 14 | if [[ ${RELEASE_TYPE} =~ ^ltr$ ]]; then 15 | QGIS_PPA="${OS}-ltr" 16 | else 17 | QGIS_PPA="${OS}" 18 | fi 19 | 20 | if [[ ${QGIS_TYPE} =~ ^server$ ]]; then 21 | REPO='qgis-server' 22 | else 23 | REPO='qgis' 24 | fi 25 | 26 | 27 | echo "Building QGIS Server Docker image:" 28 | echo "QGIS_TYPE: ${QGIS_TYPE}" 29 | echo "RELEASE_TYPE: ${RELEASE_TYPE}" 30 | echo "QGIS_VERSION: ${QGIS_VERSION}" 31 | echo "MAJOR_QGIS_VERSION: ${MAJOR_QGIS_VERSION}" 32 | echo "OS: ${OS}" 33 | echo "OS_RELEASE: ${OS_RELEASE}" 34 | echo "DEFAULT_OS_RELEASE: ${DEFAULT_OS_RELEASE}" 35 | echo "QGIS_PPA: ${QGIS_PPA}" 36 | 37 | TAGS="${RELEASE_TYPE}-${OS_RELEASE} ${MAJOR_QGIS_VERSION}-${OS_RELEASE} ${QGIS_VERSION}-${OS_RELEASE}" 38 | if [[ ${OS_RELEASE} == ${DEFAULT_OS_RELEASE} ]]; then 39 | TAGS="${RELEASE_TYPE} ${MAJOR_QGIS_VERSION} ${QGIS_VERSION} ${TAGS}" 40 | fi 41 | echo "TAGS: ${TAGS}" 42 | 43 | ALL_TAGS="" 44 | for TAG in ${TAGS}; do 45 | ALL_TAGS="${ALL_TAGS} --tag qgis/${REPO}:${TAG}" 46 | done 47 | 48 | docker buildx build --push --platform linux/amd64 \ 49 | --build-arg os=${OS} \ 50 | --build-arg release=${OS_RELEASE} \ 51 | --build-arg repo=${QGIS_PPA} \ 52 | --build-arg qgis_version=${QGIS_VERSION} \ 53 | ${ALL_TAGS} -f ${QGIS_TYPE}/Dockerfile . 54 | -------------------------------------------------------------------------------- /scripts/check_version.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | VERSION_CHECK=$1 4 | VERSION_INSTALLED=$(apt list --installed qgis | grep installed | cut -d: -f2 | cut -d\+ -f1) 5 | 6 | if [[ ${VERSION_CHECK} == 'master' ]]; then 7 | echo "installed: ${VERSION_INSTALLED}" 8 | exit 0 9 | fi 10 | 11 | if [[ ${VERSION_INSTALLED} =~ ^${VERSION_CHECK}$ ]]; then 12 | echo "version check ok: ${VERSION_INSTALLED}" 13 | exit 0 14 | fi 15 | 16 | echo "version mismatch!" 17 | echo "installed: ${VERSION_INSTALLED}" 18 | echo "check: ${VERSION_CHECK}" 19 | exit 1 20 | -------------------------------------------------------------------------------- /scripts/get_docker_image_version.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # This scripts will output the QGIS versions on which LTR and stable rely on 4 | # Formatted as json: {"stable": "3.14.0", "ltr": "3.10.7"} 5 | 6 | import argparse 7 | import json 8 | import re 9 | 10 | import requests 11 | 12 | if __name__ == "__main__": 13 | parser = argparse.ArgumentParser() 14 | parser.add_argument("-q", "--qgis", help="desktop or server", choices=["desktop", "server"]) 15 | parser.add_argument("-u", "--dist", help="The Ubuntu/Debian distribution") 16 | # parser.add_argument('-d', '--default-dist', help='The default Ubuntu distribution, for which no suffix is in the tag') 17 | args = parser.parse_args() 18 | distro = args.dist 19 | # default_distro = args.default_dist 20 | 21 | stable = "" 22 | ltr = "" 23 | 24 | if args.qgis == "desktop": 25 | repo_name = "qgis" 26 | else: 27 | repo_name = "qgis-server" 28 | 29 | url = f"https://registry.hub.docker.com/v2/repositories/qgis/{repo_name}/tags?page_size=10000" 30 | r = requests.get(url) 31 | if r.status_code == 404: 32 | # the image does not exist yet 33 | pass 34 | else: 35 | data = r.content.decode("utf-8") 36 | tags = json.loads(data)["results"] 37 | 38 | stable_sha = None 39 | ltr_sha = None 40 | 41 | # get available tags 42 | availables_tags = dict() 43 | 44 | # get the full version 45 | match = rf"^\d\.\d+\.\d+-{distro}$" 46 | 47 | for tag in tags: 48 | if tag["name"] == f"stable-{distro}": 49 | stable_sha = tag["images"][0]["digest"] # sha 50 | elif tag["name"] == f"ltr-{distro}": 51 | ltr_sha = tag["images"][0]["digest"] # sha 52 | elif re.match(match, tag["name"]): 53 | availables_tags[tag["name"]] = tag["images"][0]["digest"] 54 | 55 | # determine what is ltr and stable 56 | for tag, sha in availables_tags.items(): 57 | if sha == stable_sha: 58 | stable = tag 59 | stable = stable.replace(f"-{distro}", "") 60 | elif sha == ltr_sha: 61 | ltr = tag 62 | ltr = ltr.replace(f"-{distro}", "") 63 | 64 | output = {"stable": stable, "ltr": ltr} 65 | print(json.dumps(output)) 66 | -------------------------------------------------------------------------------- /scripts/get_ubuntu_qgis_package_version.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # This scripts will output the last LTR and stable QGIS versions for Ubuntu 4 | # Formatted as json: {"stable": "3.14.0", "ltr": "3.10.7"} 5 | 6 | import argparse 7 | import json 8 | import re 9 | 10 | from apt_repo import APTRepository 11 | 12 | if __name__ == "__main__": 13 | parser = argparse.ArgumentParser() 14 | parser.add_argument("-q", "--qgis", help="desktop or server", choices=["desktop", "server"]) 15 | parser.add_argument( 16 | "-o", "--os", help="The operating system", choices=["ubuntu", "debian"], default="ubuntu" 17 | ) 18 | parser.add_argument("-d", "--dist", help="The Ubuntu/Debian distribution", default="focal") 19 | args = parser.parse_args() 20 | os = args.os 21 | dist = args.dist 22 | 23 | if args.qgis == "desktop": 24 | package_name = "qgis" 25 | else: 26 | package_name = "qgis-server" 27 | 28 | data = {} 29 | for ltr in (True, False): 30 | url = "https://qgis.org/{}{}".format(os, "-ltr" if ltr else "") 31 | components = ["main"] 32 | repo = APTRepository(url, dist, components) 33 | package = repo.get_packages_by_name(package_name)[0] 34 | assert package.package == package_name 35 | # https://regex101.com/r/lkuibv/2 36 | p = re.compile(rf"^1:(\d(?:\.\d+)+)(?:\+\d+{dist})(?:\-\d+)?$") 37 | m = p.match(package.version) 38 | data["ltr" if ltr else "stable"] = m.group(1) 39 | 40 | print(json.dumps(data)) 41 | -------------------------------------------------------------------------------- /server/Dockerfile: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # vim: tabstop=4 shiftwidth=4 softtabstop=4 3 | # vim: syntax=dockerfile 4 | # 5 | # oq-qgis-server 6 | # Copyright (C) 2018-2020 GEM Foundation 7 | # 8 | # oq-qgis-server is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU Affero General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # oq-qgis-server is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU Affero General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU Affero General Public License 19 | # along with this program. If not, see . 20 | 21 | ARG ubuntu_dist=noble 22 | 23 | FROM ubuntu:${ubuntu_dist} 24 | LABEL maintainer="OPENGIS.ch " 25 | 26 | ARG ubuntu_dist 27 | ARG repo=ubuntu 28 | ARG qgis_version 29 | 30 | RUN apt update && apt install -y gnupg wget software-properties-common && \ 31 | wget -qO - https://qgis.org/downloads/qgis-2022.gpg.key | gpg --no-default-keyring --keyring gnupg-ring:/etc/apt/trusted.gpg.d/qgis-archive.gpg --import && \ 32 | chmod a+r /etc/apt/trusted.gpg.d/qgis-archive.gpg && \ 33 | add-apt-repository "deb https://qgis.org/${repo} ${ubuntu_dist} main" && \ 34 | apt update && \ 35 | DEBIAN_FRONTEND=noninteractive apt install -y xvfb nginx-core spawn-fcgi qgis-server python3-qgis && \ 36 | apt clean all 37 | 38 | # This part is kept to allow the container to be used in 39 | # standalone mode, without composing it with 'nginx' 40 | ADD server/conf/qgis-server-nginx.conf /etc/nginx/nginx.conf 41 | ADD server/start-xvfb-nginx.sh /usr/local/bin/start-xvfb-nginx.sh 42 | 43 | ENV QGIS_PREFIX_PATH /usr 44 | ENV QGIS_PLUGINPATH /io/plugins 45 | ENV QGIS_SERVER_LOG_LEVEL 1 46 | ENV QGIS_SERVER_LOG_STDERR true 47 | ENV QGIS_SERVER_PARALLEL_RENDERING true 48 | ENV QGIS_SERVER_MAX_THREADS 2 49 | ENV QGIS_AUTH_DB_DIR_PATH /tmp/ 50 | 51 | ENV QT_GRAPHICSSYSTEM raster 52 | ENV DISPLAY :99 53 | ENV HOME /var/lib/qgis 54 | 55 | RUN mkdir $HOME && \ 56 | chmod 1777 $HOME 57 | WORKDIR $HOME 58 | 59 | EXPOSE 80/tcp 9993/tcp 60 | CMD /usr/local/bin/start-xvfb-nginx.sh 61 | -------------------------------------------------------------------------------- /server/README.md: -------------------------------------------------------------------------------- 1 | # QGIS Server 2 | 3 | [![Docker Hub](https://img.shields.io/docker/pulls/qgis/qgis-server)](https://hub.docker.com/r/qgis/qgis-server) 4 | 5 | ## General information 6 | 7 | The Docker image is built using *Ubuntu 20.04 (focal), 22.04 (jammy) and 24.04 (noble)* and official QGIS DEBs from . 8 | It includes *Nginx* and *Xvfb* and can be used as a standalone service (via HTTP TCP port 80) or as *FCGI* backend (via TCP port 9993). 9 | 10 | ## Requisites 11 | 12 | You need **Docker >= 18.04** with `seccomp`. Support for the `statx` syscall, required by Qt 5.10+, may be made necessary in the future. This is generally included in **libseccomp >= 2.3.3**; 13 | a kernel with `statx` support is also required; any kernel newer than 4.11 should be ok. Please check with your vendor. 14 | 15 | Known good configurations are: 16 | - Ubuntu 18.04.2+ 17 | - CentOS 8 18 | - Fedora 29+ 19 | 20 | See for further details. 21 | 22 | Containers are not tested on hosts running OSes other than Linux. 23 | 24 | ## Services provided 25 | 26 | This Docker container exposes HTTP on port `80` via Nginx and a direct FastCGI on port `9993` that can be used directly by an external HTTP proxy (like the provided `docker-compose.yml` does). 27 | A sample Nginx configuration for using it as a *FastCGI* backend is also [provided](conf/nginx-fcgi-sample.conf). 28 | 29 | ## Available tags 30 | 31 | Image name: `qgis/qgis-server` 32 | 33 | - **QGIS stable**: `stable` | `stable-ubuntu` 34 | - **QGIS LTR**: `ltr` | `ltr-ubuntu` 35 | 36 | Example: 37 | 38 | ```sh 39 | docker pull qgis/qgis-server:ltr 40 | -------------------------------------------------------------------------------- /server/conf/nginx-fcgi-sample.conf: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # vim: tabstop=4 shiftwidth=4 softtabstop=4 3 | # 4 | # oq-qgis-server 5 | # Copyright (C) 2018-2019 GEM Foundation 6 | # 7 | # oq-qgis-server is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU Affero General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # oq-qgis-server is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU Affero General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU Affero General Public License 18 | # along with this program. If not, see . 19 | 20 | user nginx; 21 | worker_processes auto; 22 | 23 | error_log /var/log/nginx/error.log warn; 24 | pid /var/run/nginx.pid; 25 | 26 | 27 | events { 28 | worker_connections 1024; 29 | } 30 | 31 | 32 | http { 33 | sendfile on; 34 | tcp_nopush on; 35 | tcp_nodelay on; 36 | keepalive_timeout 65; 37 | types_hash_max_size 2048; 38 | types_hash_bucket_size 64; 39 | 40 | include /etc/nginx/mime.types; 41 | default_type application/octet-stream; 42 | 43 | upstream qgis-fcgi { 44 | # When not using 'host' network these must reflect the number 45 | # of containers spawned by docker-compose and must also have 46 | # names generated by it (including the name of the stack) 47 | server oq-qgis-server_qgis-server_1:9993; 48 | #server oq-qgis-server_qgis-server_2:9993; 49 | #server oq-qgis-server_qgis-server_3:9993; 50 | } 51 | 52 | # Get 'host' from `$host` unless 'X-Forwarded-Host' 53 | # is set by the reverse proxy. 54 | # 'X-Forwarded-Host' may contain also the port, 55 | # so it is removed from the variable 56 | map $http_x_forwarded_host $qgis_host { 57 | "~(?[^:]+)" $h; 58 | default $host; 59 | } 60 | # Get 'PORT' from `$http_host` 61 | map $http_host $port { 62 | "~*.*:(?

.*)" $p; 63 | default $server_port; 64 | } 65 | # Get 'HTTPS' status from `$https` unless 'X-Forwarded-Proto' 66 | # is set by the reverse proxy and contains 'https' scheme 67 | map $http_x_forwarded_proto $qgis_ssl { 68 | "https" "on"; 69 | default $https; 70 | } 71 | # Get 'PORT' from `$port` unless 'X-Forwarded-Port' 72 | # is set by the reverse proxy 73 | map $http_x_forwarded_port $qgis_port { 74 | "" $port; 75 | default $http_x_forwarded_port; 76 | } 77 | 78 | server { 79 | listen 80 default_server; 80 | listen [::]:80 default_server; 81 | server_name _; 82 | root /usr/share/nginx/html; 83 | 84 | location / { 85 | root /var/www/html; 86 | } 87 | location /ogc/ { 88 | rewrite ^/ogc/(.*)$ /qgis/qgis_mapserv.fcgi?map=/io/data/$1/$1.qgs; 89 | } 90 | location /qgis/ { 91 | internal; # Used only by the OGC rewrite 92 | root /var/www/data; 93 | fastcgi_pass qgis-fcgi; 94 | 95 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 96 | fastcgi_param QUERY_STRING $query_string; 97 | # build links in GetCapabilities based on 98 | # the hostname exposed by the reverse proxy 99 | fastcgi_param HTTPS $qgis_ssl; 100 | fastcgi_param SERVER_NAME $qgis_host; 101 | fastcgi_param SERVER_PORT $qgis_port; 102 | include fastcgi_params; 103 | } 104 | error_page 404 /404.html; 105 | location = /40x.html { 106 | } 107 | error_page 500 502 503 504 /50x.html; 108 | location = /50x.html { 109 | } 110 | } 111 | 112 | } 113 | -------------------------------------------------------------------------------- /server/conf/pg_service.conf: -------------------------------------------------------------------------------- 1 | [demoservice] 2 | host=localhost 3 | dbname=gis 4 | port=5432 5 | user=docker 6 | password=docker 7 | -------------------------------------------------------------------------------- /server/conf/qgis-server-nginx.conf: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # vim: tabstop=4 shiftwidth=4 softtabstop=4 3 | # 4 | # oq-qgis-server 5 | # Copyright (C) 2018-2019 GEM Foundation 6 | # 7 | # oq-qgis-server is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU Affero General Public License as published by 9 | # the Free Software Foundation, either version 3 of the License, or 10 | # (at your option) any later version. 11 | # 12 | # oq-qgis-server is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # GNU Affero General Public License for more details. 16 | # 17 | # You should have received a copy of the GNU Affero General Public License 18 | # along with this program. If not, see . 19 | 20 | user www-data; 21 | worker_processes auto; 22 | error_log stderr; 23 | pid /run/nginx.pid; 24 | 25 | # Load dynamic modules. See /usr/share/doc/nginx/README.dynamic. 26 | include /usr/share/nginx/modules/*.conf; 27 | 28 | events { 29 | worker_connections 1024; 30 | } 31 | 32 | http { 33 | access_log /dev/stdout; 34 | 35 | sendfile on; 36 | tcp_nopush on; 37 | tcp_nodelay on; 38 | keepalive_timeout 65; 39 | types_hash_max_size 2048; 40 | 41 | include /etc/nginx/mime.types; 42 | default_type application/octet-stream; 43 | 44 | # Get 'host' from `$host` unless 'X-Forwarded-Host' 45 | # is set by the reverse proxy. 46 | # 'X-Forwarded-Host' may contain also the port, 47 | # so it is removed from the variable 48 | map $http_x_forwarded_host $qgis_host { 49 | "~(?[^:]+)" $h; 50 | default $host; 51 | } 52 | # Get 'PORT' from `$http_host` 53 | map $http_host $port { 54 | "~*.*:(?

.*)" $p; 55 | default $server_port; 56 | } 57 | # Get 'HTTPS' status from `$https` unless 'X-Forwarded-Proto' 58 | # is set by the reverse proxy and contains 'https' scheme 59 | map $http_x_forwarded_proto $qgis_ssl { 60 | "https" "on"; 61 | default $https; 62 | } 63 | # Get 'PORT' from `$port` unless 'X-Forwarded-Port' 64 | # is set by the reverse proxy 65 | map $http_x_forwarded_port $qgis_port { 66 | "" $port; 67 | default $http_x_forwarded_port; 68 | } 69 | 70 | server { 71 | listen 80 default_server; 72 | listen [::]:80 default_server; 73 | server_name _; 74 | root /usr/share/nginx/html; 75 | 76 | location /ogc/ { 77 | rewrite ^/ogc/(.*)$ /qgis/qgis_mapserv.fcgi?map=/io/data/$1/$1.qgs; 78 | } 79 | # Direct access without map rewrite 80 | location /ows/ { 81 | rewrite ^/ows/$ /qgis/qgis_mapserv.fcgi; 82 | } 83 | location /wfs3/ { 84 | rewrite ^/wfs3/(.*)$ /qgis/qgis_mapserv.fcgi; 85 | } 86 | location /qgis/ { 87 | internal; # Used only by the OGC rewrite 88 | root /var/www/data; 89 | fastcgi_pass localhost:9993; 90 | 91 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 92 | fastcgi_param QUERY_STRING $query_string; 93 | # build links in GetCapabilities based on 94 | # the hostname exposed by the reverse proxy 95 | fastcgi_param HTTPS $qgis_ssl; 96 | fastcgi_param SERVER_NAME $qgis_host; 97 | fastcgi_param SERVER_PORT $qgis_port; 98 | include fastcgi_params; 99 | } 100 | error_page 404 /404.html; 101 | location = /40x.html { 102 | } 103 | error_page 500 502 503 504 /50x.html; 104 | location = /50x.html { 105 | } 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /server/start-xvfb-nginx.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # -*- coding: utf-8 -*- 3 | # vim: tabstop=4 shiftwidth=4 softtabstop=4 4 | # 5 | # oq-qgis-server 6 | # Copyright (C) 2018-2019 GEM Foundation 7 | # 8 | # oq-qgis-server is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU Affero General Public License as published by 10 | # the Free Software Foundation, either version 3 of the License, or 11 | # (at your option) any later version. 12 | # 13 | # oq-qgis-server is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # GNU Affero General Public License for more details. 17 | # 18 | # You should have received a copy of the GNU Affero General Public License 19 | # along with this program. If not, see . 20 | 21 | 22 | cleanup() { 23 | # SIGTERM is propagated to children. 24 | # Timeout is managed directly by Docker, via it's '-t' flag: 25 | # if SIGTERM does not teminate the entrypoint, after the time 26 | # defined by '-t' (default 10 secs) the container is killed 27 | kill $XVFB_PID $QGIS_PID $NGINX_PID 28 | } 29 | 30 | waitfor() { 31 | # Make startup syncronous 32 | while ! pidof $1 >/dev/null; do 33 | sleep 1 34 | done 35 | pidof $1 36 | } 37 | 38 | trap cleanup SIGINT SIGTERM 39 | 40 | # always convert $SKIP_NGINX to lowercase 41 | typeset -l SKIP_NGINX 42 | 43 | rm -f /tmp/.X99-lock 44 | # Update font cache 45 | fc-cache 46 | /usr/bin/Xvfb :99 -ac -screen 0 1280x1024x16 +extension GLX +render -noreset >/dev/null & 47 | XVFB_PID=$(waitfor /usr/bin/Xvfb) 48 | # Do not start NGINX if environment variable '$SKIP_NGINX' is set but not '0' or 'false' 49 | # this may be useful in production where an external reverse proxy is used 50 | if [ -z "$SKIP_NGINX" ] || [ "$SKIP_NGINX" == "false" ] || [ "$SKIP_NGINX" == "0" ]; then 51 | nginx 52 | NGINX_PID=$(waitfor /usr/sbin/nginx) 53 | fi 54 | # To avoid issues with GeoPackages when scaling out QGIS should not run as root 55 | spawn-fcgi -n -u ${QGIS_USER:-www-data} -g ${QGIS_USER:-www-data} -d ${HOME:-/var/lib/qgis} -P /run/qgis.pid -p 9993 -- /usr/lib/cgi-bin/qgis_mapserv.fcgi & 56 | QGIS_PID=$(waitfor qgis_mapserv.fcgi) 57 | wait $QGIS_PID 58 | -------------------------------------------------------------------------------- /server/test/data/test_project/layers/layer.gpkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qgis/qgis-docker/c884e4d1f1e8f00f28d0b8921780da41d8861265/server/test/data/test_project/layers/layer.gpkg -------------------------------------------------------------------------------- /server/test/data/test_project/test_project.qgd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qgis/qgis-docker/c884e4d1f1e8f00f28d0b8921780da41d8861265/server/test/data/test_project/test_project.qgd -------------------------------------------------------------------------------- /server/test/data/test_project/test_project.qgs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <autotransaction active="0"/> 5 | <evaluateDefaultValues active="0"/> 6 | <trust active="0"/> 7 | <projectCrs> 8 | <spatialrefsys> 9 | <proj4>+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs</proj4> 10 | <srsid>3857</srsid> 11 | <srid>3857</srid> 12 | <authid>EPSG:3857</authid> 13 | <description>WGS 84 / Pseudo Mercator</description> 14 | <projectionacronym>merc</projectionacronym> 15 | <ellipsoidacronym>WGS84</ellipsoidacronym> 16 | <geographicflag>false</geographicflag> 17 | </spatialrefsys> 18 | </projectCrs> 19 | <layer-tree-group> 20 | <customproperties/> 21 | <layer-tree-layer id="layer_82cc2330_f8d3_4fc4_bdf8_a357ce5902b5" providerKey="memory" expanded="1" source="Polygon?crs=EPSG:4326&uid={90d37f67-0739-4594-9ca6-f087d7f1152d}" name="layer" checked="Qt::Checked"> 22 | <customproperties/> 23 | </layer-tree-layer> 24 | <custom-order enabled="0"> 25 | <item>layer_82cc2330_f8d3_4fc4_bdf8_a357ce5902b5</item> 26 | </custom-order> 27 | </layer-tree-group> 28 | <snapping-settings enabled="0" intersection-snapping="0" type="1" mode="2" tolerance="12" unit="1"> 29 | <individual-layer-settings> 30 | <layer-setting id="layer_82cc2330_f8d3_4fc4_bdf8_a357ce5902b5" enabled="0" type="1" tolerance="12" units="1"/> 31 | </individual-layer-settings> 32 | </snapping-settings> 33 | <relations/> 34 | <mapcanvas annotationsVisible="1" name="theMapCanvas"> 35 | <units>meters</units> 36 | <extent> 37 | <xmin>8445306.51171122118830681</xmin> 38 | <ymin>2655924.08398920437321067</ymin> 39 | <xmax>10321318.58701100945472717</xmax> 40 | <ymax>3789288.46987967612221837</ymax> 41 | </extent> 42 | <rotation>0</rotation> 43 | <destinationsrs> 44 | <spatialrefsys> 45 | <proj4>+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs</proj4> 46 | <srsid>3857</srsid> 47 | <srid>3857</srid> 48 | <authid>EPSG:3857</authid> 49 | <description>WGS 84 / Pseudo Mercator</description> 50 | <projectionacronym>merc</projectionacronym> 51 | <ellipsoidacronym>WGS84</ellipsoidacronym> 52 | <geographicflag>false</geographicflag> 53 | </spatialrefsys> 54 | </destinationsrs> 55 | <rendermaptile>0</rendermaptile> 56 | </mapcanvas> 57 | <legend updateDrawingOrder="true"> 58 | <legendlayer showFeatureCount="0" open="true" name="layer" checked="Qt::Checked" drawingOrder="-1"> 59 | <filegroup open="true" hidden="false"> 60 | <legendlayerfile visible="1" isInOverview="0" layerid="layer_82cc2330_f8d3_4fc4_bdf8_a357ce5902b5"/> 61 | </filegroup> 62 | </legendlayer> 63 | </legend> 64 | <mapViewDocks/> 65 | <mapViewDocks3D/> 66 | <projectlayers> 67 | <maplayer simplifyDrawingHints="1" hasScaleBasedVisibilityFlag="0" refreshOnNotifyEnabled="0" simplifyAlgorithm="0" labelsEnabled="0" geometry="Polygon" autoRefreshEnabled="0" simplifyLocal="1" maxScale="0" type="vector" simplifyDrawingTol="1" refreshOnNotifyMessage="" minScale="1e+8" simplifyMaxScale="1" readOnly="0" autoRefreshTime="0"> 68 | <extent> 69 | <xmin>79.63074247298308705</xmin> 70 | <ymin>24.31316349301498292</ymin> 71 | <xmax>87.51526816056684765</xmax> 72 | <ymax>29.8162406055734408</ymax> 73 | </extent> 74 | <id>layer_82cc2330_f8d3_4fc4_bdf8_a357ce5902b5</id> 75 | <datasource>./layers/layer.gpkg</datasource> 76 | <keywordList> 77 | <value/> 78 | </keywordList> 79 | <layername>layer</layername> 80 | <srs> 81 | <spatialrefsys> 82 | <proj4>+proj=longlat +datum=WGS84 +no_defs</proj4> 83 | <srsid>3452</srsid> 84 | <srid>4326</srid> 85 | <authid>EPSG:4326</authid> 86 | <description>WGS 84</description> 87 | <projectionacronym>longlat</projectionacronym> 88 | <ellipsoidacronym>WGS84</ellipsoidacronym> 89 | <geographicflag>true</geographicflag> 90 | </spatialrefsys> 91 | </srs> 92 | <resourceMetadata> 93 | <identifier/> 94 | <parentidentifier/> 95 | <language/> 96 | <type/> 97 | <title/> 98 | <abstract/> 99 | <fees/> 100 | <encoding/> 101 | <crs> 102 | <spatialrefsys> 103 | <proj4/> 104 | <srsid>0</srsid> 105 | <srid>0</srid> 106 | <authid/> 107 | <description/> 108 | <projectionacronym/> 109 | <ellipsoidacronym/> 110 | <geographicflag>false</geographicflag> 111 | </spatialrefsys> 112 | </crs> 113 | <extent> 114 | <spatial maxz="0" miny="0" maxy="0" minx="0" minz="0" maxx="0" crs="" dimensions="2"/> 115 | <temporal> 116 | <period> 117 | <start/> 118 | <end/> 119 | </period> 120 | </temporal> 121 | </extent> 122 | <contact> 123 | <name/> 124 | <organization/> 125 | <position/> 126 | <voice/> 127 | <fax/> 128 | <email/> 129 | <role/> 130 | </contact> 131 | <links/> 132 | </resourceMetadata> 133 | <provider encoding="UTF-8">ogr</provider> 134 | <vectorjoins/> 135 | <layerDependencies/> 136 | <dataDependencies/> 137 | <expressionfields/> 138 | <map-layer-style-manager current="default"> 139 | <map-layer-style name="default"/> 140 | </map-layer-style-manager> 141 | <auxiliaryLayer/> 142 | <renderer-v2 symbollevels="0" forceraster="0" type="singleSymbol" enableorderby="0"> 143 | <symbols> 144 | <symbol clip_to_extent="1" alpha="1" type="fill" name="0"> 145 | <layer enabled="1" locked="0" pass="0" class="SimpleFill"> 146 | <prop v="3x:0,0,0,0,0,0" k="border_width_map_unit_scale"/> 147 | <prop v="65,124,183,255" k="color"/> 148 | <prop v="bevel" k="joinstyle"/> 149 | <prop v="0,0" k="offset"/> 150 | <prop v="3x:0,0,0,0,0,0" k="offset_map_unit_scale"/> 151 | <prop v="MM" k="offset_unit"/> 152 | <prop v="35,35,35,255" k="outline_color"/> 153 | <prop v="solid" k="outline_style"/> 154 | <prop v="0.26" k="outline_width"/> 155 | <prop v="MM" k="outline_width_unit"/> 156 | <prop v="solid" k="style"/> 157 | <data_defined_properties> 158 | <Option type="Map"> 159 | <Option value="" type="QString" name="name"/> 160 | <Option name="properties"/> 161 | <Option value="collection" type="QString" name="type"/> 162 | </Option> 163 | </data_defined_properties> 164 | </layer> 165 | </symbol> 166 | </symbols> 167 | <rotation/> 168 | <sizescale/> 169 | </renderer-v2> 170 | <customproperties> 171 | <property key="dualview/previewExpressions"> 172 | <value>num</value> 173 | </property> 174 | <property value="0" key="embeddedWidgets/count"/> 175 | <property key="variableNames"/> 176 | <property key="variableValues"/> 177 | </customproperties> 178 | <blendMode>0</blendMode> 179 | <featureBlendMode>0</featureBlendMode> 180 | <layerOpacity>1</layerOpacity> 181 | <SingleCategoryDiagramRenderer attributeLegend="1" diagramType="Histogram"> 182 | <DiagramCategory sizeType="MM" scaleBasedVisibility="0" diagramOrientation="Up" width="15" lineSizeScale="3x:0,0,0,0,0,0" height="15" sizeScale="3x:0,0,0,0,0,0" enabled="0" scaleDependency="Area" penAlpha="255" opacity="1" labelPlacementMethod="XHeight" minimumSize="0" minScaleDenominator="0" rotationOffset="270" barWidth="5" maxScaleDenominator="1e+8" penColor="#000000" penWidth="0" backgroundColor="#ffffff" backgroundAlpha="255" lineSizeType="MM"> 183 | <fontProperties style="" description="Cantarell,11,-1,5,50,0,0,0,0,0"/> 184 | </DiagramCategory> 185 | </SingleCategoryDiagramRenderer> 186 | <DiagramLayerSettings showAll="1" linePlacementFlags="18" zIndex="0" dist="0" placement="0" priority="0" obstacle="0"> 187 | <properties> 188 | <Option type="Map"> 189 | <Option value="" type="QString" name="name"/> 190 | <Option name="properties"/> 191 | <Option value="collection" type="QString" name="type"/> 192 | </Option> 193 | </properties> 194 | </DiagramLayerSettings> 195 | <fieldConfiguration> 196 | <field name="num"> 197 | <editWidget type=""> 198 | <config> 199 | <Option/> 200 | </config> 201 | </editWidget> 202 | </field> 203 | </fieldConfiguration> 204 | <aliases> 205 | <alias index="0" name="" field="num"/> 206 | </aliases> 207 | <excludeAttributesWMS/> 208 | <excludeAttributesWFS/> 209 | <defaults> 210 | <default applyOnUpdate="0" expression="" field="num"/> 211 | </defaults> 212 | <constraints> 213 | <constraint constraints="0" notnull_strength="0" unique_strength="0" field="num" exp_strength="0"/> 214 | </constraints> 215 | <constraintExpressions> 216 | <constraint desc="" exp="" field="num"/> 217 | </constraintExpressions> 218 | <attributeactions> 219 | <defaultAction value="{00000000-0000-0000-0000-000000000000}" key="Canvas"/> 220 | </attributeactions> 221 | <attributetableconfig sortExpression="" actionWidgetStyle="dropDown" sortOrder="0"> 222 | <columns> 223 | <column type="field" width="-1" name="num" hidden="0"/> 224 | <column type="actions" width="-1" hidden="1"/> 225 | </columns> 226 | </attributetableconfig> 227 | <editform/> 228 | <editforminit/> 229 | <editforminitcodesource>0</editforminitcodesource> 230 | <editforminitfilepath/> 231 | <editforminitcode><![CDATA[# -*- coding: utf-8 -*- 232 | """ 233 | QGIS forms can have a Python function that is called when the form is 234 | opened. 235 | 236 | Use this function to add extra logic to your forms. 237 | 238 | Enter the name of the function in the "Python Init function" 239 | field. 240 | An example follows: 241 | """ 242 | from qgis.PyQt.QtWidgets import QWidget 243 | 244 | def my_form_open(dialog, layer, feature): 245 | geom = feature.geometry() 246 | control = dialog.findChild(QWidget, "MyLineEdit") 247 | ]]></editforminitcode> 248 | <featformsuppress>0</featformsuppress> 249 | <editorlayout>generatedlayout</editorlayout> 250 | <editable/> 251 | <labelOnTop/> 252 | <widgets/> 253 | <conditionalstyles> 254 | <rowstyles/> 255 | <fieldstyles/> 256 | </conditionalstyles> 257 | <expressionfields/> 258 | <previewExpression>num</previewExpression> 259 | <mapTip/> 260 | </maplayer> 261 | </projectlayers> 262 | <layerorder> 263 | <layer id="layer_82cc2330_f8d3_4fc4_bdf8_a357ce5902b5"/> 264 | </layerorder> 265 | <properties> 266 | <WMSOnlineResource type="QString"/> 267 | <Identify> 268 | <disabledLayers type="QStringList"/> 269 | </Identify> 270 | <Gui> 271 | <CanvasColorBluePart type="int">255</CanvasColorBluePart> 272 | <SelectionColorBluePart type="int">0</SelectionColorBluePart> 273 | <SelectionColorRedPart type="int">255</SelectionColorRedPart> 274 | <CanvasColorGreenPart type="int">255</CanvasColorGreenPart> 275 | <CanvasColorRedPart type="int">255</CanvasColorRedPart> 276 | <SelectionColorAlphaPart type="int">255</SelectionColorAlphaPart> 277 | <SelectionColorGreenPart type="int">255</SelectionColorGreenPart> 278 | </Gui> 279 | <WMSAccessConstraints type="QString">None</WMSAccessConstraints> 280 | <WMSAddWktGeometry type="bool">false</WMSAddWktGeometry> 281 | <Legend> 282 | <filterByMap type="bool">false</filterByMap> 283 | </Legend> 284 | <WMSContactPosition type="QString"/> 285 | <WMSUseLayerIDs type="bool">false</WMSUseLayerIDs> 286 | <WMSSegmentizeFeatureInfoGeometry type="bool">false</WMSSegmentizeFeatureInfoGeometry> 287 | <WCSUrl type="QString"/> 288 | <WMSContactPerson type="QString"/> 289 | <WMSPrecision type="QString">8</WMSPrecision> 290 | <WMSFees type="QString">conditions unknown</WMSFees> 291 | <WMSContactPhone type="QString"/> 292 | <WMSServiceAbstract type="QString"/> 293 | <WMSServiceTitle type="QString"/> 294 | <Macros> 295 | <pythonCode type="QString"/> 296 | </Macros> 297 | <WCSLayers type="QStringList"/> 298 | <WMSUrl type="QString"/> 299 | <WMSServiceCapabilities type="bool">false</WMSServiceCapabilities> 300 | <WFSUrl type="QString"/> 301 | <DefaultStyles> 302 | <ColorRamp type="QString"/> 303 | <RandomColors type="bool">true</RandomColors> 304 | <Fill type="QString"/> 305 | <Line type="QString"/> 306 | <Opacity type="double">1</Opacity> 307 | <Marker type="QString"/> 308 | </DefaultStyles> 309 | <Measurement> 310 | <AreaUnits type="QString">m2</AreaUnits> 311 | <DistanceUnits type="QString">meters</DistanceUnits> 312 | </Measurement> 313 | <Measure> 314 | <Ellipsoid type="QString">WGS84</Ellipsoid> 315 | </Measure> 316 | <PositionPrecision> 317 | <DecimalPlaces type="int">2</DecimalPlaces> 318 | <DegreeFormat type="QString">MU</DegreeFormat> 319 | <Automatic type="bool">true</Automatic> 320 | </PositionPrecision> 321 | <WMSImageQuality type="int">90</WMSImageQuality> 322 | <WMSContactMail type="QString"/> 323 | <WMSKeywordList type="QStringList"> 324 | <value/> 325 | </WMSKeywordList> 326 | <WMSRequestDefinedDataSources type="bool">false</WMSRequestDefinedDataSources> 327 | <WFSTLayers> 328 | <Update type="QStringList"/> 329 | <Delete type="QStringList"/> 330 | <Insert type="QStringList"/> 331 | </WFSTLayers> 332 | <WMSContactOrganization type="QString"/> 333 | <WFSLayers type="QStringList"/> 334 | <SpatialRefSys> 335 | <ProjectionsEnabled type="int">1</ProjectionsEnabled> 336 | </SpatialRefSys> 337 | <Paths> 338 | <Absolute type="bool">false</Absolute> 339 | </Paths> 340 | </properties> 341 | <visibility-presets/> 342 | <transformContext/> 343 | <Annotations/> 344 | <Layouts/> 345 | </qgis> 346 | --------------------------------------------------------------------------------