5 # Copyright:: 2015, OpenStreetMap Foundation
7 # Licensed under the Apache License, Version 2.0 (the "License");
8 # you may not use this file except in compliance with the License.
9 # You may obtain a copy of the License at
11 # https://www.apache.org/licenses/LICENSE-2.0
13 # Unless required by applicable law or agreed to in writing, software
14 # distributed under the License is distributed on an "AS IS" BASIS,
15 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 # See the License for the specific language governing permissions and
17 # limitations under the License.
20 include_recipe "accounts"
21 include_recipe "prometheus"
23 if platform?("debian")
24 include_recipe "postgresql"
25 include_recipe "python"
26 include_recipe "nginx"
28 include_recipe "fail2ban"
30 basedir = data_bag_item("accounts", "nominatim")["home"]
31 project_directory = "#{basedir}/planet-project"
32 bin_directory = "#{basedir}/bin"
33 cfg_directory = "#{basedir}/etc"
34 ui_directory = "#{basedir}/ui"
35 qa_bin_directory = "#{basedir}/Nominatim-Data-Analyser"
36 qa_data_directory = "#{basedir}/qa-data"
45 [basedir, bin_directory, cfg_directory, project_directory, ui_directory].each do |path|
53 if node[:nominatim][:flatnode_file]
54 directory File.dirname(node[:nominatim][:flatnode_file]) do
59 directory "#{bin_directory}/maintenance" do
65 ## Log directory setup
67 directory node[:nominatim][:logdir] do
74 file "#{node[:nominatim][:logdir]}/query.log" do
75 action :create_if_missing
83 postgresql_version = node[:nominatim][:dbcluster].split("/").first
84 postgis_version = node[:nominatim][:postgis]
86 package "postgresql-#{postgresql_version}-postgis-#{postgis_version}"
88 node[:nominatim][:dbadmins].each do |user|
89 postgresql_user user do
90 cluster node[:nominatim][:dbcluster]
95 postgresql_user "nominatim" do
96 cluster node[:nominatim][:dbcluster]
100 postgresql_user "www-data" do
101 cluster node[:nominatim][:dbcluster]
106 python_directory = "#{basedir}/venv"
120 python_virtualenv python_directory do
121 interpreter "/usr/bin/python3"
124 # These are updated during the database update.
125 python_package "nominatim-db" do
126 python_virtualenv python_directory
127 extra_index_url node[:nominatim][:pip_index]
130 python_package "nominatim-api" do
131 python_virtualenv python_directory
132 extra_index_url node[:nominatim][:pip_index]
135 remote_directory "#{project_directory}/static-website" do
140 files_owner "nominatim"
141 files_group "nominatim"
146 template "#{project_directory}/.env" do
147 source "nominatim.env.erb"
151 variables :base_url => "nominatim.openstreetmap.org",
152 :dbname => node[:nominatim][:dbname],
153 :flatnode_file => node[:nominatim][:flatnode_file],
154 :log_file => "#{node[:nominatim][:logdir]}/query.log",
155 :pool_size => node[:nominatim][:api_pool_size],
156 :query_timeout => node[:nominatim][:api_query_timeout],
157 :request_timeout => node[:nominatim][:api_request_timeout]
160 remote_file "#{project_directory}/secondary_importance.sql.gz" do
161 action :create_if_missing
162 source "https://nominatim.org/data/wikimedia-secondary-importance.sql.gz"
168 remote_file "#{project_directory}/wikimedia-importance.csv.gz" do
169 action :create_if_missing
170 source "https://nominatim.org/data/wikimedia-importance.csv.gz"
176 %w[gb_postcodes.csv.gz us_postcodes.csv.gz].each do |fname|
177 remote_file "#{project_directory}/#{fname}" do
179 source "https://nominatim.org/data/#{fname}"
186 # Webserver + frontend
188 %w[user_agent referrer email generic].each do |name|
189 file "#{cfg_directory}/nginx_blocked_#{name}.conf" do
190 action :create_if_missing
197 systemd_service "nominatim" do
198 description "Nominatim running as a gunicorn application"
201 working_directory project_directory
202 standard_output "append:#{node[:nominatim][:logdir]}/gunicorn.log"
203 standard_error "inherit"
204 exec_start "#{python_directory}/bin/gunicorn --max-requests 200000 -b unix:/run/gunicorn-nominatim.openstreetmap.org.sock -w #{node[:nominatim][:api_workers]} -k uvicorn.workers.UvicornWorker 'nominatim_api.server.falcon.server:run_wsgi()'"
205 exec_reload "/bin/kill -s HUP $MAINPID"
209 requires "nominatim.socket"
210 after "network.target"
213 systemd_socket "nominatim" do
214 description "Gunicorn socket for Nominatim"
215 listen_stream "/run/gunicorn-nominatim.openstreetmap.org.sock"
216 socket_user "www-data"
219 ssl_certificate node[:fqdn] do
220 domains [node[:fqdn],
221 "nominatim.openstreetmap.org",
223 "nominatim.openstreetmap.com",
224 "nominatim.openstreetmap.net",
225 "nominatim.openstreetmaps.org",
226 "nominatim.openmaps.org",
227 "nominatim.qgis.org"]
228 notifies :reload, "service[nginx]"
231 nginx_site "default" do
235 frontends = search(:node, "recipes:web\\:\\:frontend").sort_by(&:name)
237 nginx_site "nominatim" do
239 directory project_directory
240 variables :pools => node[:nominatim][:fpm_pools],
241 :frontends => frontends,
242 :confdir => "#{basedir}/etc",
243 :ui_directory => ui_directory
246 template "/etc/logrotate.d/nginx" do
247 source "logrotate.nginx.erb"
253 ### Import, update and maintenance scripts
256 nominatim-update-data
257 nominatim-update-refresh-db
258 nominatim-daily-maintenance].each do |fname|
259 template "#{bin_directory}/#{fname}" do
260 source "#{fname}.erb"
264 variables :bindir => bin_directory,
265 :projectdir => project_directory,
266 :venvprefix => "#{python_directory}/bin/",
267 :qadatadir => qa_data_directory
271 systemd_service "nominatim-update" do
272 description "Update the Nominatim database"
273 exec_start "#{bin_directory}/nominatim-update"
275 standard_output "journal"
276 standard_error "inherit"
277 working_directory project_directory
280 systemd_service "nominatim-update-maintenance-trigger" do
281 description "Trigger daily maintenance tasks for Nominatim DB"
282 exec_start "ln -sf #{bin_directory}/nominatim-daily-maintenance #{bin_directory}/maintenance/"
286 systemd_timer "nominatim-update-maintenance-trigger" do
288 description "Schedule daily maintenance tasks for Nominatim DB"
289 on_calendar "*-*-* 02:03:00 UTC"
292 service "nominatim-update-maintenance-trigger" do
300 repository node[:nominatim][:ui_repository]
301 revision node[:nominatim][:ui_revision]
306 template "#{ui_directory}/dist/theme/config.theme.js" do
307 source "ui-config.js.erb"
315 if node[:nominatim][:enable_qa_tiles]
316 python_package "nominatim-data-analyser" do
317 python_virtualenv python_directory
318 extra_index_url node[:nominatim][:pip_index]
321 directory qa_data_directory do
328 template "#{project_directory}/qa-config.yaml" do
329 source "qa_config.erb"
333 variables :outputdir => "#{qa_data_directory}/new"
336 ssl_certificate "qa-tile.nominatim.openstreetmap.org" do
337 domains ["qa-tile.nominatim.openstreetmap.org"]
338 notifies :reload, "service[nginx]"
341 nginx_site "qa-tiles.nominatim" do
342 template "nginx-qa-tiles.erb"
343 directory qa_data_directory
344 variables :qa_data_directory => qa_data_directory
348 ## Logging and monitoring
350 template "/etc/logrotate.d/nominatim" do
351 source "logrotate.nominatim.erb"
357 prometheus_exporter "nominatim" do
360 restrict_address_families "AF_UNIX"
362 "--nominatim.query-log=#{node[:nominatim][:logdir]}/query.log",
363 "--nominatim.database-name=#{node[:nominatim][:dbname]}"
367 ################################# END OF DEBIAN #############################
371 if node[:nominatim][:api_flavour] == "php"
372 include_recipe "php::fpm"
375 basedir = data_bag_item("accounts", "nominatim")["home"]
376 email_errors = data_bag_item("accounts", "lonvia")["email"]
385 ## Log directory setup
387 directory node[:nominatim][:logdir] do
394 file "#{node[:nominatim][:logdir]}/query.log" do
395 action :create_if_missing
401 file "#{node[:nominatim][:logdir]}/update.log" do
402 action :create_if_missing
410 include_recipe "postgresql"
412 postgresql_version = node[:nominatim][:dbcluster].split("/").first
413 postgis_version = node[:nominatim][:postgis]
415 package "postgresql-#{postgresql_version}-postgis-#{postgis_version}"
417 node[:nominatim][:dbadmins].each do |user|
418 postgresql_user user do
419 cluster node[:nominatim][:dbcluster]
421 only_if { node[:nominatim][:state] != "slave" }
425 postgresql_user "nominatim" do
426 cluster node[:nominatim][:dbcluster]
428 only_if { node[:nominatim][:state] != "slave" }
431 postgresql_user "www-data" do
432 cluster node[:nominatim][:dbcluster]
433 only_if { node[:nominatim][:state] != "slave" }
436 directory "#{basedir}/tablespaces" do
442 # NOTE: tablespaces must be exactly in the same location on each
443 # Nominatim instance when replication is in use. Therefore
444 # use symlinks to canonical directory locations.
445 node[:nominatim][:tablespaces].each do |name, location|
446 directory location do
453 link "#{basedir}/tablespaces/#{name}" do
457 postgresql_tablespace name do
458 cluster node[:nominatim][:dbcluster]
459 location "#{basedir}/tablespaces/#{name}"
466 include_recipe "python"
468 python_directory = "#{basedir}/venv"
476 libboost-filesystem-dev
495 python3-sqlalchemy-ext
506 if node[:nominatim][:api_flavour] == "php"
511 elsif node[:nominatim][:api_flavour] == "python"
513 python_virtualenv python_directory do
514 interpreter "/usr/bin/python3"
517 python_package "SQLAlchemy" do
518 python_virtualenv python_directory
522 python_package "PyICU" do
523 python_virtualenv python_directory
527 python_package "psycopg[binary]" do
528 python_virtualenv python_directory
532 python_package "psycopg2-binary" do
533 python_virtualenv python_directory
537 python_package "python-dotenv" do
538 python_virtualenv python_directory
542 python_package "pygments" do
543 python_virtualenv python_directory
547 python_package "PyYAML" do
548 python_virtualenv python_directory
552 python_package "falcon" do
553 python_virtualenv python_directory
557 python_package "uvicorn" do
558 python_virtualenv python_directory
562 python_package "gunicorn" do
563 python_virtualenv python_directory
567 python_package "jinja2" do
568 python_virtualenv python_directory
572 python_package "datrie" do
573 python_virtualenv python_directory
577 python_package "psutil" do
578 python_virtualenv python_directory
582 python_package "osmium" do
583 python_virtualenv python_directory
588 source_directory = "#{basedir}/src/nominatim"
589 build_directory = "#{basedir}/src/build"
590 project_directory = "#{basedir}/planet-project"
591 bin_directory = "#{basedir}/bin"
592 cfg_directory = "#{basedir}/etc"
593 ui_directory = "#{basedir}/ui"
594 qa_bin_directory = "#{basedir}/src/Nominatim-Data-Analyser"
595 qa_data_directory = "#{basedir}/qa-data"
597 [basedir, "#{basedir}/src", cfg_directory, bin_directory, build_directory, project_directory].each do |path|
606 directory "#{bin_directory}/maintenance" do
612 if node[:nominatim][:flatnode_file]
613 directory File.dirname(node[:nominatim][:flatnode_file]) do
618 remote_directory "#{project_directory}/static-website" do
623 files_owner "nominatim"
624 files_group "nominatim"
629 # Normally syncing via chef is a bad idea because syncing might involve
630 # an update of database functions which should not be done while an update
631 # is ongoing. Therefore we sync in between update cycles. There is an
632 # exception for slaves: they get DB function updates from the master, so
633 # only the source code needs to be updated, which chef may do.
634 git source_directory do
635 action node[:nominatim][:state] == "slave" ? :sync : :checkout
636 repository node[:nominatim][:repository]
637 revision node[:nominatim][:revision]
638 enable_submodules true
641 not_if { node[:nominatim][:state] != "slave" && File.exist?("#{source_directory}/README.md") }
642 notifies :run, "execute[compile_nominatim]"
645 remote_file "#{source_directory}/data/country_osm_grid.sql.gz" do
646 action :create_if_missing
647 source "https://nominatim.org/data/country_grid.sql.gz"
653 execute "compile_nominatim" do
657 command "cmake #{source_directory} && make"
658 notifies :run, "execute[install_nominatim]"
661 execute "install_nominatim" do
664 command "make install"
669 template "#{project_directory}/.env" do
670 source "nominatim.env.erb"
674 variables :base_url => node[:nominatim][:state] == "off" ? node[:fqdn] : "nominatim.openstreetmap.org",
675 :dbname => node[:nominatim][:dbname],
676 :flatnode_file => node[:nominatim][:flatnode_file],
677 :log_file => "#{node[:nominatim][:logdir]}/query.log",
678 :tokenizer => node[:nominatim][:config][:tokenizer],
679 :forward_dependencies => node[:nominatim][:config][:forward_dependencies],
680 :pool_size => node[:nominatim][:api_pool_size],
681 :query_timeout => node[:nominatim][:api_query_timeout],
682 :request_timeout => node[:nominatim][:api_request_timeout]
685 remote_file "#{project_directory}/secondary_importance.sql.gz" do
686 action :create_if_missing
687 source "https://nominatim.org/data/wikimedia-secondary-importance.sql.gz"
693 remote_file "#{project_directory}/wikimedia-importance.sql.gz" do
694 action :create_if_missing
695 source "https://nominatim.org/data/wikimedia-importance.sql.gz"
701 %w[gb_postcodes.csv.gz us_postcodes.csv.gz].each do |fname|
702 remote_file "#{project_directory}/#{fname}" do
704 source "https://nominatim.org/data/#{fname}"
711 # Webserver + frontend
713 %w[user_agent referrer email generic].each do |name|
714 file "#{cfg_directory}/nginx_blocked_#{name}.conf" do
715 action :create_if_missing
722 if node[:nominatim][:api_flavour] == "php"
723 node[:nominatim][:fpm_pools].each do |name, data|
727 pm_max_children data[:max_children]
729 pm_min_spare_servers 10
730 pm_max_spare_servers 20
731 pm_max_requests 10000
732 prometheus_port data[:prometheus_port]
735 elsif node[:nominatim][:api_flavour] == "python"
736 systemd_service "nominatim" do
737 description "Nominatim running as a gunicorn application"
740 working_directory project_directory
741 standard_output "append:#{node[:nominatim][:logdir]}/gunicorn.log"
742 standard_error "inherit"
743 exec_start "#{python_directory}/bin/gunicorn --max-requests 200000 -b unix:/run/gunicorn-nominatim.openstreetmap.org.sock -w #{node[:nominatim][:api_workers]} -k uvicorn.workers.UvicornWorker nominatim_api.server.falcon.server:run_wsgi"
744 exec_reload "/bin/kill -s HUP $MAINPID"
745 environment :PYTHONPATH => "/usr/local/lib/nominatim/lib-python/"
749 requires "nominatim.socket"
750 after "network.target"
753 systemd_socket "nominatim" do
754 description "Gunicorn socket for Nominatim"
755 listen_stream "/run/gunicorn-nominatim.openstreetmap.org.sock"
756 socket_user "www-data"
760 ssl_certificate node[:fqdn] do
761 domains [node[:fqdn],
762 "nominatim.openstreetmap.org",
764 "nominatim.openstreetmap.com",
765 "nominatim.openstreetmap.net",
766 "nominatim.openstreetmaps.org",
767 "nominatim.openmaps.org",
768 "nominatim.qgis.org"]
769 notifies :reload, "service[nginx]"
772 include_recipe "nginx"
774 nginx_site "default" do
778 frontends = search(:node, "recipes:web\\:\\:frontend").sort_by(&:name)
780 nginx_site "nominatim" do
782 directory project_directory
783 variables :pools => node[:nominatim][:fpm_pools],
784 :frontends => frontends,
785 :confdir => "#{basedir}/etc",
786 :ui_directory => ui_directory
789 template "/etc/logrotate.d/nginx" do
790 source "logrotate.nginx.erb"
799 nominatim-update-source
800 nominatim-update-refresh-db
801 nominatim-update-data
802 nominatim-daily-maintenance].each do |fname|
803 template "#{bin_directory}/#{fname}" do
804 source "#{fname}.erb"
808 variables :bindir => bin_directory,
809 :srcdir => source_directory,
810 :builddir => build_directory,
811 :projectdir => project_directory,
812 :qabindir => qa_bin_directory,
813 :qadatadir => qa_data_directory
817 systemd_service "nominatim-update" do
818 description "Update the Nominatim database"
819 exec_start "#{bin_directory}/nominatim-update"
821 standard_output "append:#{node[:nominatim][:logdir]}/update.log"
822 standard_error "inherit"
823 working_directory project_directory
826 systemd_service "nominatim-update-maintenance-trigger" do
827 description "Trigger daily maintenance tasks for Nominatim DB"
828 exec_start "ln -sf #{bin_directory}/nominatim-daily-maintenance #{bin_directory}/maintenance/"
832 systemd_timer "nominatim-update-maintenance-trigger" do
833 action node[:nominatim][:state] != "off" ? :create : :delete
834 description "Schedule daily maintenance tasks for Nominatim DB"
835 on_calendar "*-*-* 02:03:00 UTC"
838 service "nominatim-update-maintenance-trigger" do
839 action node[:nominatim][:state] != "off" ? :enable : :disable
846 repository node[:nominatim][:ui_repository]
847 revision node[:nominatim][:ui_revision]
852 template "#{ui_directory}/dist/theme/config.theme.js" do
853 source "ui-config.js.erb"
861 if node[:nominatim][:enable_qa_tiles]
862 package "python3-geojson"
864 git qa_bin_directory do
865 repository node[:nominatim][:qa_repository]
866 revision node[:nominatim][:qa_revision]
867 enable_submodules true
870 notifies :run, "execute[compile_qa]"
873 execute "compile_qa" do
876 cwd "#{qa_bin_directory}/clustering-vt"
880 directory qa_data_directory do
887 template "#{qa_bin_directory}/analyser/config/config.yaml" do
888 source "qa_config.erb"
892 variables :outputdir => "#{qa_data_directory}/new"
895 ssl_certificate "qa-tile.nominatim.openstreetmap.org" do
896 domains ["qa-tile.nominatim.openstreetmap.org"]
897 notifies :reload, "service[nginx]"
900 nginx_site "qa-tiles.nominatim" do
901 template "nginx-qa-tiles.erb"
902 directory build_directory
903 variables :qa_data_directory => qa_data_directory
910 cron_d "nominatim-clean-db" do
911 action node[:nominatim][:state] == "master" ? :create : :delete
915 command "#{bin_directory}/clean-db-nominatim"
919 if node[:nominatim][:state] == "master"
920 postgresql_user "replication" do
921 cluster node[:nominatim][:dbcluster]
922 password data_bag_item("nominatim", "passwords")["replication"]
926 directory node[:rsyncd][:modules][:archive][:path] do
932 template "#{bin_directory}/clean-db-nominatim" do
933 source "clean-db-nominatim.erb"
937 variables :archive_dir => node[:rsyncd][:modules][:archive][:path],
938 :update_stop_file => "#{basedir}/status/updates_disabled",
939 :streaming_clients => search(:node, "nominatim_state:slave").map { |slave| slave[:fqdn] }.join(" ")
945 cron_d "nominatim-backup" do
946 action (node[:nominatim][:enable_backup] && node[:nominatim][:state] != "off") ? :create : :delete
951 command "#{bin_directory}/backup-nominatim"
955 cron_d "nominatim-vacuum-db" do
956 action node[:nominatim][:state] != "off" ? :create : :delete
960 command "#{bin_directory}/vacuum-db-nominatim"
964 %w[backup-nominatim vacuum-db-nominatim].each do |fname|
965 template "#{bin_directory}/#{fname}" do
966 source "#{fname}.erb"
970 variables :db => node[:nominatim][:dbname]
976 template "/etc/logrotate.d/nominatim" do
977 source "logrotate.nominatim.erb"
984 prometheus_exporter "nominatim" do
987 restrict_address_families "AF_UNIX"
989 "--nominatim.query-log=#{node[:nominatim][:logdir]}/query.log",
990 "--nominatim.database-name=#{node[:nominatim][:dbname]}"
994 include_recipe "fail2ban"
995 end # platform?('debian')
997 frontend_addresses = frontends.collect { |f| f.ipaddresses(:role => :external) }
999 fail2ban_jail "nominatim_limit_req" do
1000 filter "nginx-limit-req"
1001 logpath "#{node[:nominatim][:logdir]}/nominatim.openstreetmap.org-error.log"
1004 ignoreips frontend_addresses.flatten.sort