#!/usr/bin/env bash # Build / refresh OSRM routing graph для site-finder (#39). # # Готовит .osrm-файлы (MLD pipeline) из OpenStreetMap-экстракта Свердловской # области и кладёт их в ./data/osrm/, откуда их монтирует docker-compose.prod.yml # (service `osrm`, volume ./data/osrm:/data → osrm-routed читает /data/.osrm). # # === Где запускать === # На VPS из /opt/gendesign: bash scripts/build_osrm.sh # (требует docker + ~5GB свободного диска + ~3GB свободной RAM на время extract). # # === ВАЖНО: это offline-задача, НЕ часть deploy.yml === # Download ~600MB-1.5GB + build 30-50 мин. Запускается ВРУЧНУЮ оркестратором, # НЕ на каждом push. deploy.yml только поднимает уже готовый osrm-контейнер. # # === Idempotent / re-runnable === # Повторный запуск перекачивает свежий pbf и пересобирает граф (overwrite). # Безопасно гонять ежемесячно (osm.pbf на Geofabrik обновляется ежедневно). # # monthly refresh: cron на VPS, напр. 0 4 1 * * bash /opt/gendesign/scripts/build_osrm.sh >> /var/log/osrm-build.log 2>&1 # После refresh: docker compose -p gendesign -f docker-compose.prod.yml up -d --force-recreate --no-deps osrm # # === Регион / источник pbf === # Geofabrik НЕ публикует Свердловскую область отдельным extract'ом — только # «Уральский ФО» (ural-fed-district). Два режима (CLIP=1 по умолчанию): # # CLIP=1 (default, РЕКОМЕНДОВАНО): качаем Ural-FO pbf и обрезаем по bbox # Свердл. обл. через `osmium extract`. Результат легче → меньше RAM/диск # в build и в runtime (osrm-routed warm ~0.8-1.2GB вместо ~1.5GB+ на # весь Ural-FO). Минус: нужен extra-шаг osmium (запускаем в docker). # # CLIP=0: используем весь Ural-FO pbf как есть (zero-clip, проще, но тяжелее — # граф Курган/Тюмень/ХМАО/ЯНАО грузится в RAM без пользы для Свердл). # Бери, если RAM не жмёт и нужен запас на соседние регионы. # # bbox Свердловской обл (грубый, с запасом): lon 57.2..66.2, lat 56.0..62.0. # Можно сузить до ЕКБ-агломерации если RAM критична (lon 59.0..62.5, lat 56.2..58.2) # через EKB_TIGHT=1. # # === Профили === # Car (driving) — основной, строится всегда → .osrm*. # Foot (walking) — опциональный Phase-2, строится при WALK=1 → -foot.osrm*. # Для walking нужен второй compose-service (osrm-walk) — см. docs/osrm-routing.md. # Этот PR car-first; walk выключен по умолчанию (issue хочет оба eventually). # # === Проверка после build === # docker compose -p gendesign -f docker-compose.prod.yml up -d osrm # docker compose -p gendesign -f docker-compose.prod.yml exec osrm \ # sh -c 'echo OK' # контейнер жив # # route-probe С ХОСТА (внутри образа нет curl) — временно прокинуть порт: # docker run --rm --network gendesign_default curlimages/curl:8 -fsS \ # 'http://osrm:5000/route/v1/driving/60.6,56.84;60.65,56.86' # ↑ ожидаем JSON {"code":"Ok","routes":[...]}. set -euo pipefail # --- Config (override через env) ------------------------------------------- OSRM_IMAGE="${OSRM_IMAGE:-osrm/osrm-backend:latest}" OSMIUM_IMAGE="${OSMIUM_IMAGE:-stefda/osmium-tool:latest}" REGION="${OSRM_REGION:-sverdlovsk}" # имя выходных .osrm* (= OSRM_REGION в compose) PBF_URL="${PBF_URL:-https://download.geofabrik.de/russia/ural-fed-district-latest.osm.pbf}" CLIP="${CLIP:-1}" # 1 = обрезать по bbox Свердл, 0 = весь Ural-FO EKB_TIGHT="${EKB_TIGHT:-0}" # 1 = узкий bbox только ЕКБ-агломерация WALK="${WALK:-0}" # 1 = ещё и пеший профиль -foot.osrm* CAR="${CAR:-1}" # 0 = пропустить car-граф (foot-only refresh, #39 A3) CAR_PROFILE="${CAR_PROFILE:-/opt/car.lua}" # встроенные профили в osrm-backend образе FOOT_PROFILE="${FOOT_PROFILE:-/opt/foot.lua}" # bbox: left,bottom,right,top (lon_min,lat_min,lon_max,lat_max) if [ "$EKB_TIGHT" = "1" ]; then BBOX="${BBOX:-59.0,56.2,62.5,58.2}" # ЕКБ-агломерация (минимум RAM) else BBOX="${BBOX:-57.2,56.0,66.2,62.0}" # вся Свердловская обл (с запасом) fi # Каталог host (= ./data/osrm в compose). Скрипт зовём из /opt/gendesign. DATA_DIR="${DATA_DIR:-$(pwd)/data/osrm}" mkdir -p "$DATA_DIR" RAW_PBF="$DATA_DIR/${REGION}-raw.osm.pbf" # сырой Ural-FO (или clip-источник) WORK_PBF="$DATA_DIR/${REGION}.osm.pbf" # то, что реально пойдёт в osrm-extract echo "=== OSRM build ===" echo " region : $REGION" echo " pbf source : $PBF_URL" echo " clip to bbox : CLIP=$CLIP BBOX=$BBOX (EKB_TIGHT=$EKB_TIGHT)" echo " walking profile : WALK=$WALK" echo " data dir (host) : $DATA_DIR" echo "" # --- 1. Download ------------------------------------------------------------ # -N: качаем только если на сервере новее (idempotent monthly refresh). echo "=== 1. Download pbf (Ural-FO ≈600MB) ===" if command -v wget >/dev/null 2>&1; then wget -N -O "$RAW_PBF" "$PBF_URL" else curl -fSL --time-cond "$RAW_PBF" -o "$RAW_PBF" "$PBF_URL" fi # --- 2. Clip (optional) ----------------------------------------------------- if [ "$CLIP" = "1" ]; then echo "=== 2. Clip to Свердл bbox через osmium ($BBOX) ===" docker run --rm -v "$DATA_DIR:/data" "$OSMIUM_IMAGE" \ osmium extract -b "$BBOX" "/data/$(basename "$RAW_PBF")" \ -o "/data/$(basename "$WORK_PBF")" --overwrite else echo "=== 2. No clip (CLIP=0) — используем Ural-FO целиком ===" cp -f "$RAW_PBF" "$WORK_PBF" fi # --- helper: запуск osrm-backend образа на /data -------------------------- osrm() { docker run --rm -v "$DATA_DIR:/data" "$OSRM_IMAGE" "$@" } # --- 3. MLD pipeline (car) -------------------------------------------------- # MLD = extract → partition → customize. НЕ osrm-contract (то CH-алгоритм, # несовместим с --algorithm mld в compose). # # osrm-extract пишет .osrm. Имя входного pbf специально равно # целевому базовому имени (.osm.pbf → .osrm; -foot.osm.pbf # → -foot.osrm), поэтому partition/customize работают по тому же базовому # имени и переименование не требуется. build_profile() { local profile="$1" src_pbf="$2" local base extracted base="$(basename "${src_pbf%.osm.pbf}")" extracted="/data/${base}.osrm" echo "--- extract ($profile → ${base}.osrm) ---" osrm osrm-extract -p "$profile" "/data/$(basename "$src_pbf")" echo "--- partition (${base}.osrm) ---" osrm osrm-partition "$extracted" echo "--- customize (${base}.osrm) ---" osrm osrm-customize "$extracted" } if [ "$CAR" = "1" ]; then echo "=== 3. Build car profile → ${REGION}.osrm* ===" build_profile "$CAR_PROFILE" "$WORK_PBF" else echo "=== 3. Skip car profile (CAR=0) — foot-only refresh, не трогаем живой ${REGION}.osrm ===" fi # --- 4. MLD pipeline (foot, optional) -------------------------------------- if [ "$WALK" = "1" ]; then echo "=== 4. Build foot profile → ${REGION}-foot.osrm* ===" # Отдельная копия pbf под пеший граф (osrm-extract overwrite'ит базу). WALK_PBF="$DATA_DIR/${REGION}-foot.osm.pbf" cp -f "$WORK_PBF" "$WALK_PBF" build_profile "$FOOT_PROFILE" "$WALK_PBF" else echo "=== 4. Skip foot profile (WALK=0) — car-first, см. docs/osrm-routing.md ===" fi # --- 5. Cleanup heavy intermediates ---------------------------------------- echo "=== 5. Cleanup промежуточных .pbf (граф .osrm* уже собран) ===" rm -f "$RAW_PBF" "$WORK_PBF" "${DATA_DIR}/${REGION}-foot.osm.pbf" 2>/dev/null || true echo "" echo "=== DONE ===" echo "Артефакты в $DATA_DIR:" ls -lh "$DATA_DIR"/*.osrm* 2>/dev/null | awk '{print " "$5"\t"$9}' || true echo "" echo "Дальше:" echo " docker compose -p gendesign -f docker-compose.prod.yml up -d osrm" echo " # verify (curl с хоста — внутри образа curl нет):" echo " docker run --rm --network gendesign_default curlimages/curl:8 -fsS \\" echo " 'http://osrm:5000/route/v1/driving/60.6,56.84;60.65,56.86'"