Commit ea6c4b0e by Bambang Adrian

feat(osrm): initial develop osrm self-manage v1.0

parent a08f12e2
🔥 Siap, berikut ini rencana **final changelog untuk `install-osrm-ngpm.sh v4.6`**, biar kamu tahu persis apa yang akan saya implementasikan sebelum saya keluarkan skrip lengkapnya:
# 🧩 OSRM NGPM Installer — Specification (Final Draft v1.0)
## 1. Tujuan
Menyediakan **satu bash script tunggal (`install-osrm-ngpm.sh`)** untuk mengelola *lifecycle* OSRM (Open Source Routing Machine) environment berbasis Docker pada sistem **Rocky Linux 10**.
Skrip ini bersifat:
* **Idempotent** → aman dijalankan berulang kali.
* **Modular** → bisa install, update, dan remove secara terpisah untuk backend, frontend, profil, dan map.
* **Self-contained** → tidak memerlukan konfigurasi eksternal manual selain argumen CLI.
* **Interactive** → menggunakan prompt CLI konfirmasi (bukan `dialog`).
* **Auto dependency check** → instalasi otomatis bila memungkinkan.
---
## 2. Struktur Direktori Default
| Komponen | Path | Keterangan |
| --------------------- | -------------------------------------------- | ----------------------- |
| Working directory | `~/data/docker-data/osrm` | Root semua komponen |
| Dataset | `~/data/docker-data/osrm/dataset` | File `.osm.pbf` |
| Profiles | `~/data/docker-data/osrm/profiles` | File `.lua` profil |
| Logs | `~/data/docker-data/osrm/logs` | Semua log proses |
| Docker Compose Config | `~/data/docker-data/osrm/docker-compose.yml` | Konfigurasi absolut |
| Backup | `~/data/docker-data/osrm/backup/` | Backup konfigurasi lama |
--- ---
## 🧩 **install-osrm-ngpm.sh v4.6 — Production Ready Edition** ## 3. Dependency Management
### ⚙️ Core Enhancements ### 3.1. Package wajib
**Auto Lua Patch** * `docker`
* `docker-compose`
* `curl`
* `wget`
* `jq` *(optional tapi direkomendasikan untuk output JSON)*
* `tar`, `gzip`
* Deteksi otomatis `arg[0]` atau variabel bermasalah di file profile `.lua`. ### 3.2. Otomatisasi
* Patch jadi aman (`local profile_name = "car"`).
* Berlaku untuk semua profile (`car`, `motorcycle`, `bicycle`, `foot`, dll).
**Sequential Routing Preparation** Jika dependency tidak ditemukan:
* Preprocessing tetap jalan satu per satu untuk menghindari OOM/CPU spikes. * Jika bisa diinstal tanpa konfigurasi tambahan → auto install dengan `dnf install -y`.
* Setelah preprocessing sukses → otomatis buat **`osrm-routed-*` service** di `docker-compose.yml`. * Jika butuh setup tambahan (contoh: `docker`) → tampilkan warning dan petunjuk manual.
* Jika preprocess gagal → skip pembuatan service & tampilkan log merah `[FAILED]`.
**Docker Compose Update Logic** ---
* Template `docker-compose.yml` disusun modular. ## 4. Profil Routing
* Section `services:` akan diupdate otomatis jika profil bertambah.
* Tambah opsi `--rebuild` untuk force-regenerate compose file (jika sudah ada lama).
**Improved Logging** ### 4.1. Default Profiles
* Tambah notifikasi jelas: | Nama | File | Catatan |
| ---------- | ---------------- | ------------------------------------------------------------------ |
| car | `car.lua` | bawaan OSRM |
| bike | `bicycle.lua` | bawaan OSRM |
| foot | `foot.lua` | bawaan OSRM |
| motorcycle | `motorcycle.lua` | kustom: `maxspeed=100km/h`, `u-turn penalty=60s`, `avoid toll=yes` |
``` Semua profil disimpan di `--profile-dir` dan digunakan sesuai argumen `--profiles`.
[INFO] ✅ Preprocess success: car
[INFO] 🚀 Spawning routed service: osrm-routed-car (port: 5000)
```
* Tambah warning warna jika ada error Lua atau path permission issue.
--- ---
### 🌐 **Frontend / NGPM Integration** ## 5. Default Konfigurasi
✅ Tidak auto-modify frontend config (tetap aman untuk multi-proxy) | Flag | Deskripsi | Default |
✅ Menampilkan **Frontend & NGPM hints** di akhir proses: | --------------------- | ------------------------------------------------------ | --------------------------------------------------------------------- |
| `--profiles` | Daftar profil yang diinstall | `car` |
| `--profile-dir` | Direktori profil Lua | `~/data/docker-data/osrm/profiles` |
| `--map-dir` | Direktori file `.osm.pbf` | `~/data/docker-data/osrm/dataset` |
| `--map-source` | URL atau local path sumber peta | `http://download.geofabrik.de/asia/indonesia-latest.osm.pbf` |
| `--backend-version` | Versi OSRM backend | `latest` |
| `--frontend-version` | Versi OSRM frontend | `latest` |
| `--backend-registry` | Registry backend | `ghcr.io/project-osrm/osrm-backend` |
| `--frontend-registry` | Registry frontend | `ghcr.io/project-osrm/osrm-frontend` |
| `--backend-port` | Port backend | `5000` |
| `--frontend-port` | Port frontend | `9966` |
| `--backend-domain` | Domain backend | `localhost` |
| `--frontend-domain` | Domain frontend | `localhost` |
| `--backend-path` | Path API backend | `/osrm` |
| `--api-version` | Versi API backend | `v1` |
| `--backend-url` | URL lengkap backend (untuk frontend) | `http://{backend-domain}:{backend-port}/osrm/{api-version}/{profile}` |
| `--network` | Docker network | `ngpm-network` |
| `--work-dir` | Direktori kerja utama | `~/data/docker-data/osrm` |
| `--domain` | Alias untuk domain publik (opsional) | *kosong* |
| `--dry-run` | Simulasi tanpa eksekusi | `false` |
| `--print-config` | Cetak konfigurasi saja | `false` |
| `--update` | Update backend/frontend tanpa map/profile | `false` |
| `--update-map` | Update map dataset | `false` |
| `--update-profile` | Update profil routing | `false` |
| `--update-backend` | Pull image OSRM backend terbaru | `false` |
| `--update-frontend` | Pull image OSRM frontend terbaru | `false` |
| `--update-all` | Update semua komponen (backend, frontend, map, profil) | `false` |
| `--remove-backend` | Hapus container + config backend | `false` |
| `--remove-frontend` | Hapus container frontend | `false` |
| `--remove-profile` | Hapus profil routing | `false` |
| `--remove-map` | Hapus dataset peta | `false` |
| `--remove-all` | Hapus semua (backend, frontend, profil, peta) | `false` |
| `--no-frontend` | Jangan install OSRM frontend | `false` |
| `--no-backend` | Jangan install OSRM backend | `false` |
---
## 6. Behavior Perintah
### 🧱 6.1. INSTALL
**Contoh command:**
```bash
./install-osrm-ngpm.sh --profiles car,bike,foot,motorcycle
``` ```
────────────────────────────────────────────
🌐 Frontend Configuration Hints (for NGPM)
────────────────────────────────────────────
- Backend URL: https://route.geotency.com/osrm/v1/
- Profiles available: car, motorcycle
- Example endpoint: /osrm/v1/car/...
────────────────────────────────────────────
```
✅ Mendukung argument: **Proses:**
1. Validasi dependency.
2. Buat direktori kerja jika belum ada.
3. Download profil `.lua` jika belum ada di `--profile-dir`.
4. Cek file `.osm.pbf`:
* `--domain` untuk menentukan domain base URL (misal `https://route.geotency.com`) * Jika `--map-source` lokal dan file ada → skip download.
* `--backend-path` untuk menentukan path (misal `/osrm/v1`) * Jika URL dan file belum ada → download otomatis.
5. Generate docker-compose absolute config.
6. Jalankan container backend & frontend (kecuali `--no-backend` / `--no-frontend`).
7. Tampilkan hasil:
```
Backend (car): http://localhost:5000/osrm/v1/car
Frontend: http://localhost:9966
```
--- ---
### 🧠 **Argumen Baru / Diperbaiki** ### 🔄 6.2. UPDATE
| Flag | Deskripsi | Default | #### a. Backend only
| --------------------- | ------------------------------------- | ------------------------------------------------------------ |
| `--profiles` | Daftar profil yang diinstall | `car` | ```bash
| `--profile-dir` | Direktori profil Lua | `~/data/docker-data/osrm/profiles` | ./install-osrm-ngpm.sh --update-backend
| `--map-dir` | Direktori file `.osm.pbf` | `~/data/docker-data/osrm/dataset` | ```
| `--map-source` | URL sumber peta untuk update otomatis | `http://download.geofabrik.de/asia/indonesia-latest.osm.pbf` |
| `--backend-version` | Versi OSRM backend | `latest` | * Pull image backend terbaru.
| `--frontend-version` | Versi OSRM frontend | `latest` | * Recreate container backend.
| `--backend-registry` | Registry backend | `ghcr.io/project-osrm/osrm-backend` | * Tidak memodifikasi dataset/profil.
| `--frontend-registry` | Registry frontend | `ghcr.io/project-osrm/osrm-frontend` |
| `--backend-port` | Port backend utama | `5000` | #### b. Frontend only
| `--frontend-port` | Port frontend (opsional) | `8080` |
| `--network` | Docker network | `ngpm-network` | ```bash
| `--work-dir` | Direktori kerja utama | `~/data/docker-data/osrm` | ./install-osrm-ngpm.sh --update-frontend
| `--with-frontend` | Ikut deploy OSRM frontend | `false` | ```
| `--domain` | Domain base (untuk hint output) | *kosong* |
| `--backend-path` | Base path API | `/osrm/v1` | * Pull image frontend terbaru.
| `--dry-run` | Simulasi tanpa eksekusi | `false` | * Recreate container frontend.
| `--update` | Update image, peta, dan profil | `false` | * Keep backend as-is.
| `--print-config` | Menampilkan konfigurasi saja | `false` |
#### c. Map
```bash
./install-osrm-ngpm.sh --update-map
```
* Download ulang `.osm.pbf` dari `--map-source`.
* Jalankan ulang proses extract + partition + customize.
* Restart backend container.
#### d. Profile
```bash
./install-osrm-ngpm.sh --update-profile
```
* Download ulang profil `.lua` default OSRM.
* Rebuild data `.osrm*` untuk tiap profil.
* Restart backend container.
#### e. All
```bash
./install-osrm-ngpm.sh --update-all
```
* Menjalankan semua proses update di atas secara berurutan.
--- ---
### 🪄 **Auto-Fix for Common Issues** ### 🧹 6.3. REMOVE
* Fix permission issue saat preprocess (pakai tmp writable volume) #### a. Remove backend
* Patch Lua `arg` nil
* Skip rebuild jika hasil `.osrm` sudah valid ```bash
* Auto-recreate Docker network jika belum ada ./install-osrm-ngpm.sh --remove-backend
```
* Stop & remove container backend.
* Backup config → `backup/`.
* Hapus file compose section backend.
#### b. Remove frontend
```bash
./install-osrm-ngpm.sh --remove-frontend
```
* Stop & remove container frontend.
* Backup config frontend.
#### c. Remove profile
```bash
./install-osrm-ngpm.sh --remove-profile --profiles car,bike
```
* Hapus profil `.lua` dan hasil preprocessing `.osrm*`.
#### d. Remove map
```bash
./install-osrm-ngpm.sh --remove-map
```
* Hapus file `.osm.pbf` dari dataset.
#### e. Remove all
```bash
./install-osrm-ngpm.sh --remove-all
```
* Jalankan semua remove di atas (backend, frontend, profil, map).
* Bersihkan network jika kosong.
--- ---
### 🧾 **Output Akhir** ## 7. Workflow Diagram (Mermaid)
```mermaid
flowchart TD
A[Start Script] --> B{Arguments Provided?}
B -->|--remove-*| R[Branch REMOVE Flow]
B -->|--update-*| U[Branch UPDATE Flow]
B -->|Default| I[Branch INSTALL Flow]
%% INSTALL
I --> I1[Check Dependencies]
I1 --> I2[Create directories (dataset, profiles, logs)]
I2 --> I3[Validate map-source]
I3 -->|URL| I4[Download if missing]
I3 -->|Local path| I5[Check file exists]
I4 --> I6[Extract, Partition, Customize]
I5 --> I6
I6 --> I7[Generate docker-compose.yml (absolute path)]
I7 --> I8{no-backend / no-frontend?}
I8 -->|Install Backend| I9[Deploy backend container]
I8 -->|Install Frontend| I10[Deploy frontend container]
I9 --> I11[Show backend URL]
I10 --> I12[Show frontend URL]
I11 --> Z[End]
I12 --> Z
%% UPDATE
U --> U1{Type of Update?}
U1 -->|Backend| UB[Pull backend image, recreate container]
U1 -->|Frontend| UF[Pull frontend image, recreate container]
U1 -->|Map| UM[Download .osm.pbf, rebuild, restart backend]
U1 -->|Profile| UP[Update .lua, rebuild, restart backend]
U1 -->|All| UA[Run all updates sequentially]
UA --> Z
UB --> Z
UF --> Z
UM --> Z
UP --> Z
%% REMOVE
R --> R1{Type of Remove?}
R1 -->|Backend| RB[Stop & remove backend]
R1 -->|Frontend| RF[Stop & remove frontend]
R1 -->|Profile| RP[Delete .lua & .osrm*]
R1 -->|Map| RM[Delete .osm.pbf]
R1 -->|All| RA[Run all removals sequentially]
RB --> Z
RF --> Z
RP --> Z
RM --> Z
RA --> Z
``` ```
2025-10-15 10:21:45 [SUCCESS] OSRM backend deployed for: car, motorcycle
2025-10-15 10:21:45 [SUCCESS] Docker Compose file: /home/sysadmin/data/docker-data/osrm/docker-compose.yml ---
──────────────────────────────────────────── ## 8. Logging
🌐 Frontend Configuration Hints (for NGPM)
──────────────────────────────────────────── * Semua aktivitas disimpan di `~/data/docker-data/osrm/logs/osrm-installer-YYYYMMDD.log`.
Backend: https://route.geotency.com/osrm/v1/ * Kesalahan fatal juga ditulis di file log.
Profiles: car, motorcycle * Setiap proses major (install/update/remove) akan mencatat timestamp mulai dan selesai.
Network: ngpm-network
──────────────────────────────────────────── ---
💡 Test endpoint:
curl "https://route.geotency.com/osrm/v1/car/106.8,-6.2;106.9,-6.3" ## 9. Output Akhir
────────────────────────────────────────────
Setelah sukses dijalankan:
```bash
✅ OSRM installation complete!
Backend URLs:
- car: http://localhost:5000/osrm/v1/car
- bike: http://localhost:5000/osrm/v1/bike
- foot: http://localhost:5000/osrm/v1/foot
- motorcycle: http://localhost:5000/osrm/v1/motorcycle
Frontend URL:
→ http://localhost:9966
Logs stored at: ~/data/docker-data/osrm/logs/osrm-installer-20251015.log
``` ```
---
## 10. Konfirmasi Sebelum Implementasi
Mohon konfirmasi hal berikut:
1. Apakah semua flag dan behavior sudah sesuai (termasuk pemisahan `update-*` & `remove-*`)?
2. Apakah format URL backend `http://{backend-domain}:{backend-port}/osrm/{api-version}/{profile}` sudah final?
3. Apakah lokasi default (`~/data/docker-data/osrm`) boleh di-hardcode dalam skrip (dapat diubah via `--work-dir`)?
4. Apakah backup otomatis (zip sebelum override) tetap diaktifkan?
5. Apakah kamu ingin output tambahan berupa file `config-summary.json` untuk dokumentasi hasil instalasi?
Setelah kamu konfirmasi, saya akan lanjut ke tahap **penulisan bashscript implementasi final** sesuai spesifikasi ini.
#!/usr/bin/env bash #!/usr/bin/env bash
# install-osrm-ngpm.sh v4.6 #
# Production-grade OSRM backend + frontend installer & manager # install-osrm-ngpm.sh
# - kebab-case CLI, NGPM used for Nginx Proxy Manager # OSRM + NGPM installer & manager (Rocky Linux 10)
# - sequential preprocessing, patched lua profiles, safe mounts # Version: v1.0 (final draft based on spec v3.3)
# - print-config, ngpm/frontend hints (no auto-edit frontend) #
# Features:
# - single-file, self-contained, idempotent installer/manager
# - absolute paths for docker-compose mounts
# - supports install / update / remove granular modes
# - flags use kebab-case
# - auto-install dependencies on Rocky Linux when possible
# - logs saved to ~/data/docker-data/osrm/logs/
# - supports --map-source (URL or local path). default checks dataset/indonesia-latest.osm.pbf
# - motorcycle profile patched from car.lua (maxspeed=100km/h, u-turn penalty=60s, avoid tolls)
# - default backend port 5000 (host+container), frontend port 9966
# - default backend URL format: http://{backend-domain}:{backend-port}/osrm/{api-version}/{profile}
# - interactive CLI prompts (y/N), supports --yes for non-interactive
#
set -euo pipefail set -euo pipefail
IFS=$'\n\t' IFS=$'\n\t'
# ------------------------- # -------------------------
# Defaults # DEFAULTS
# ------------------------- # -------------------------
DEFAULT_WORK_DIR="${HOME}/data/docker-data/osrm" DEFAULT_WORK_DIR="${HOME}/data/docker-data/osrm"
DEFAULT_MAP_DIR="${DEFAULT_WORK_DIR}/dataset" DEFAULT_MAP_DIR="${DEFAULT_WORK_DIR}/dataset"
DEFAULT_PROFILE_DIR="${DEFAULT_WORK_DIR}/profiles" DEFAULT_PROFILE_DIR="${DEFAULT_WORK_DIR}/profiles"
DEFAULT_PATCHED_DIR="${DEFAULT_WORK_DIR}/patched-profiles" DEFAULT_PATCHED_DIR="${DEFAULT_WORK_DIR}/patched-profiles"
DEFAULT_BACKUP_DIR="${DEFAULT_WORK_DIR}/backup"
DEFAULT_LOG_DIR="${DEFAULT_WORK_DIR}/logs"
DEFAULT_BACKEND_REGISTRY="ghcr.io/project-osrm/osrm-backend" DEFAULT_BACKEND_REGISTRY="ghcr.io/project-osrm/osrm-backend"
DEFAULT_FRONTEND_REGISTRY="ghcr.io/project-osrm/osrm-frontend" DEFAULT_FRONTEND_REGISTRY="ghcr.io/project-osrm/osrm-frontend"
DEFAULT_BACKEND_VERSION="latest" DEFAULT_BACKEND_VERSION="latest"
...@@ -22,99 +38,150 @@ DEFAULT_MAP_SOURCE="https://download.geofabrik.de/asia/indonesia-latest.osm.pbf" ...@@ -22,99 +38,150 @@ DEFAULT_MAP_SOURCE="https://download.geofabrik.de/asia/indonesia-latest.osm.pbf"
DEFAULT_NETWORK="ngpm-network" DEFAULT_NETWORK="ngpm-network"
DEFAULT_PROFILES="car" DEFAULT_PROFILES="car"
DEFAULT_BACKEND_PORT=5000 DEFAULT_BACKEND_PORT=5000
DEFAULT_FRONTEND_PORT=8080 ya DEFAULT_FRONTEND_PORT=9966
DEFAULT_DOMAIN="" DEFAULT_BACKEND_DOMAIN="localhost"
DEFAULT_PATH_PREFIX="/osrm" DEFAULT_FRONTEND_DOMAIN="localhost"
DEFAULT_BACKEND_PATH="/osrm"
LOGFILE="${DEFAULT_WORK_DIR}/osrm-install.log" DEFAULT_API_VERSION="v1"
NGPM_HINTS="${DEFAULT_WORK_DIR}/ngpm-config.txt" DEFAULT_NO_FRONTEND=false
DEFAULT_NO_BACKEND=false
# -------------------------
# Runtime variables (CLI overrides) # -------------------------
# ------------------------- # RUN-TIME VARIABLES (overridden by CLI)
work_dir="${DEFAULT_WORK_DIR}" # -------------------------
map_dir="${DEFAULT_MAP_DIR}" work-dir="${DEFAULT_WORK_DIR}"
profile_dir="${DEFAULT_PROFILE_DIR}" map-dir="${DEFAULT_MAP_DIR}"
patched_dir="${DEFAULT_PATCHED_DIR}" profile-dir="${DEFAULT_PROFILE_DIR}"
backend_registry="${DEFAULT_BACKEND_REGISTRY}" patched-dir="${DEFAULT_PATCHED_DIR}"
frontend_registry="${DEFAULT_FRONTEND_REGISTRY}" backup-dir="${DEFAULT_BACKUP_DIR}"
backend_version="${DEFAULT_BACKEND_VERSION}" log-dir="${DEFAULT_LOG_DIR}"
frontend_version="${DEFAULT_FRONTEND_VERSION}"
map_source="${DEFAULT_MAP_SOURCE}" backend-registry="${DEFAULT_BACKEND_REGISTRY}"
network_name="${DEFAULT_NETWORK}" frontend-registry="${DEFAULT_FRONTEND_REGISTRY}"
profiles_csv="${DEFAULT_PROFILES}" backend-version="${DEFAULT_BACKEND_VERSION}"
backend_port="${DEFAULT_BACKEND_PORT}" frontend-version="${DEFAULT_FRONTEND_VERSION}"
frontend_port="${DEFAULT_FRONTEND_PORT}" map-source="${DEFAULT_MAP_SOURCE}"
with_frontend=false network-name="${DEFAULT_NETWORK}"
dry_run=false profiles-csv="${DEFAULT_PROFILES}"
domain="${DEFAULT_DOMAIN}" backend-port="${DEFAULT_BACKEND_PORT}"
path_prefix="${DEFAULT_PATH_PREFIX}" frontend-port="${DEFAULT_FRONTEND_PORT}"
map_arg="" # user-provided --map (path or url) backend-domain="${DEFAULT_BACKEND_DOMAIN}"
print_config=false frontend-domain="${DEFAULT_FRONTEND_DOMAIN}"
backend-path="${DEFAULT_BACKEND_PATH}"
# update flags (when using 'update' command) api-version="${DEFAULT_API_VERSION}"
update_map=false no-frontend="${DEFAULT_NO_FRONTEND}"
update_profiles=false no-backend="${DEFAULT_NO_BACKEND}"
update_images=false
dry-run=false
# command: install | update | remove | status | print-config print-config=false
command_name="" yes-all=false
# update/remove flags (granular)
do-update-backend=false
do-update-frontend=false
do-update-map=false
do-update-profile=false
do-update-all=false
do-remove-backend=false
do-remove-frontend=false
do-remove-profile=false
do-remove-map=false
do-remove-all=false
do-purge=false
map-arg="" # explicit --map-source given (URL or path) override var map-source
# derived
COMPOSE_FILE=""
LOGFILE=""
NGPM_HINTS=""
# ------------------------- # -------------------------
# Logging helpers # Logging helpers
# ------------------------- # -------------------------
mkdir -p "$(dirname "$LOGFILE")" 2>/dev/null || true timestamp() { date '+%Y-%m-%d %H:%M:%S'; }
logfile_init() {
mkdir -p "${log-dir}"
LOGFILE="${log-dir}/osrm-installer-$(date '+%Y%m%d_%H%M%S').log"
: > "${LOGFILE}"
}
_log() { _log() {
local lvl="$1"; shift local lvl="$1"; shift
local ts; ts="$(date '+%Y-%m-%d %H:%M:%S')" local ts; ts="$(timestamp)"
printf "%s [%s] %s\n" "$ts" "$lvl" "$*" | tee -a "$LOGFILE" printf "%s [%s] %s\n" "$ts" "$lvl" "$*" | tee -a "${LOGFILE}"
} }
info() { _log INFO "$*"; } info() { _log "INFO" "$*"; }
success() { _log SUCCESS "$*"; } success() { _log "SUCCESS" "$*"; }
warn() { _log WARNING "$*"; } warn() { _log "WARN" "$*"; }
err() { _log ERROR "$*" >&2; } err() { _log "ERROR" "$*" >&2; }
# ------------------------- # -------------------------
# Usage # Usage
# ------------------------- # -------------------------
usage() { usage() {
cat <<EOF cat <<EOF
install-osrm-ngpm.sh v4.6 install-osrm-ngpm.sh - OSRM + NGPM installer & manager (Rocky Linux 10)
Usage: Usage:
$0 <command> [options] $0 <command> [options]
Commands: Commands: (use one or combine with flags)
install Install and run OSRM backend (+ optional frontend) install Install and run OSRM backend + frontend (unless disabled)
update Update images / profiles / map (controlled with flags) update Use update flags (--update-backend, --update-frontend, --update-map, --update-profile, --update-all)
remove Stop & remove generated containers and data remove Use remove flags (--remove-backend, --remove-frontend, --remove-profile, --remove-map, --remove-all)
status Show docker containers in the NGPM network
print-config Print resolved configuration and exit
Common options (kebab-case): Common options (kebab-case):
--work-dir <dir> Working directory (default: ${DEFAULT_WORK_DIR}) --work-dir <dir> Work dir (default: ${DEFAULT_WORK_DIR})
--map <file|url> Map file path or URL (overrides auto-detect) --map-source <url|path> Map URL or local file path (default: ${DEFAULT_MAP_SOURCE})
--map-dir <dir> Map directory (default: ${DEFAULT_MAP_DIR}) --map-dir <dir> Map directory (default: ${DEFAULT_MAP_DIR})
--map-source <url> Map download source (default: ${DEFAULT_MAP_SOURCE}) --profiles <csv> Profiles to enable (car,motorcycle,bike,foot). Default 'car'
--profiles <csv> Comma-separated profiles (car,motorcycle,bicycle,foot). Default 'car' --profile-dir <dir> Profile Lua dir (default: ${DEFAULT_PROFILE_DIR})
--profile-dir <dir> Profile directory for .lua (default: ${DEFAULT_PROFILE_DIR}) --backend-registry <img> Backend registry (default: ${DEFAULT_BACKEND_REGISTRY})
--with-frontend Deploy OSRM frontend (UI) --frontend-registry <img> Frontend registry (default: ${DEFAULT_FRONTEND_REGISTRY})
--backend-registry <img> Backend registry (default: ${DEFAULT_BACKEND_REGISTRY}) --backend-version <tag> Backend image tag (default: ${DEFAULT_BACKEND_VERSION})
--frontend-registry <img> Frontend registry (default: ${DEFAULT_FRONTEND_REGISTRY}) --frontend-version <tag> Frontend image tag (default: ${DEFAULT_FRONTEND_VERSION})
--backend-version <tag> Backend image tag (default: ${DEFAULT_BACKEND_VERSION}) --backend-port <port> Host start port for backend mapping (default: ${DEFAULT_BACKEND_PORT})
--frontend-version <tag> Frontend image tag (default: ${DEFAULT_FRONTEND_VERSION}) --frontend-port <port> Host port for frontend UI (default: ${DEFAULT_FRONTEND_PORT})
--backend-port <port> First host port for backend mapping (default: ${DEFAULT_BACKEND_PORT}) --network <name> Docker network name (default: ${DEFAULT_NETWORK})
--frontend-port <port> Host port for frontend UI (default: ${DEFAULT_FRONTEND_PORT}) --backend-domain <domain> Domain for backend URLs (default: ${DEFAULT_BACKEND_DOMAIN})
--network <name> Docker network name (default: ${DEFAULT_NETWORK}) --frontend-domain <domain> Domain for frontend (default: ${DEFAULT_FRONTEND_DOMAIN})
--domain <url> Domain used in hints only (optional) --backend-path <path> Base path for backend (default: ${DEFAULT_BACKEND_PATH})
--path-prefix <path> Path prefix used in hints only (default: ${DEFAULT_PATH_PREFIX}) --api-version <version> API version (default: ${DEFAULT_API_VERSION})
--update-map With 'update': update/download map --no-frontend Do NOT install frontend (frontend is installed by default)
--update-profiles With 'update': regenerate profile wrappers (won't overwrite custom) --no-backend Do NOT install backend
--update-images With 'update': pull backend/frontend images --dry-run Simulate steps
--update-all With 'update': run all updates --print-config Print resolved configuration and exit
--print-config Print resolved configuration and exit --yes Non-interactive (answer Yes to all prompts)
--dry-run Simulate actions (no changes)
Update flags:
--update-backend
--update-frontend
--update-map
--update-profile
--update-all
Remove flags:
--remove-backend
--remove-frontend
--remove-profile
--remove-map
--remove-all
--purge Use with remove to delete data (dataset, profiles, compose)
Other:
-h, --help Show this help -h, --help Show this help
Examples:
Install default:
$0 install
Install with profiles:
$0 install --profiles car,motorcycle,bike,foot
Update only map (local file):
$0 update --update-map --map-source /data/osm/indonesia-latest.osm.pbf
EOF EOF
exit 0 exit 0
} }
...@@ -123,139 +190,261 @@ EOF ...@@ -123,139 +190,261 @@ EOF
# Parse args # Parse args
# ------------------------- # -------------------------
if [ $# -eq 0 ]; then usage; fi if [ $# -eq 0 ]; then usage; fi
command_name="$1"; shift command_name="$1"; shift || true
while (( "$#" )); do while (( "$#" )); do
case "$1" in case "$1" in
--work-dir) work_dir="${2/#\~/$HOME}"; shift 2 ;; --work-dir) work-dir="${2/#\~/$HOME}"; shift 2 ;;
--map) map_arg="${2/#\~/$HOME}"; shift 2 ;; --map-source) map-source="${2}"; map-arg="${2}"; shift 2 ;;
--map-dir) map_dir="${2/#\~/$HOME}"; shift 2 ;; --map-dir) map-dir="${2/#\~/$HOME}"; shift 2 ;;
--map-source) map_source="$2"; shift 2 ;; --profiles) profiles-csv="${2}"; shift 2 ;;
--profiles) profiles_csv="$2"; shift 2 ;; --profile-dir) profile-dir="${2/#\~/$HOME}"; shift 2 ;;
--profile-dir) profile_dir="${2/#\~/$HOME}"; shift 2 ;; --backend-registry) backend-registry="$2"; shift 2 ;;
--with-frontend) with_frontend=true; shift ;; --frontend-registry) frontend-registry="$2"; shift 2 ;;
--backend-registry) backend_registry="$2"; shift 2 ;; --backend-version) backend-version="$2"; shift 2 ;;
--frontend-registry) frontend_registry="$2"; shift 2 ;; --frontend-version) frontend-version="$2"; shift 2 ;;
--backend-version) backend_version="$2"; shift 2 ;; --backend-port) backend-port="$2"; shift 2 ;;
--frontend-version) frontend_version="$2"; shift 2 ;; --frontend-port) frontend-port="$2"; shift 2 ;;
--backend-port) backend_port="$2"; shift 2 ;; --network) network-name="$2"; shift 2 ;;
--frontend-port) frontend_port="$2"; shift 2 ;; --backend-domain) backend-domain="$2"; shift 2 ;;
--network) network_name="$2"; shift 2 ;; --frontend-domain) frontend-domain="$2"; shift 2 ;;
--domain) domain="$2"; shift 2 ;; --backend-path) backend-path="$2"; shift 2 ;;
--path-prefix) path_prefix="$2"; shift 2 ;; --api-version) api-version="$2"; shift 2 ;;
--dry-run) dry_run=true; shift ;; --no-frontend) no-frontend=true; shift ;;
--update-map) update_map=true; shift ;; --no-backend) no-backend=true; shift ;;
--update-profiles) update_profiles=true; shift ;; --dry-run) dry-run=true; shift ;;
--update-images) update_images=true; shift ;; --print-config) print-config=true; shift ;;
--update-all) update_map=true; update_profiles=true; update_images=true; shift ;; --yes) yes-all=true; shift ;;
--print-config) print_config=true; shift ;; --update-backend) do-update-backend=true; shift ;;
--update-frontend) do-update-frontend=true; shift ;;
--update-map) do-update-map=true; shift ;;
--update-profile) do-update-profile=true; shift ;;
--update-all) do-update-all=true; shift ;;
--remove-backend) do-remove-backend=true; shift ;;
--remove-frontend) do-remove-frontend=true; shift ;;
--remove-profile) do-remove-profile=true; shift ;;
--remove-map) do-remove-map=true; shift ;;
--remove-all) do-remove-all=true; shift ;;
--purge) do-purge=true; shift ;;
-h|--help) usage ;; -h|--help) usage ;;
*) warn "Unknown option: $1"; shift ;; *) warn "Unknown option: $1"; shift ;;
esac esac
done done
# normalize booleans # normalize
with_frontend=${with_frontend:-false} work-dir="${work-dir/#\~/$HOME}"
dry_run=${dry_run:-false} map-dir="${map-dir/#\~/$HOME}"
print_config=${print_config:-false} profile-dir="${profile-dir/#\~/$HOME}"
update_map=${update_map:-false} patched-dir="${patched-dir/#\~/$HOME}"
update_profiles=${update_profiles:-false} backup-dir="${backup-dir/#\~/$HOME}"
update_images=${update_images:-false} log-dir="${log-dir/#\~/$HOME}"
# normalize paths mkdir -p "${work-dir}" "${map-dir}" "${profile-dir}" "${patched-dir}" "${backup-dir}" "${log-dir}"
work_dir="${work_dir/#\~/$HOME}" COMPOSE_FILE="${work-dir}/docker-compose.yml"
map_dir="${map_dir/#\~/$HOME}" NGPM_HINTS="${work-dir}/ngpm-config.txt"
profile_dir="${profile_dir/#\~/$HOME}"
patched_dir="${patched_dir/#\~/$HOME}" logfile_init
mkdir -p "$work_dir" "$map_dir" "$profile_dir" "$patched_dir"
COMPOSE_FILE="${work_dir}/docker-compose.yml"
LOGFILE="${work_dir}/osrm-install.log"
NGPM_HINTS="${work_dir}/ngpm-config.txt"
# parse profiles csv -> array # parse profiles csv -> array
IFS=',' read -r -a PROFILE_ARR <<< "$(echo "${profiles_csv}" | tr -d '[:space:]')" IFS=',' read -r -a PROFILE_ARR <<< "$(echo "${profiles-csv}" | tr -d '[:space:]')"
if [ "${#PROFILE_ARR[@]}" -eq 0 ]; then PROFILE_ARR=(car); fi if [ "${#PROFILE_ARR[@]}" -eq 0 ]; then PROFILE_ARR=(car); fi
BACKEND_IMAGE="${backend_registry}:${backend_version}" BACKEND_IMAGE="${backend-registry}:${backend-version}"
FRONTEND_IMAGE="${frontend_registry}:${frontend_version}" FRONTEND_IMAGE="${frontend-registry}:${frontend-version}"
info "Command: ${command_name}" info "Command: ${command_name}"
info "Work dir: ${work_dir}" info "Work dir: ${work-dir}"
info "Map dir: ${map_dir}" info "Map dir: ${map-dir}"
info "Profile dir: ${profile_dir}" info "Profile dir: ${profile-dir}"
info "Patched profiles dir: ${patched_dir}" info "Patched profiles dir: ${patched-dir}"
info "Network: ${network_name}" info "Backup dir: ${backup-dir}"
info "Log dir: ${log-dir}"
info "Network: ${network-name}"
info "Backend image: ${BACKEND_IMAGE}" info "Backend image: ${BACKEND_IMAGE}"
info "Frontend image: ${FRONTEND_IMAGE}" info "Frontend image: ${FRONTEND_IMAGE}"
info "Profiles: ${PROFILE_ARR[*]}" info "Profiles: ${PROFILE_ARR[*]}"
info "With frontend: ${with_frontend}" info "No frontend: ${no-frontend}"
info "Dry run: ${dry_run}" info "No backend: ${no-backend}"
[ -n "$domain" ] && info "Domain (hints only): ${domain}" info "Dry run: ${dry-run}"
info "Path prefix (hints only): ${path_prefix}" info "Backend domain: ${backend-domain}"
info "Frontend domain: ${frontend-domain}"
info "Backend path: ${backend-path}"
info "API version: ${api-version}"
[ -n "${map-arg}" ] && info "Map source override: ${map-arg}"
# ------------------------- # -------------------------
# Preconditions # Preconditions & helpers
# ------------------------- # -------------------------
command -v docker >/dev/null 2>&1 || { err "docker not found. Install Docker."; exit 1; } command_exists() { command -v "$1" >/dev/null 2>&1; }
if command -v curl >/dev/null 2>&1; then downloader="curl"; else downloader="wget"; fi
# ------------------------- ensure_package() {
# Helpers local pkg="$1"
# ------------------------- if command_exists "$pkg"; then
download_file() { info "Dependency found: ${pkg}"
local url="$1"; local dest="$2"
info "Downloading ${url} -> ${dest}"
if [ "$dry_run" = "true" ]; then
warn "[DRY RUN] Would download ${url} to ${dest}"
return 0 return 0
fi fi
mkdir -p "$(dirname "$dest")" info "Dependency missing: ${pkg}"
if [ "$downloader" = "curl" ]; then if [ "$(id -u)" -ne 0 ]; then
curl -L --fail -o "${dest}.tmp" "$url" && mv "${dest}.tmp" "$dest" warn "Cannot auto-install ${pkg} because script not running as root. Please run: sudo dnf install -y ${pkg}"
return 1
fi
info "Attempting to install ${pkg} via dnf..."
if dnf install -y "${pkg}"; then
success "Installed ${pkg}"
return 0
else else
wget -O "${dest}.tmp" "$url" && mv "${dest}.tmp" "$dest" warn "Failed to auto-install ${pkg}. Please install manually."
return 1
fi fi
} }
safe_copy_map() { prereqs_check() {
local src="$1"; local dest="$2" local ok=0
if [ "$dry_run" = "true" ]; then # docker must be present but we can't auto-configure daemon here; just warn
warn "[DRY RUN] Would copy ${src} -> ${dest} (if needed)" if ! command_exists docker; then
warn "docker not found. Please install docker and ensure docker daemon is running."
ok=1
fi
# docker compose plugin
if ! docker compose version >/dev/null 2>&1; then
warn "docker compose plugin not found. Attempting to install docker-compose-plugin via dnf (requires root)."
if ! ensure_package docker-compose-plugin; then
warn "docker compose plugin not available. Please install docker compose manually."
ok=1
fi
fi
# other utils
for util in curl jq tar awk sed wget; do
if ! command_exists "$util"; then
info "Utility ${util} missing; attempting to install"
if ! ensure_package "$util"; then
warn "Please install ${util} manually."
ok=1
fi
fi
done
return ${ok}
}
# SELinux label detection for mounts
SELINUX_LABEL=""
if command_exists sestatus && sestatus 2>/dev/null | grep -qi enabled; then
SELINUX_LABEL=":z"
info "SELinux is enabled. Will append :z to bind mounts."
fi
# interactive prompt helper
ask_confirm() {
local prompt="$1"; shift
if [ "${yes-all}" = true ]; then
info "[auto-yes] ${prompt} -> yes"
return 0 return 0
fi fi
mkdir -p "$(dirname "$dest")" while true; do
if [ -e "$dest" ]; then printf "%s [y/N]: " "${prompt}"
if [ "$(realpath "$src")" = "$(realpath "$dest")" ]; then read -r yn
info "Map already at target location; skipping copy." case "${yn}" in
[Yy]*) return 0 ;;
[Nn]*|"") return 1 ;;
*) echo "Please answer y or n." ;;
esac
done
}
# -------------------------
# Map selection logic
# - accepts URL or local path
# - default file: ${map-dir}/indonesia-latest.osm.pbf
# -------------------------
select_map() {
info "Selecting map..."
# if user provided local path (map-arg) and it exists -> use it
if [ -n "${map-arg}" ]; then
if [[ "${map-arg}" =~ ^https?:// ]]; then
MAP_PATH="${map-dir}/$(basename "${map-arg}")"
download_map_if_needed "${map-arg}" "${MAP_PATH}"
return 0 return 0
fi fi
if [ "$(stat -c%s "$src")" = "$(stat -c%s "$dest")" ] && [ "$(sha256sum "$src" | awk '{print $1}')" = "$(sha256sum "$dest" | awk '{print $1}')" ]; then # local path provided
info "Map identical to target (checksum); skipping copy." if [ -f "${map-arg}" ]; then
MAP_PATH="$(realpath "${map-arg}")"
info "Using user-provided local map: ${MAP_PATH}"
return 0 return 0
fi fi
err "Provided --map-source file not found: ${map-arg}"
exit 1
fi
# check default filename in dataset dir
local default_file="${map-dir}/indonesia-latest.osm.pbf"
if [ -f "${default_file}" ]; then
MAP_PATH="${default_file}"
info "Found existing default map: ${MAP_PATH}"
return 0
fi
# otherwise, if map-source is URL -> download
if [[ "${map-source}" =~ ^https?:// ]]; then
MAP_PATH="${map-dir}/$(basename "${map-source}")"
download_map_if_needed "${map-source}" "${MAP_PATH}"
return 0
fi
# if map-source is local file path
if [ -f "${map-source}" ]; then
MAP_PATH="$(realpath "${map-source}")"
info "Using map-source path: ${MAP_PATH}"
return 0
fi fi
info "Copying map ${src} -> ${dest}"
cp -f "$src" "$dest" # else attempt to download from default URL
MAP_PATH="${map-dir}/$(basename "${DEFAULT_MAP_SOURCE}")"
download_map_if_needed "${DEFAULT_MAP_SOURCE}" "${MAP_PATH}"
}
download_map_if_needed() {
local url="$1"; local dest="$2"
info "Preparing map: ${url} -> ${dest}"
if [ -f "${dest}" ]; then
info "Map already exists at ${dest}. Skipping download."
return 0
fi
if [ "${dry-run}" = true ]; then
warn "[DRY RUN] Would download ${url} to ${dest}"
return 0
fi
mkdir -p "$(dirname "${dest}")"
if command_exists curl; then
info "Downloading with curl..."
curl -L --fail -o "${dest}.tmp" "${url}" && mv "${dest}.tmp" "${dest}"
elif command_exists wget; then
info "Downloading with wget..."
wget -O "${dest}.tmp" "${url}" && mv "${dest}.tmp" "${dest}"
else
err "No download client available (curl or wget). Cannot download map."
exit 1
fi
success "Map downloaded: ${dest}"
} }
# ------------------------- # -------------------------
# Profile wrappers & patching # Profile wrappers & patched profiles
# - generate default wrappers if missing (do not overwrite custom) # - ensure default wrappers present
# - create patched copy that ensures `arg[0]` fallback to profile name # - create patched copy with arg fallback and motorcycle modifications
# ------------------------- # -------------------------
generate_profile_wrappers() { generate_profile_wrappers() {
info "Generating profile wrappers into ${profile_dir} (only when missing)" info "Generating profile wrappers in ${profile-dir} (if missing)"
mkdir -p "$profile_dir" mkdir -p "${profile-dir}"
for profile in "${PROFILE_ARR[@]}"; do for profile in "${PROFILE_ARR[@]}"; do
local pf="${profile_dir}/${profile}.lua" local pf="${profile-dir}/${profile}.lua"
if [ -f "$pf" ]; then if [ -f "${pf}" ]; then
info "Custom profile exists: ${pf} (will use it)" info "Profile exists: ${pf}"
continue continue
fi fi
case "$profile" in case "${profile}" in
car|bicycle|foot) car|bicycle|bike|foot)
cat > "$pf" <<'LUA' info "Generating wrapper for ${profile} from builtin"
cat > "${pf}" <<'LUA'
-- wrapper: attempt to load builtin profile -- wrapper: attempt to load builtin profile
local function load_builtin(name) local function load_builtin(name)
local cands = {"/opt/profiles/"..name..".lua", "/opt/"..name..".lua"} local cands = {"/opt/profiles/"..name..".lua", "/opt/"..name..".lua"}
...@@ -265,19 +454,19 @@ local function load_builtin(name) ...@@ -265,19 +454,19 @@ local function load_builtin(name)
end end
error("builtin profile not found: "..name) error("builtin profile not found: "..name)
end end
-- prefer explicit name if possible
local base = load_builtin("car") local base = load_builtin("car")
if type(base) == "table" and base.setup then return base end if type(base) == "table" and base.setup then return base end
if type(base) == "table" then return base end if type(base) == "table" then return base end
if type(base) == "function" then return base() end if type(base) == "function" then return base() end
error("unexpected profile format") error("unexpected profile format")
LUA LUA
success "Generated default wrapper for ${profile}: ${pf}" success "Generated wrapper: ${pf}"
;; ;;
motorcycle) motorcycle)
# custom motorcycle per spec info "Generating motorcycle wrapper based on car"
cat > "$pf" <<'LUA' # we'll create a motorcycle wrapper that loads car and patches properties
-- custom motorcycle wrapper (derived from car) cat > "${pf}" <<'LUA'
-- motorcycle wrapper derived from car profile
local function load_builtin(name) local function load_builtin(name)
local cands = {"/opt/profiles/"..name..".lua", "/opt/"..name..".lua"} local cands = {"/opt/profiles/"..name..".lua", "/opt/"..name..".lua"}
for _,p in ipairs(cands) do for _,p in ipairs(cands) do
...@@ -298,20 +487,13 @@ local properties = base_profile.properties or {} ...@@ -298,20 +487,13 @@ local properties = base_profile.properties or {}
local speed_profile = base_profile.speed_profile or {} local speed_profile = base_profile.speed_profile or {}
local turn_penalty_profile = base_profile.turn_penalty_profile or {} local turn_penalty_profile = base_profile.turn_penalty_profile or {}
properties.max_speed = 100 properties.max_speed = properties.max_speed or 100
properties.u_turn_penalty = 60 properties.u_turn_penalty = 60
local restrictions = { -- add motorcycle-specific restrictions
['motorcycle:designated']='designated', local restrictions = base_profile.restrictions or {}
['motorcycle:yes']='permissive', -- set toll to prohibited
['motorcycle:no']='prohibited', restrictions["toll"] = "prohibited"
['motorcar:designated']='prohibited',
['motorcar:yes']='prohibited',
['motorcar:no']='permissive',
['highway:motorway']='prohibited',
['highway:trunk']='prohibited',
['toll']='prohibited'
}
local profile = { local profile = {
properties = properties, properties = properties,
...@@ -331,23 +513,22 @@ LUA ...@@ -331,23 +513,22 @@ LUA
success "Generated motorcycle wrapper: ${pf}" success "Generated motorcycle wrapper: ${pf}"
;; ;;
*) *)
warn "No generator for profile '${profile}'. Please provide ${pf}" warn "No generator for profile '${profile}'. Provide ${pf} manually."
;; ;;
esac esac
done done
} }
create_patched_profiles() { create_patched_profiles() {
info "Creating patched profiles in ${patched_dir} (safe copies with arg fallback)" info "Creating patched profiles in ${patched-dir:-${patched-dir}} (safe copies with arg fallback)"
mkdir -p "${patched_dir}" mkdir -p "${patched-dir}"
for profile in "${PROFILE_ARR[@]}"; do for profile in "${PROFILE_ARR[@]}"; do
local src="${profile_dir}/${profile}.lua" local src="${profile-dir}/${profile}.lua"
local dst="${patched_dir}/${profile}.lua" local dst="${patched-dir}/${profile}.lua"
if [ ! -f "${src}" ]; then if [ ! -f "${src}" ]; then
warn "Profile source not found: ${src} (skipping patch). Ensure wrapper exists." warn "Profile source not found: ${src} (skipping patch)"
continue continue
fi fi
# create patched file: ensure arg table exists with [0] = profile name
{ {
printf 'local arg = arg or {}\n' printf 'local arg = arg or {}\n'
printf 'arg[0] = arg[0] or "%s"\n' "${profile}" printf 'arg[0] = arg[0] or "%s"\n' "${profile}"
...@@ -360,22 +541,25 @@ create_patched_profiles() { ...@@ -360,22 +541,25 @@ create_patched_profiles() {
} }
# ------------------------- # -------------------------
# Compose generation # Docker Compose generation (absolute paths)
# - both preprocess and routed services present for each profile # - Create preprocess and routed service per profile
# - preprocess uses profile-data (rw) so preprocess can write .osrm outputs # - Preprocess mounts patched-profiles and profile-data (rw)
# - routed uses profile-data (ro) # - Routed mounts profile-data (ro)
# - host ports start at backend_port and increase per profile # - Host ports assigned starting at backend-port incremented per profile
# - Frontend service (default installed unless --no-frontend)
# ------------------------- # -------------------------
generate_docker_compose() { generate_docker_compose() {
info "Generating docker-compose.yml at ${COMPOSE_FILE}" info "Generating docker-compose.yml at ${COMPOSE_FILE}"
mkdir -p "$(dirname "$COMPOSE_FILE")" mkdir -p "$(dirname "${COMPOSE_FILE}")"
local selabel="${SELINUX_LABEL:-}"
local services="" local services=""
local idx=0 local idx=0
for profile in "${PROFILE_ARR[@]}"; do for profile in "${PROFILE_ARR[@]}"; do
idx=$((idx+1)) idx=$((idx+1))
local host_port=$((backend_port + idx - 1)) local host_port=$((backend-port + idx - 1))
mkdir -p "${work_dir}/${profile}-data" local pdata="${work-dir}/${profile}-data"
mkdir -p "${pdata}"
services+=" services+="
osrm-preprocess-${profile}: osrm-preprocess-${profile}:
image: ${BACKEND_IMAGE} image: ${BACKEND_IMAGE}
...@@ -385,10 +569,10 @@ generate_docker_compose() { ...@@ -385,10 +569,10 @@ generate_docker_compose() {
osrm-partition /data/map.osrm && osrm-partition /data/map.osrm &&
osrm-customize /data/map.osrm\" osrm-customize /data/map.osrm\"
volumes: volumes:
- ./patched-profiles:/opt/custom_profiles:ro - ${patched-dir}:/opt/custom_profiles:ro${selabel}
- ./${profile}-data:/data:rw - ${pdata}:/data:rw${selabel}
networks: networks:
- ${network_name} - ${network-name}
osrm-routed-${profile}: osrm-routed-${profile}:
image: ${BACKEND_IMAGE} image: ${BACKEND_IMAGE}
...@@ -396,372 +580,550 @@ generate_docker_compose() { ...@@ -396,372 +580,550 @@ generate_docker_compose() {
restart: unless-stopped restart: unless-stopped
command: osrm-routed --algorithm mld --max-table-size 10000 /data/map.osrm command: osrm-routed --algorithm mld --max-table-size 10000 /data/map.osrm
volumes: volumes:
- ./${profile}-data:/data:ro - ${pdata}:/data:ro${selabel}
ports: ports:
- \"${host_port}:5000\" - \"${host_port}:5000\"
networks: networks:
- ${network_name} - ${network-name}
" "
done done
local frontend_block="" local frontend_block=""
if [ "${with_frontend}" = "true" ]; then if [ "${no-frontend}" != true ]; then
# build list of backend urls for frontend environment variable
local backend_urls=""
local idx2=0
for profile in "${PROFILE_ARR[@]}"; do
idx2=$((idx2+1))
local host_port=$((backend-port + idx2 - 1))
# backend url format: {backend-domain}:{backend-port}/osrm/{api-version}/{profile}
local url="http://${backend-domain}:${host_port}${backend-path}/${api-version}/${profile}"
if [ -z "${backend_urls}" ]; then
backend_urls="${url}"
else
backend_urls="${backend_urls},${url}"
fi
done
frontend_block+=" frontend_block+="
osrm-frontend: osrm-frontend:
image: ${FRONTEND_IMAGE} image: ${FRONTEND_IMAGE}
container_name: osrm-frontend container_name: osrm-frontend
restart: unless-stopped restart: unless-stopped
environment: environment:
- DEFAULT_BACKEND_URL=http://osrm-routed-${PROFILE_ARR[0]}:5000 - DEFAULT_BACKEND_URLS=${backend-urls}
ports: ports:
- \"${frontend_port}:9966\" - \"${frontend-port}:9966\"
networks: networks:
- ${network_name} - ${network-name}
" "
fi fi
cat > "${COMPOSE_FILE}" <<EOF cat > "${COMPOSE_FILE}" <<EOF
version: '3.8'
services: services:
${services} ${services}
${frontend_block} ${frontend_block}
networks: networks:
${network_name}: ${network-name}:
external: true external: true
name: ${network_name} name: ${network-name}
EOF EOF
success "docker-compose.yml written to ${COMPOSE_FILE}" success "docker-compose.yml written to ${COMPOSE_FILE}"
} }
# ------------------------- # -------------------------
# Ensure docker network # Ensure docker network exists
# ------------------------- # -------------------------
ensure_network() { ensure_network() {
if [ "${dry_run}" = "true" ]; then if [ "${dry-run}" = true ]; then
warn "[DRY RUN] Would ensure docker network '${network_name}' exists" warn "[DRY RUN] Would ensure docker network ${network-name} exists"
return 0 return 0
fi fi
if docker network inspect "${network_name}" >/dev/null 2>&1; then if docker network inspect "${network-name}" >/dev/null 2>&1; then
info "Docker network '${network_name}' exists" info "Docker network ${network-name} exists"
else else
info "Creating docker network '${network_name}'" info "Creating docker network ${network-name}"
docker network create "${network_name}" docker network create "${network-name}"
fi
}
# -------------------------
# Map selection / prep
# -------------------------
select_map() {
if [ -n "${map_arg:-}" ]; then
if [[ "${map_arg}" =~ ^https?:// ]]; then
MAP_PATH="${map_dir}/$(basename "${map_arg}")"
download_file "${map_arg}" "${MAP_PATH}"
return 0
fi
if [ -f "${map_arg}" ]; then
MAP_PATH="${map_arg}"
info "Using user-provided map: ${MAP_PATH}"
return 0
fi
err "Provided --map not found: ${map_arg}"
exit 1
fi fi
local newest
newest="$(ls -t "${map_dir}"/*.osm.pbf 2>/dev/null | head -n1 || true)"
if [ -n "${newest}" ]; then
MAP_PATH="${newest}"
info "Found newest map in ${map_dir}: ${MAP_PATH}"
return 0
fi
MAP_PATH="${map_dir}/$(basename "${map_source}")"
info "No local map found; downloading ${map_source} -> ${MAP_PATH}"
download_file "${map_source}" "${MAP_PATH}"
} }
# ------------------------- # -------------------------
# Pull images # Pull images (interactive)
# ------------------------- # -------------------------
pull_images() { pull_images() {
if [ "${dry_run}" = "true" ]; then if [ "${dry-run}" = true ]; then
warn "[DRY RUN] Would pull images: ${BACKEND_IMAGE} ${FRONTEND_IMAGE}" warn "[DRY RUN] Would pull images: ${BACKEND_IMAGE} ${FRONTEND_IMAGE}"
return 0 return 0
fi fi
info "Pulling backend image: ${BACKEND_IMAGE}" info "Pulling images..."
docker pull "${BACKEND_IMAGE}" if [ "${do-update-backend}" = true ] || [ "${do-update-all}" = true ] || [ "${command_name}" = "install" ]; then
if [ "${with_frontend}" = "true" ]; then info "Pull backend: ${BACKEND_IMAGE}"
info "Pulling frontend image: ${FRONTEND_IMAGE}" docker pull "${BACKEND_IMAGE}" || warn "docker pull backend failed"
docker pull "${FRONTEND_IMAGE}" fi
if [ "${no-frontend}" != true ] && ( [ "${do-update-frontend}" = true ] || [ "${do-update-all}" = true ] || [ "${command_name}" = "install" ] ); then
info "Pull frontend: ${FRONTEND_IMAGE}"
docker pull "${FRONTEND_IMAGE}" || warn "docker pull frontend failed"
fi fi
} }
# ------------------------- # -------------------------
# Preprocess sequentially # Preprocess sequentially (per profile)
# ------------------------- # -------------------------
preprocess_sequential() { preprocess_sequential() {
info "Starting sequential preprocessing for profiles: ${PROFILE_ARR[*]}" info "Starting sequential preprocess for profiles: ${PROFILE_ARR[*]}"
mkdir -p "${work_dir}/dataset" mkdir -p "${work-dir}/dataset"
safe_copy_map "${MAP_PATH}" "${work_dir}/dataset/map.osm.pbf" # ensure map copied into each profile-data as map.osm.pbf
safe_copy_map "${MAP_PATH}" "${work-dir}/dataset/map.osm.pbf"
local idx=0 local idx=0
for profile in "${PROFILE_ARR[@]}"; do for profile in "${PROFILE_ARR[@]}"; do
idx=$((idx+1)) idx=$((idx+1))
info "=== Preprocessing (${idx}/${#PROFILE_ARR[@]}): ${profile} ===" info "=== Preprocessing (${idx}/${#PROFILE_ARR[@]}): ${profile} ==="
local profile_data="${work_dir}/${profile}-data" local pdata="${work-dir}/${profile}-data"
mkdir -p "${profile_data}" mkdir -p "${pdata}"
safe_copy_map "${work_dir}/dataset/map.osm.pbf" "${profile_data}/map.osm.pbf" safe_copy_map "${work-dir}/dataset/map.osm.pbf" "${pdata}/map.osm.pbf"
# run preprocess if [ "${dry-run}" = true ]; then
if [ "${dry_run}" = "true" ]; then warn "[DRY RUN] Would run: docker compose --project-directory ${work-dir} up --build osrm-preprocess-${profile}"
warn "[DRY RUN] Would run: docker compose --project-directory ${work_dir} up --build osrm-preprocess-${profile}"
warn "[DRY RUN] Would run: docker compose --project-directory ${work_dir} rm -fsv osrm-preprocess-${profile}"
success "Simulated preprocess for: ${profile}"
continue continue
fi fi
# start preprocess and capture exit # run preprocess (compose creates container)
if docker compose --project-directory "${work_dir}" up --build osrm-preprocess-"${profile}"; then if docker compose --project-directory "${work-dir}" up --build osrm-preprocess-"${profile}"; then
success "Preprocess SUCCESS for: ${profile}" success "Preprocess container exited 0 for: ${profile}"
else else
err "Preprocess FAILED for: ${profile} -- skipping routed service for this profile" err "Preprocess failed for: ${profile}. Showing logs:"
# ensure container removed docker compose --project-directory "${work-dir}" logs osrm-preprocess-"${profile}" --no-log-prefix | sed -n '1,200p' || true
docker compose --project-directory "${work_dir}" rm -fsv osrm-preprocess-"${profile}" || true docker compose --project-directory "${work-dir}" rm -fsv osrm-preprocess-"${profile}" || true
# mark: skip further action for this profile by removing its profile-data (or leaving as-is)
continue continue
fi fi
# remove preprocess container (cleanup) # cleanup container
docker compose --project-directory "${work_dir}" rm -fsv osrm-preprocess-"${profile}" || true docker compose --project-directory "${work-dir}" rm -fsv osrm-preprocess-"${profile}" || true
# brief pause
# Post-check: find map.osrm.properties or any map.osrm*
if [ -f "${pdata}/map.osrm.properties" ] || ls "${pdata}"/*.osrm* >/dev/null 2>&1; then
success "Preprocess SUCCESS for: ${profile}"
else
warn "Preprocess claimed success but no OSRM outputs found in ${pdata}"
docker compose --project-directory "${work-dir}" logs osrm-preprocess-"${profile}" --no-log-prefix | sed -n '1,200p' || true
continue
fi
sleep 1 sleep 1
done done
} }
# ------------------------- # -------------------------
# Start routed services # safe copy map helper (avoid unnecessary copies)
# -------------------------
safe_copy_map() {
local src="$1"; local dest="$2"
if [ "${dry-run}" = true ]; then
warn "[DRY RUN] Would cp ${src} -> ${dest}"
return 0
fi
mkdir -p "$(dirname "${dest}")"
if [ -e "${dest}" ] && [ -e "${src}" ]; then
if [ "$(realpath "${src}")" = "$(realpath "${dest}")" ]; then
info "Map already at target location; skipping copy"
return 0
fi
if [ "$(stat -c%s "${src}")" = "$(stat -c%s "${dest}")" ] && [ "$(sha256sum "${src}" | awk '{print $1}')" = "$(sha256sum "${dest}" | awk '{print $1}')" ]; then
info "Map identical to target; skipping copy"
return 0
fi
fi
cp -f "${src}" "${dest}"
info "Copied map to ${dest}"
}
# -------------------------
# Start routed services (idempotent checks)
# ------------------------- # -------------------------
start_routed_services() { start_routed_services() {
info "Starting routed services for profiles: ${PROFILE_ARR[*]}" info "Starting routed services for profiles: ${PROFILE_ARR[*]}"
if [ "${dry_run}" = "true" ]; then if [ "${dry-run}" = true ]; then
warn "[DRY RUN] Would start routed containers via docker compose" warn "[DRY RUN] Would start routed containers via docker compose"
return 0 return 0
fi fi
# achieve idempotent behavior: try to start only services that have .osrm present
local to_start=() local to_start=()
local idx=0 local idx=0
for profile in "${PROFILE_ARR[@]}"; do for profile in "${PROFILE_ARR[@]}"; do
idx=$((idx+1)) idx=$((idx+1))
local profile_data="${work_dir}/${profile}-data" local pdata="${work-dir}/${profile}-data"
if [ -f "${profile_data}/map.osrm" ] || ls "${profile_data}"/*.osrm 2>/dev/null | grep -q .; then if [ -f "${pdata}/map.osrm.properties" ] || ls "${pdata}"/*.osrm* >/dev/null 2>&1; then
to_start+=("osrm-routed-${profile}") to_start+=("osrm-routed-${profile}")
else else
warn "Skipping osrm-routed-${profile}: .osrm output not found in ${profile_data}" warn "Skipping osrm-routed-${profile}: no OSRM outputs found in ${pdata}"
fi fi
done done
if [ "${#to_start[@]}" -eq 0 ]; then if [ "${#to_start[@]}" -eq 0 ]; then
err "No routed services to start (no successful preprocess outputs found)." err "No routed services to start."
return 1 return 1
fi fi
docker compose --project-directory "${work_dir}" up -d "${to_start[@]}" # detect existing containers and ask for recreate if present
success "Routed services started: ${to_start[*]}" local will_start=()
for svc in "${to_start[@]}"; do
if docker ps -a --format '{{.Names}}' | grep -q "^${svc}$"; then
if ask_confirm "Container ${svc} already exists. Recreate (stop & remove & recreate)?" ; then
info "Recreating ${svc}"
docker compose --project-directory "${work-dir}" rm -fsv "${svc}" || true
will_start+=("${svc}")
else
info "Keeping existing ${svc}"
fi
else
will_start+=("${svc}")
fi
done
if [ "${#will_start[@]}" -eq 0 ]; then
info "No routed services selected to start."
return 0
fi
docker compose --project-directory "${work-dir}" up -d "${will_start[@]}"
success "Routed services started: ${will_start[*]}"
# print mapping
local idx2=0 local idx2=0
for profile in "${PROFILE_ARR[@]}"; do for profile in "${PROFILE_ARR[@]}"; do
idx2=$((idx2+1)) idx2=$((idx2+1))
local host_port=$((backend_port + idx2 - 1)) local host_port=$((backend-port + idx2 - 1))
info "Profile ${profile}: host port ${host_port} -> container osrm-routed-${profile}:5000" info "Profile ${profile}: host port ${host_port} -> container osrm-routed-${profile}:5000"
done done
} }
# ------------------------- # -------------------------
# Start frontend # Start frontend (idempotent)
# ------------------------- # -------------------------
start_frontend() { start_frontend() {
if [ "${with_frontend}" != "true" ]; then if [ "${no-frontend}" = true ]; then
info "Frontend not requested" info "Frontend installation skipped (--no-frontend)"
return 0 return 0
fi fi
if [ "${dry_run}" = "true" ]; then if [ "${dry-run}" = true ]; then
warn "[DRY RUN] Would start osrm-frontend via docker compose" warn "[DRY RUN] Would start osrm-frontend via docker compose"
return 0 return 0
fi fi
docker compose --project-directory "${work_dir}" up -d osrm-frontend if docker ps -a --format '{{.Names}}' | grep -q "^osrm-frontend$"; then
success "Frontend started (container osrm-frontend) host port ${frontend_port}" if ask_confirm "osrm-frontend container exists. Recreate?" ; then
docker compose --project-directory "${work-dir}" rm -fsv osrm-frontend || true
else
info "Keeping existing osrm-frontend"
return 0
fi
fi
docker compose --project-directory "${work-dir}" up -d osrm-frontend
success "Frontend started (container osrm-frontend) host port ${frontend-port}"
} }
# ------------------------- # -------------------------
# Generate NGPM hints (printed + saved) # Generate NGPM hints
# ------------------------- # -------------------------
generate_ngpm_hints() { generate_ngpm_hints() {
info "Generating NGPM & frontend hints at ${NGPM_HINTS}" info "Generating NGPM hints at ${NGPM_HINTS}"
{ {
echo "NGPM (Nginx Proxy Manager) configuration hints" echo "NGPM (Nginx Proxy Manager) hints"
echo "Domain (hints only): ${domain:-<your-domain>}" echo "Backend domain: ${backend-domain}"
echo "Path prefix (hints only): ${path_prefix}" echo "Frontend domain: ${frontend-domain}"
echo "Backend path: ${backend-path}/${api-version}/{profile}"
echo echo
printf "%-30s %-35s %s\n" "LOCATION PATH" "FORWARD HOSTNAME / IP" "CONTAINER PORT -> HOST" printf "%-30s %-35s %s\n" "LOCATION PATH" "FORWARD HOSTNAME / CONTAINER" "HOST PORT"
local idx=0 local idx=0
for profile in "${PROFILE_ARR[@]}"; do for profile in "${PROFILE_ARR[@]}"; do
idx=$((idx+1)) idx=$((idx+1))
local host_port=$((backend_port + idx - 1)) local host_port=$((backend-port + idx - 1))
printf "%-30s %-35s %s\n" "${path_prefix}/${profile}" "osrm-routed-${profile}" "5000 -> ${host_port}" printf "%-30s %-35s %s\n" "${backend-path}/${api-version}/${profile}" "osrm-routed-${profile}:5000" "${host_port}"
done done
if [ "${with_frontend}" = "true" ]; then if [ "${no-frontend}" != true ]; then
echo echo
echo "Frontend UI (optional):" echo "Frontend UI (container: osrm-frontend) -> host port ${frontend-port}"
echo " container: osrm-frontend" echo "Configure NGPM manually and attach it to docker network ${network-name}"
echo " host port: ${frontend_port}"
echo " In the UI you can add backend URLs (manual) such as:"
for profile in "${PROFILE_ARR[@]}"; do
echo " - http://osrm-routed-${profile}:5000"
done
fi fi
echo
echo "Sample curl test using domain (replace <host> if domain not set):"
local first_profile="${PROFILE_ARR[0]}"
echo " curl \"${domain:-http://<host>}${path_prefix}/${first_profile}/route/v1/driving/106.82,-6.17;106.83,-6.18?overview=false\""
echo
echo "Notes:"
echo "- Ensure NGPM is attached to Docker network '${network_name}'."
echo "- This script does NOT modify frontend config.js automatically. Configure frontend UI manually if needed."
} > "${NGPM_HINTS}" } > "${NGPM_HINTS}"
success "NGPM hints saved to ${NGPM_HINTS}" success "NGPM hints saved to ${NGPM_HINTS}"
info "NGPM hints (preview):" info "NGPM hints preview:"
sed -n '1,200p' "${NGPM_HINTS}" | sed -n '1,120p' sed -n '1,200p' "${NGPM_HINTS}" | sed -n '1,120p'
} }
# ------------------------- # -------------------------
# print-config # Print resolved configuration
# ------------------------- # -------------------------
print_configuration() { print_configuration() {
cat <<EOF cat <<EOF
Resolved configuration: Resolved configuration:
work-dir: ${work_dir} work-dir: ${work-dir}
map-dir: ${map_dir} map-dir: ${map-dir}
profile-dir: ${profile_dir} profile-dir: ${profile-dir}
patched-profiles-dir: ${patched_dir} patched-dir: ${patched-dir}
backend-image: ${BACKEND_IMAGE} backup-dir: ${backup-dir}
frontend-image: ${FRONTEND_IMAGE} log-dir: ${log-dir}
profiles: ${PROFILE_ARR[*]} docker-compose: ${COMPOSE_FILE}
network: ${network_name} backend-image: ${BACKEND_IMAGE}
backend-port(start): ${backend_port} frontend-image: ${FRONTEND_IMAGE}
frontend-port: ${frontend_port} profiles: ${PROFILE_ARR[*]}
with-frontend: ${with_frontend} network: ${network-name}
domain (hints only): ${domain} backend-port: ${backend-port}
path-prefix (hints only): ${path_prefix} frontend-port: ${frontend-port}
dry-run: ${dry_run} backend-domain: ${backend-domain}
frontend-domain: ${frontend-domain}
backend-path: ${backend-path}
api-version: ${api-version}
no-frontend: ${no-frontend}
no-backend: ${no-backend}
dry-run: ${dry-run}
EOF EOF
} }
# ------------------------- # -------------------------
# Update logic # Update functions
# ------------------------- # -------------------------
perform_update() { update_backend() {
info "Performing update actions" info "Updating backend..."
if [ "${update_map}" = "false" ] && [ "${update_profiles}" = "false" ] && [ "${update_images}" = "false" ]; then pull_images
update_map=true; update_profiles=true; update_images=true # recreate routed services for backend profiles
fi for profile in "${PROFILE_ARR[@]}"; do
local svc="osrm-routed-${profile}"
if docker ps -a --format '{{.Names}}' | grep -q "^${svc}$"; then
if ask_confirm "Recreate ${svc} with new backend image?" ; then
docker compose --project-directory "${work-dir}" rm -fsv "${svc}" || true
else
info "Skipping ${svc}"
continue
fi
fi
docker compose --project-directory "${work-dir}" up -d "${svc}" || warn "Failed to start ${svc}"
success "Updated ${svc}"
done
}
if [ "${update_images}" = "true" ]; then update_frontend() {
pull_images info "Updating frontend..."
pull_images
if [ "${no-frontend}" = true ]; then
info "--no-frontend set; skipping frontend update"
return 0
fi fi
if docker ps -a --format '{{.Names}}' | grep -q "^osrm-frontend$"; then
if [ "${update_profiles}" = "true" ]; then if ask_confirm "Recreate osrm-frontend with new image?" ; then
generate_profile_wrappers docker compose --project-directory "${work-dir}" rm -fsv osrm-frontend || true
create_patched_profiles else
info "Keeping existing frontend"
return 0
fi
fi fi
docker compose --project-directory "${work-dir}" up -d osrm-frontend || warn "Failed to start osrm-frontend"
success "Frontend updated"
}
if [ "${update_map}" = "true" ]; then update_map() {
local target="${map_dir}/$(basename "${map_source}")" info "Updating map dataset..."
download_file "${map_source}" "${target}" # if map-source is url -> download
info "Map updated: ${target}" if [[ "${map-source}" =~ ^https?:// ]]; then
local dest="${map-dir}/$(basename "${map-source}")"
if [ "${dry-run}" = true ]; then
warn "[DRY RUN] Would download ${map-source} -> ${dest}"
else
download_map_if_needed "${map-source}" "${dest}"
MAP_PATH="${dest}"
fi
elif [ -f "${map-source}" ]; then
MAP_PATH="$(realpath "${map-source}")"
info "Using local map: ${MAP_PATH}"
else
warn "Map source not found: ${map-source}"
return 1
fi fi
success "Update finished" # rebuild all profiles if requested
preprocess_sequential
# restart routed services
start_routed_services || true
success "Map update completed"
}
update_profile() {
info "Updating profiles..."
# re-generate wrappers and patched profiles
generate_profile_wrappers
create_patched_profiles
preprocess_sequential
start_routed_services || true
success "Profile update completed"
}
update_all() {
info "Running update-all (backend, frontend, map, profile)"
pull_images
update_map
update_profile
update_backend
update_frontend
success "All components updated"
} }
# ------------------------- # -------------------------
# Remove (clean) # Remove functions
# ------------------------- # -------------------------
perform_remove() { backup_before_remove() {
info "Removing containers and generated data (profile-data)" local bfile="${backup-dir}/backup-$(date '+%Y%m%d_%H%M%S').tar.gz"
if [ "${dry_run}" = "true" ]; then info "Creating backup ${bfile}"
warn "[DRY RUN] Would docker compose down and remove ${work_dir}/*-data" tar -czf "${bfile}" -C "${work-dir}" . || warn "Backup failed"
return 0 info "Backup saved: ${bfile}"
fi }
docker compose --project-directory "${work_dir}" down --remove-orphans || true
remove_backend() {
info "Removing backend containers..."
for profile in "${PROFILE_ARR[@]}"; do for profile in "${PROFILE_ARR[@]}"; do
rm -rf "${work_dir}/${profile}-data" || true local svc="osrm-routed-${profile}"
if docker ps -a --format '{{.Names}}' | grep -q "^${svc}$"; then
docker rm -f "${svc}" || warn "Failed to remove ${svc}"
success "Removed ${svc}"
else
info "${svc} not present"
fi
if [ "${do-purge}" = true ]; then
rm -rf "${work-dir}/${profile}-data" || warn "Failed to purge ${work-dir}/${profile}-data"
info "Purged ${work-dir}/${profile}-data"
fi
done done
# remove preprocess containers if any
docker compose --project-directory "${work-dir}" rm -fsv $(docker compose --project-directory "${work-dir}" ps --services --filter "status=exited" 2>/dev/null || true) || true
success "Backend removal completed"
}
remove_frontend() {
info "Removing frontend container..."
if docker ps -a --format '{{.Names}}' | grep -q '^osrm-frontend$'; then
docker rm -f osrm-frontend || warn "Failed to remove osrm-frontend"
success "Removed osrm-frontend"
else
info "osrm-frontend not present"
fi
}
remove_profile() {
info "Removing profiles and patched-profiles..."
rm -rf "${profile-dir}"/* "${patched-dir}"/* || warn "Failed to remove some profile files"
success "Profiles removed"
}
remove_map() {
info "Removing map dataset..."
rm -rf "${map-dir}"/* || warn "Failed to remove map files"
success "Map files removed"
}
remove_all() {
info "Removing all OSRM components..."
backup_before_remove
remove_frontend
remove_backend
remove_profile
remove_map
rm -f "${COMPOSE_FILE}" "${NGPM_HINTS}" || true rm -f "${COMPOSE_FILE}" "${NGPM_HINTS}" || true
success "Remove completed" success "All components removed"
} }
# ------------------------- # -------------------------
# Main dispatcher # Main dispatcher
# ------------------------- # -------------------------
case "${command_name}" in case "${command_name}" in
print-config)
print_configuration
exit 0
;;
status)
docker ps --filter "network=${network_name}" --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
exit 0
;;
remove)
perform_remove
exit 0
;;
update)
perform_update
exit 0
;;
install) install)
# Prepare environment
info "Install flow started" info "Install flow started"
mkdir -p "${work_dir}" "${map_dir}" "${profile_dir}" "${patched_dir}" # prereqs
BACKEND_IMAGE="${backend_registry}:${backend_version}" prereqs_check || warn "Some dependencies missing; check above warnings"
FRONTEND_IMAGE="${frontend_registry}:${frontend_version}"
# 1) map selection # map selection
select_map select_map
# 2) generate wrappers if missing # generate wrappers if missing
generate_profile_wrappers generate_profile_wrappers
# 3) create patched profiles (patched copies with arg fallback) # create patched profiles
create_patched_profiles create_patched_profiles
# 4) pull images (if requested/desired) # pull images
pull_images pull_images
# 5) write docker-compose # generate compose
generate_docker_compose generate_docker_compose
# 6) ensure network # ensure network
ensure_network ensure_network
# 7) sequential preprocess # preprocess sequential
preprocess_sequential preprocess_sequential
# 8) start routed services (only for profiles which produced .osrm) # start routed
start_routed_services start_routed_services || true
# 9) start frontend if requested # start frontend
start_frontend start_frontend
# 10) generate NGPM & frontend hints # ngpm hints
generate_ngpm_hints generate_ngpm_hints
success "Install flow completed" success "Install flow completed"
info "NGPM hints saved: ${NGPM_HINTS}" info "NGPM hints saved: ${NGPM_HINTS}"
info "Use --print-config to verify parsed configuration quickly" print_configuration
exit 0
;;
update)
info "Update flow started"
prereqs_check || warn "Dependency warnings (check logs)"
# honor granular flags if specified; if none specified, default to update-all
if [ "${do-update-backend}" = false ] && [ "${do-update-frontend}" = false ] && [ "${do-update-map}" = false ] && [ "${do-update-profile}" = false ] && [ "${do-update-all}" = false ]; then
do-update-all=true
fi
if [ "${do-update-all}" = true ]; then
update_all
else
[ "${do-update-map}" = true ] && update_map
[ "${do-update-profile}" = true ] && update_profile
[ "${do-update-backend}" = true ] && update_backend
[ "${do-update-frontend}" = true ] && update_frontend
fi
success "Update flow completed"
exit 0
;;
remove)
info "Remove flow started"
# if no flags given, prompt overall remove-all
if [ "${do-remove-backend}" = false ] && [ "${do-remove-frontend}" = false ] && [ "${do-remove-profile}" = false ] && [ "${do-remove-map}" = false ] && [ "${do-remove-all}" = false ]; then
if ask_confirm "No remove sub-flag given. Remove all OSRM components (including data)?"; then
do-remove-all=true
else
info "Remove aborted"
exit 0
fi
fi
if [ "${do-remove-all}" = true ]; then
if ask_confirm "This will remove ALL OSRM components. Proceed?"; then
remove_all
else
info "Remove-all aborted"
exit 0
fi
else
[ "${do-remove-frontend}" = true ] && remove_frontend
[ "${do-remove-backend}" = true ] && remove_backend
[ "${do-remove-profile}" = true ] && remove_profile
[ "${do-remove-map}" = true ] && remove_map
if [ "${do-purge}" = true ]; then
info "--purge provided: purging dataset/profile directories"
rm -rf "${work-dir}/"*/-data || true
fi
fi
success "Remove flow completed"
exit 0 exit 0
;; ;;
*) *)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment