├── hosts ├── requirements.yml ├── roles ├── firewall │ ├── defaults │ │ └── main.yml │ ├── handlers │ │ └── main.yml │ ├── templates │ │ └── jail.local.j2 │ └── tasks │ │ └── main.yml ├── otp_data │ ├── handlers │ │ └── main.yml │ ├── templates │ │ ├── update_gtfs.sh.j2 │ │ └── update_osm.sh.j2 │ └── tasks │ │ └── main.yml ├── nginx │ ├── handlers │ │ └── main.yml │ ├── files │ │ └── https.conf │ ├── vars │ │ └── main.yml │ └── tasks │ │ ├── main.yml │ │ └── ssl.yml ├── opentripplanner │ ├── files │ │ ├── build-config.json │ │ ├── otp-config.json │ │ └── router-config.json │ ├── handlers │ │ └── main.yml │ ├── defaults │ │ └── main.yml │ ├── templates │ │ └── otp.service.j2 │ └── tasks │ │ └── main.yml ├── photon │ ├── handlers │ │ └── main.yml │ ├── templates │ │ └── photon.service.j2 │ └── tasks │ │ └── main.yml └── common │ └── tasks │ └── main.yml ├── ansible.cfg ├── run.sh ├── install_roles.yml ├── playbook.yml ├── group_vars └── all │ └── vars.yml └── photon └── rebuild_nominatim_photon /hosts: -------------------------------------------------------------------------------- 1 | [otp] 2 | otp.osmz.ru 3 | -------------------------------------------------------------------------------- /requirements.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - src: oefenweb.swapfile 3 | - src: geerlingguy.ntp 4 | -------------------------------------------------------------------------------- /roles/firewall/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | timezone: Europe/Moscow 3 | admin_email: nobody@example.com 4 | -------------------------------------------------------------------------------- /roles/firewall/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: restart ufw 3 | systemd: name=ufw state=restarted 4 | -------------------------------------------------------------------------------- /roles/otp_data/handlers/main.yml: -------------------------------------------------------------------------------- 1 | - name: restart otp 2 | service: 3 | name: otp 4 | state: restarted 5 | -------------------------------------------------------------------------------- /roles/nginx/handlers/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: restart nginx 3 | service: name=nginx state=restarted 4 | become: yes 5 | -------------------------------------------------------------------------------- /roles/opentripplanner/files/build-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "transitServiceStart": "-P1W", 3 | "transitServiceEnd": "P3M" 4 | } 5 | -------------------------------------------------------------------------------- /roles/opentripplanner/handlers/main.yml: -------------------------------------------------------------------------------- 1 | - name: Restart OpenTripPlanner 2 | service: 3 | name: otp 4 | state: restarted 5 | -------------------------------------------------------------------------------- /roles/photon/handlers/main.yml: -------------------------------------------------------------------------------- 1 | - name: restart photon 2 | become: yes 3 | service: 4 | name: photon 5 | state: restarted 6 | -------------------------------------------------------------------------------- /ansible.cfg: -------------------------------------------------------------------------------- 1 | [defaults] 2 | pipelining = True 3 | roles_path = roles.galaxy:roles 4 | # vault_password_file = .vault_pass 5 | retry_files_enabled = False 6 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # ansible-playbook -i hosts playbook.yml ${1+--start-at-task "$1"} 3 | ansible-playbook playbook.yml -i hosts ${1+-e "photon_data=$1"} 4 | -------------------------------------------------------------------------------- /roles/nginx/files/https.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80 default_server; 3 | listen [::]:80 default_server; 4 | server_name _; 5 | return 301 https://$host$request_uri; 6 | } 7 | -------------------------------------------------------------------------------- /roles/opentripplanner/files/otp-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "otpFeatures" : { 3 | "APIBikeRental" : false, 4 | "APIGraphInspectorTile": false, 5 | "APIExternalGeocoder": false 6 | } 7 | } 8 | 9 | -------------------------------------------------------------------------------- /roles/otp_data/templates/update_gtfs.sh.j2: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | WGET=/usr/bin/wget 3 | # URL="http://peatus.ee/gtfs/gtfs.zip" 4 | URL="http://transport.tallinn.ee/data/gtfs.zip" 5 | 6 | $WGET "$URL" -O "{{ otp_data_dir }}/gtfs.zip" 7 | -------------------------------------------------------------------------------- /roles/nginx/vars/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | acme_challenge_type: http-01 3 | acme_directory: https://acme-v02.api.letsencrypt.org/directory 4 | acme_version: 2 5 | acme_email: ilya@zverev.info 6 | 7 | letsencrypt_dir: /etc/letsencrypt 8 | letsencrypt_account_key: /etc/letsencrypt/account/account.key 9 | domain_name: osmcards.org 10 | -------------------------------------------------------------------------------- /install_roles.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: localhost 3 | vars: 4 | galaxy_path: roles.galaxy 5 | tasks: 6 | - name: Remove old galaxy roles 7 | file: path={{ galaxy_path }} state=absent 8 | - name: Install Ansible Galaxy roles 9 | local_action: command ansible-galaxy install -r requirements.yml --roles-path {{ galaxy_path }} 10 | -------------------------------------------------------------------------------- /roles/photon/templates/photon.service.j2: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Start Photon process 3 | After=network-online.target 4 | 5 | [Service] 6 | User={{ photon_user }} 7 | WorkingDirectory={{ photon_dir }} 8 | Restart=on-failure 9 | ExecStart=/usr/bin/java -jar {{ photon_dir }}/photon-{{ photon_version }}.jar 10 | [Install] 11 | WantedBy=multi-user.target 12 | -------------------------------------------------------------------------------- /playbook.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - hosts: all 3 | gather_facts: no 4 | remote_user: root 5 | roles: 6 | - common 7 | 8 | - hosts: all 9 | become: yes 10 | roles: 11 | - geerlingguy.ntp 12 | - geerlingguy.certbot 13 | - firewall 14 | 15 | - hosts: all 16 | remote_user: zverik 17 | roles: 18 | - opentripplanner 19 | - otp_data 20 | - photon 21 | -------------------------------------------------------------------------------- /roles/opentripplanner/files/router-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "routingDefaults": { 3 | "walkSpeed": 1.4, 4 | "maxWalkDistance": 1500, 5 | "-walkReluctance": 2.0, 6 | "debugItineraryFilter": false, 7 | "groupBySimilarityKeepOne": 0.99, 8 | "itineraryFilters": { 9 | "debug": false, 10 | "groupSimilarityKeepOne": 0.99 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /roles/firewall/templates/jail.local.j2: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | destemail = {{ admin_email }} 3 | 4 | [sshd] 5 | enabled = true 6 | maxretry = 3 7 | 8 | [apache-auth] 9 | enabled = true 10 | 11 | [apache-badbots] 12 | enabled = true 13 | 14 | [apache-overflows] 15 | enabled = true 16 | 17 | [apache-fakegooglebot] 18 | enabled = true 19 | 20 | [apache-shellshock] 21 | enabled = true 22 | 23 | [postfix] 24 | enabled = false 25 | 26 | [sendmail-auth] 27 | enabled = false 28 | -------------------------------------------------------------------------------- /roles/nginx/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install nginx and uwsgi 3 | apt: 4 | name: uwsgi,uwsgi-plugin-python3,nginx-full,git 5 | state: present 6 | 7 | - name: Redirect HTTPS 8 | copy: 9 | src: https.conf 10 | dest: /etc/nginx/sites-available/https.conf 11 | 12 | - name: Enable redirecting HTTPS 13 | file: 14 | state: link 15 | src: /etc/nginx/sites-available/https.conf 16 | path: /etc/nginx/sites-enabled/https.conf 17 | notify: restart nginx 18 | -------------------------------------------------------------------------------- /roles/opentripplanner/defaults/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | otp_bin_dir: /opt/opentripplanner 3 | otp_data_dir: /var/otp 4 | otp_user: opentripplanner 5 | otp_version: "1.4.0" 6 | otp_jar_suffix: "-shaded" 7 | otp_jar_sha1: "0367b1a15bac5f587807a5b897a9734209f8135c" 8 | otp_process_mem: 3G 9 | otp_web_port: 8080 10 | otp_service_after: 'network-online.target' 11 | otp_service_wantedby: 'multi-user.target' 12 | 13 | java_version: "8u*" 14 | java_major_version: "8" 15 | java_flavor: "openjdk" 16 | java_oracle_accept_license_agreement: False 17 | -------------------------------------------------------------------------------- /group_vars/all/vars.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ntp_timezone: Europe/Tallinn 3 | admin_email: ilya@zverev.info 4 | 5 | certbot_admin_email: ilya@zverev.info 6 | certbot_create_if_missing: true 7 | certbot_create_extra_args: '' 8 | certbot_certs: 9 | - domains: 10 | - otp.osmz.org 11 | 12 | otp_bin_dir: /opt/src/opentripplanner 13 | otp_data_dir: /var/otp 14 | otp_user: zverik 15 | otp_version: "2.0.0" 16 | otp_jar_sha1: "b2652bba5983393bb102e6e16190f73e9c297e51" 17 | otp_process_mem: 2G 18 | 19 | photon_dir: /opt/src/photon 20 | photon_version: "0.5.0" 21 | photon_user: zverik 22 | -------------------------------------------------------------------------------- /roles/opentripplanner/templates/otp.service.j2: -------------------------------------------------------------------------------- 1 | ## NOTICE: This file is written by ansible, and any changes made here will be overwritten on 2 | # next provision. 3 | 4 | [Unit] 5 | Description=Start OpenTripPlanner process 6 | After={{ otp_service_after }} 7 | 8 | [Service] 9 | User={{ otp_user }} 10 | WorkingDirectory={{ otp_bin_dir }} 11 | Restart=on-failure 12 | ExecStart=/usr/bin/java -Xmx{{otp_process_mem}} -jar {{ otp_bin_dir }}/{{ otp_jar_name }} --build --serve --port {{ otp_web_port }} {{ otp_data_dir }} 13 | 14 | [Install] 15 | WantedBy={{ otp_service_wantedby }} 16 | -------------------------------------------------------------------------------- /roles/firewall/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install UFW 3 | apt: name=ufw state=present 4 | 5 | - name: Configure ufw defaults 6 | ufw: direction={{ item.direction }} policy={{ item.policy }} 7 | with_items: 8 | - { direction: 'incoming', policy: 'deny' } 9 | - { direction: 'outgoing', policy: 'allow' } 10 | notify: restart ufw 11 | 12 | - name: Open Nginx and SSH ports 13 | ufw: 14 | port: "{{ item }}" 15 | proto: tcp 16 | rule: allow 17 | with_items: 18 | - ssh 19 | - '8080' 20 | - '2322' 21 | notify: restart ufw 22 | 23 | - name: Enable ufw logging 24 | ufw: logging=on 25 | notify: restart ufw 26 | 27 | - name: Enable ufw 28 | ufw: state=enabled 29 | -------------------------------------------------------------------------------- /roles/nginx/tasks/ssl.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Create directories for Let's Encrypt 3 | become: yes 4 | file: 5 | path: "{{ letsencrypt_dir }}/{{ item }}" 6 | state: directory 7 | owner: root 8 | group: root 9 | mode: u=rwx,g=x,o=x 10 | with_items: 11 | - account 12 | - certs 13 | - csrs 14 | - keys 15 | 16 | - name: Generate account key 17 | command: openssl genrsa 4096 -out {{ letsencrypt_account_key }} 18 | args: 19 | creates: "{{ letsencrypt_account_key }}" 20 | 21 | - name: Generate domain key 22 | command: openssl genrsa 4096 -out {{ letsencrypt_dir }}/keys/{{ domain_name }}.key 23 | args: 24 | creates: "{{ letsencrypt_dir }}/keys/{{ domain_name }}.key" 25 | -------------------------------------------------------------------------------- /roles/otp_data/templates/update_osm.sh.j2: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | OSMIUM=/usr/bin/osmium 3 | WGET=/usr/bin/wget 4 | SOURCE="http://download.geofabrik.de/europe/estonia-latest.osm.pbf" 5 | BBOX=24.500647,59.339805,25.038977,59.53254 6 | 7 | $WGET "$SOURCE" -O /tmp/estonia.osm.pbf 8 | if [ -n "${BBOX-}" ]; then 9 | $OSMIUM extract --strategy complete_ways --bbox "$BBOX" /tmp/estonia.osm.pbf -o /tmp/estonia-crop.osm.pbf 10 | mv /tmp/estonia-crop.osm.pbf /tmp/estonia.osm.pbf 11 | fi 12 | 13 | $OSMIUM tags-filter /tmp/estonia.osm.pbf w/highway w/public_transport=platform w/railway=platform w/park_ride=yes r/type=restriction \ 14 | -o {{ otp_data_dir }}/estonia.osm.pbf -f pbf,add_metadata=false 15 | rm /tmp/estonia.osm.pbf 16 | 17 | # TODO: pre-build osm data 18 | -------------------------------------------------------------------------------- /roles/photon/tasks/main.yml: -------------------------------------------------------------------------------- 1 | - name: Create Photon directory 2 | file: 3 | path: "{{ photon_dir }}" 4 | mode: "0775" 5 | state: directory 6 | 7 | - name: Download Photon 8 | get_url: 9 | url: "https://github.com/komoot/photon/releases/download/{{ photon_version }}/photon-{{ photon_version }}.jar" 10 | dest: "{{ photon_dir }}/photon-{{ photon_version }}.jar" 11 | 12 | - name: Remove old data 13 | when: photon_data is defined 14 | file: 15 | path: "{{ photon_dir }}/photon_data" 16 | state: absent 17 | 18 | - name: Upload data 19 | when: photon_data is defined 20 | unarchive: 21 | src: "{{ photon_data }}" 22 | dest: "{{ photon_dir }}" 23 | creates: "{{ photon_dir }}/photon_data" 24 | notify: restart photon 25 | 26 | - name: Install systemd service 27 | become: yes 28 | template: 29 | src: photon.service.j2 30 | dest: /etc/systemd/system/photon.service 31 | notify: restart photon 32 | register: ph_service 33 | 34 | - name: Enable Photon service 35 | when: ph_service 36 | become: yes 37 | systemd: 38 | name: photon.service 39 | enabled: yes 40 | daemon_reload: yes 41 | -------------------------------------------------------------------------------- /roles/common/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Learn OS 3 | setup: 4 | gather_subset: '!all' 5 | filter: 'ansible_distribution*' 6 | 7 | - name: Test for Ubuntu 8 | when: ansible_distribution != 'Ubuntu' 9 | fail: 10 | msg: Requires Ubuntu. 11 | 12 | - name: Ensure apt cache is up to date 13 | apt: update_cache=yes cache_valid_time=3600 upgrade=dist 14 | changed_when: False 15 | 16 | - name: install setfacl support 17 | apt: name=acl 18 | 19 | - name: Create zverik user 20 | user: name=zverik shell=/bin/bash 21 | 22 | - name: Add a ssh key to zverik 23 | authorized_key: 24 | user: zverik 25 | state: present 26 | key: "{{ lookup('file', lookup('env', 'HOME') + '/.ssh/id_rsa.pub') }}" 27 | 28 | - name: Add zverik to sudoers 29 | copy: 30 | content: "zverik ALL=(ALL) NOPASSWD:ALL" 31 | dest: /etc/sudoers.d/zverik 32 | 33 | - name: Install useful packages 34 | apt: 35 | name: 36 | - tmux 37 | - htop 38 | - ncdu 39 | - vim 40 | state: present 41 | 42 | - name: Create /opt/src directory 43 | file: 44 | path: /opt/src 45 | state: directory 46 | owner: zverik 47 | mode: 0755 48 | -------------------------------------------------------------------------------- /roles/otp_data/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install osmium-tool 3 | become: yes 4 | apt: 5 | name: osmium-tool 6 | state: present 7 | 8 | - name: Upload GTFS script 9 | template: 10 | src: update_gtfs.sh.j2 11 | dest: /opt/src/update_gtfs.sh 12 | mode: '0755' 13 | 14 | - name: Add GTFS script to cron 15 | cron: 16 | name: update gtfs 17 | minute: '0' 18 | hour: '3' 19 | job: "/opt/src/update_gtfs.sh > /dev/null" 20 | 21 | - name: Run GTFS script 22 | command: /opt/src/update_gtfs.sh 23 | args: 24 | creates: "{{ otp_data_dir}}/gtfs.zip" 25 | 26 | - name: Upload OSM script 27 | template: 28 | src: update_osm.sh.j2 29 | dest: /opt/src/update_osm.sh 30 | mode: '0755' 31 | 32 | - name: Add OSM script to cron 33 | cron: 34 | name: update osm 35 | minute: '30' 36 | hour: '3' 37 | weekday: '0' 38 | job: "/opt/src/update_osm.sh > /dev/null" 39 | 40 | - name: Run OSM script 41 | command: /opt/src/update_osm.sh 42 | args: 43 | creates: "{{ otp_data_dir}}/estonia.osm.pbf" 44 | 45 | - name: Restart OTP each night 46 | become: yes 47 | cron: 48 | name: restart opentripplanner 49 | minute: "0" 50 | hour: "4" 51 | job: "systemctl restart otp" 52 | -------------------------------------------------------------------------------- /roles/opentripplanner/tasks/main.yml: -------------------------------------------------------------------------------- 1 | --- 2 | - name: Install OpenJDK 3 | become: yes 4 | apt: 5 | name: openjdk-11-jre-headless 6 | state: present 7 | 8 | - name: Create OpenTripPlanner binary directory 9 | file: 10 | path: "{{ otp_bin_dir }}" 11 | mode: "0775" 12 | state: directory 13 | 14 | - name: Store derived jarfile name for convenience 15 | set_fact: 16 | otp_jar_name: "otp-{{ otp_version }}{{ otp_jar_suffix }}.jar" 17 | 18 | - name: Download OpenTripPlanner 19 | get_url: 20 | url: "https://repo1.maven.org/maven2/org/opentripplanner/otp/{{ otp_version }}/{{ otp_jar_name }}" 21 | dest: "{{ otp_bin_dir }}/{{ otp_jar_name }}" 22 | checksum: sha1:{{ otp_jar_sha1 }} 23 | mode: "0775" 24 | 25 | - name: Create data directory 26 | become: yes 27 | file: 28 | path: "{{ otp_data_dir }}" 29 | mode: "0775" 30 | state: directory 31 | owner: "{{ otp_user }}" 32 | 33 | - name: Copy json configuration 34 | become: yes 35 | copy: 36 | src: "{{ item }}" 37 | dest: "{{ otp_data_dir }}" 38 | owner: "{{ otp_user }}" 39 | loop: 40 | - build-config.json 41 | - otp-config.json 42 | - router-config.json 43 | 44 | - name: Install systemd service 45 | become: yes 46 | template: 47 | src: otp.service.j2 48 | dest: /etc/systemd/system/otp.service 49 | 50 | - name: Enable OpenTripPlanner service 51 | become: yes 52 | systemd: 53 | name: otp.service 54 | enabled: yes 55 | daemon_reload: yes 56 | -------------------------------------------------------------------------------- /photon/rebuild_nominatim_photon: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -u -e 3 | 4 | PHOTON_VERSION=0.5.0 5 | NOMINATIM_VERSION=4.4 6 | IMPORT_WIKIPEDIA=false 7 | 8 | # If docker container is running, something was wrong with Photon 9 | if [ -z "$(docker ps | grep nominatim)" ]; then 10 | 11 | # Remove stray nominatim containers 12 | docker rm nominatim || true 13 | 14 | # Download Photon 15 | echo "Downloading Photon $PHOTON_VERSION" 16 | rm -f photon.jar 17 | curl -L -s -o photon.jar "https://github.com/komoot/photon/releases/download/$PHOTON_VERSION/photon-$PHOTON_VERSION.jar" 18 | 19 | # Download and trim the extract 20 | echo "Downloading and trimming the OSM extract" 21 | BBOX=24.500647,59.339805,25.038977,59.53254 22 | rm -f estonia-latest.osm.pbf 23 | curl -L -s -O 'https://download.geofabrik.de/europe/estonia-latest.osm.pbf' 24 | osmium extract --bbox "$BBOX" estonia-latest.osm.pbf -o /tmp/estonia-crop.osm.pbf 25 | mv /tmp/estonia-crop.osm.pbf estonia-latest.osm.pbf 26 | 27 | # Start a Nominatim container 28 | echo "Starting Nominatim $NOMINATIM_VERSION container" 29 | docker run -d --rm \ 30 | -e PBF_PATH=/nominatim/data/estonia-latest.osm.pbf \ 31 | -e IMPORT_WIKIPEDIA=$IMPORT_WIKIPEDIA \ 32 | -e FREEZE=true \ 33 | -e NOMINATIM_PASSWORD=qwerty12 \ 34 | -p 8080:8080 -p 5433:5432 \ 35 | -v $(pwd):/nominatim/data \ 36 | --name nominatim \ 37 | mediagis/nominatim:$NOMINATIM_VERSION 38 | 39 | # Wait until Nominatim is ready 40 | until [ "$(curl -s localhost:8080/status)" == "OK" ]; do echo -n .; sleep 2; done 41 | echo 42 | 43 | else 44 | echo "Nominatim already started, continuing with the export" 45 | fi 46 | 47 | # Export the Photon database 48 | echo "Exporting the Photon database" 49 | [ -e photon_data ] && rm -f photon_data_backup && mv photon_data photon_data_backup 50 | java -jar photon.jar -nominatim-import -host localhost -port 5433 -database nominatim -user nominatim -password qwerty12 -languages en,ee,ru 51 | tar -czf photon_ee_$(date +%y%m%d).tgz photon_data 52 | 53 | # Stop and remove the container 54 | echo "All done, stopping container and removing temporary files" 55 | docker stop nominatim 56 | 57 | # Remove temporary files 58 | rm estonia-latest.osm.pbf 59 | rm photon.jar 60 | rm -r photon_data 61 | --------------------------------------------------------------------------------