Commit b4677cf5 by Bambang Adrian

fix(osrm): reconfigure motorcycle lua profile

parent ea6c4b0e
# 🧩 OSRM + NGPM Installer — Complete Technical Specification
## 1️⃣ Tujuan
Menyediakan **satu skrip bash tunggal (`install-osrm-ngpm.sh`)** yang:
* **self-contained & self-managing**
* mampu melakukan **install, update, remove**, dan **diagnostic check**
* dapat **dijalankan berulang** tanpa konflik (idempotent)
* dapat dijalankan pada **Rocky Linux 10**
* mengatur instalasi **OSRM Backend**, **OSRM Frontend**, serta (opsional) integrasi **Nginx Proxy Manager (NGPM)**
---
## 2️⃣ Lingkup Fitur
| Kategori | Fitur Utama |
| ------------------------ | ----------------------------------------------------------------------------------------- |
| **Install** | Instalasi penuh OSRM backend + frontend dengan docker-compose, termasuk semua dependency. |
| **Update** | Update backend, frontend, map, profile, atau keseluruhan sistem (`update all`). |
| **Remove** | Hapus backend, frontend, profile, atau keseluruhan sistem (`remove all`). |
| **Configurable Domains** | Dapat mengatur domain backend dan frontend via argumen CLI. |
| **Self-Check** | Deteksi otomatis komponen yang sudah ada dan meminta konfirmasi override. |
| **Profiles** | Dukungan profil default: `car`, `motorcycle`, `bike`, `foot`. |
| **Map Source** | Mendukung sumber peta dari URL Geofabrik atau local path. |
| **Logging** | Semua log tersimpan di `~/data/docker-data/osrm/logs/`. |
| **Dry-Run Mode** | Simulasi proses tanpa mengeksekusi tindakan destruktif. |
| **Network Management** | Membuat jaringan Docker eksternal bernama `ngpm-network`. |
| **SELinux Aware** | Otomatis menambahkan flag `:z` pada bind mount jika SELinux aktif. |
| **Backup Aware** | Menyimpan file backup penting di `$work_dir/backup`. |
| **Prompt Confirmations** | Menggunakan CLI konfirmasi standar `[y/N]`. |
---
## 3️⃣ Struktur Direktori Standar
| Path | Deskripsi |
| -------------------------------------------- | ---------------------------------- |
| `~/data/docker-data/osrm/` | Root direktori kerja (`$work_dir`) |
| `~/data/docker-data/osrm/dataset/` | Tempat file peta (`*.osm.pbf`) |
| `~/data/docker-data/osrm/profiles/` | Tempat profile OSRM asli |
| `~/data/docker-data/osrm/patched-profiles/` | Profile OSRM hasil patch (custom) |
| `~/data/docker-data/osrm/backup/` | Backup konfigurasi lama |
| `~/data/docker-data/osrm/logs/` | Semua log operasi |
| `~/data/docker-data/osrm/docker-compose.yml` | File Docker Compose absolut |
---
## 4️⃣ Default Konfigurasi dan Nilai
| Parameter | Default | Deskripsi |
| ------------------ | ------------------------------------------------------------- | ------------------------------------------ |
| `backend_image` | `ghcr.io/project-osrm/osrm-backend:latest` | Docker image OSRM backend |
| `frontend_image` | `ghcr.io/project-osrm/osrm-frontend:latest` | Docker image OSRM frontend |
| `network_name` | `ngpm-network` | Docker network eksternal |
| `backend_domain` | `localhost` | Domain backend |
| `frontend_domain` | `localhost` | Domain frontend |
| `backend_port` | `5000` | Port backend (host dan container) |
| `frontend_port` | `9966` | Port frontend (host dan container) |
| `backend_path` | `/osrm` | Path API OSRM |
| `api_version` | `v1` | Versi API OSRM |
| `map_url_default` | `https://download.geofabrik.de/asia/indonesia-latest.osm.pbf` | Default map source |
| `map_file_default` | `~/data/docker-data/osrm/dataset/indonesia-latest.osm.pbf` | Default map file |
| `log_dir` | `~/data/docker-data/osrm/logs/` | Folder log |
| `no_frontend` | `false` | Jika `true`, lewati instalasi frontend |
| `no_backend` | `false` | Jika `true`, lewati instalasi backend |
| `dry_run` | `false` | Jika `true`, hanya simulasi tanpa eksekusi |
---
## 5️⃣ CLI Arguments & Flags
### 📦 Installation
```bash
./install-osrm-ngpm.sh install [options]
```
#### Options:
| Flag | Deskripsi | Default |
| ---------------------------- | -------------------------------- | ----------------- |
| `--backend-domain <domain>` | Domain OSRM backend | `localhost` |
| `--frontend-domain <domain>` | Domain OSRM frontend | `localhost` |
| `--backend-port <port>` | Port backend | `5000` |
| `--frontend-port <port>` | Port frontend | `9966` |
| `--api-version <version>` | API version OSRM | `v1` |
| `--map-source <url or path>` | Sumber peta lokal atau remote | default Geofabrik |
| `--no-frontend` | Jangan install frontend | - |
| `--no-backend` | Jangan install backend | - |
| `--dry-run` | Jalankan simulasi tanpa eksekusi | - |
---
### 🔁 Update
```bash
./install-osrm-ngpm.sh update <component> [options]
```
#### Komponen yang bisa diupdate:
| Component | Deskripsi |
| ---------- | ------------------------------------------------ |
| `backend` | Tarik image backend terbaru & restart container |
| `frontend` | Tarik image frontend terbaru & restart container |
| `map` | Download ulang map atau gunakan versi lokal baru |
| `profile` | Update profil routing dan rebuild data |
| `all` | Update semua komponen di atas |
Contoh:
```bash
./install-osrm-ngpm.sh update backend
```
🧩 *Menarik image backend terbaru, stop container lama, lalu recreate dengan config lama.*
---
### ❌ Remove
```bash
./install-osrm-ngpm.sh remove <component>
```
#### Komponen yang bisa dihapus:
| Component | Deskripsi |
| ---------- | -------------------------------- |
| `backend` | Hapus hanya OSRM backend |
| `frontend` | Hapus hanya frontend |
| `profile` | Hapus profil OSRM |
| `all` | Hapus seluruh sistem OSRM & NGPM |
Contoh:
```bash
./install-osrm-ngpm.sh remove all
```
🧩 *Menghentikan seluruh container dan menghapus semua folder kerja setelah konfirmasi.*
---
## 6️⃣ OSRM Profile Behavior
| Profile | Sumber | Konfigurasi Khusus |
| -------------- | ------------ | ------------------------------------------------------ |
| **car** | default OSRM | Default kecepatan OSRM |
| **motorcycle** | custom | `max_speed=100 km/h`, `u-turn penalty=60 s`, `toll=no` |
| **bike** | default OSRM | sesuai bawaan |
| **foot** | default OSRM | sesuai bawaan |
📁 Semua profil tersimpan di:
* Original → `~/data/docker-data/osrm/profiles/`
* Patched → `~/data/docker-data/osrm/patched-profiles/`
---
## 7️⃣ URL Format (Konsisten)
* **Backend URL Pattern:**
```
{backend-domain}:{backend-port}/osrm/{api-version}/{profile-name}
```
Contoh:
```
http://localhost:5000/osrm/v1/car
```
* **Frontend URL Pattern:**
```
{frontend-domain}:{frontend-port}
```
Contoh:
```
http://localhost:9966
```
* **Domain publik** disarankan dikonfigurasi manual melalui **Nginx Proxy Manager UI**, bukan otomatis.
---
## 8️⃣ Docker Compose Specification
* Versi: `3.8`
* Services:
* `osrm-preprocess-<profile>`
* `osrm-routed-<profile>`
* `osrm-frontend`
* Semua service menggunakan **absolute path** volume binding.
* Mounts otomatis diberi flag `:z` jika SELinux aktif.
* Port mapping host ↔ container 1-to-1.
* Semua service berada pada network `ngpm-network` (dibuat otomatis jika belum ada).
---
## 9️⃣ Dependency Management
| Paket | Perlakuan |
| ----------------------------------------------------------------- | ------------------------------------------------ |
| `docker`, `docker-compose-plugin`, `wget`, `curl`, `tar`, `unzip` | Auto install jika belum ada |
| `nginx-proxy-manager` | Tidak auto-install, hanya disiapkan integrasinya |
| Paket yang butuh konfigurasi manual | Beri warning, bukan auto-config |
---
## 🔟 Logging Rules
* Semua operasi (install, update, remove) mencatat log ke:
```
~/data/docker-data/osrm/logs/osrm-ngpm-YYYYMMDD-HHMMSS.log
```
* Level: `[INFO]`, `[WARN]`, `[ERROR]`, `[SUCCESS]`
* Setiap eksekusi menulis ringkasan hasil (container dibuat, diperbarui, dihapus).
---
## 11️⃣ CLI Confirmations
* Gunakan prompt CLI biasa:
```
Container osrm-routed-car already exists. Recreate (stop & remove & recreate)? [y/N]:
```
* Default → “no”.
* Tidak menggunakan `dialog` atau `whiptail`.
---
## 12️⃣ Error Handling
* Skrip harus `exit 1` untuk kesalahan fatal (misal file peta hilang).
* Semua operasi penting (`docker`, `wget`, `cp`) dilindungi `set -euo pipefail`.
* Jika ada kesalahan pada salah satu service, tampilkan log dengan konteks.
---
## 13️⃣ SELinux Binding Rules
* Jika SELinux aktif (`getenforce == Enforcing`):
* Tambahkan `:z` ke setiap volume bind yang belum memilikinya.
* Jangan duplikasi `:z`.
* Gunakan helper function `append_selinux_flag`.
---
## 14️⃣ Kinerja dan Keamanan
* Menghindari privilege berlebih (tidak pakai `--privileged`).
* Volume path absolut agar tidak bergantung pada cwd.
* Tidak menghapus data pengguna tanpa konfirmasi eksplisit.
* Tidak mengunduh peta jika sudah ada.
---
## 15️⃣ Workflow Diagram (High-Level)
```mermaid
flowchart TD
A[Start: Run Script] --> B{Command?}
B -->|install| C[Check dependencies & folders]
B -->|update| U[Select component to update]
B -->|remove| R[Select component to remove]
B -->|help| Z[Show usage]
C --> D[Check existing docker components]
D --> E{Already exists?}
E -->|Yes| F[Ask confirmation to override]
E -->|No| G[Continue installation]
F -->|y| G
F -->|n| H[Skip affected components]
G --> I[Prepare map source (URL/local)]
I --> J[Prepare OSRM profiles & patches]
J --> K[Generate docker-compose.yml]
K --> L[Create docker network]
L --> M[Run preprocess container]
M --> N[Run routed containers]
N --> O[Run frontend container (if not --no-frontend)]
O --> P[Write logs to ~/data/docker-data/osrm/logs/]
P --> Q[Show success message]
U --> UA{backend/frontend/map/profile/all}
UA --> UB[Perform targeted update steps]
UB --> Q
R --> RA{backend/frontend/profile/all}
RA --> RB[Ask confirmation before deletion]
RB --> RC[Stop & remove containers, clean files]
RC --> Q
Z --> Q
Q --> END[Finish]
```
---
## 16️⃣ Contoh Skenario
### 🔹 Install Penuh
```bash
./install-osrm-ngpm.sh install
```
➡ Membuat semua folder, mendownload peta default Indonesia, men-setup backend & frontend.
---
### 🔹 Install dengan Map Lokal
```bash
./install-osrm-ngpm.sh install --map-source ~/Downloads/jakarta.osm.pbf
```
➡ Menggunakan file lokal tanpa download ulang.
---
### 🔹 Update Map Saja
```bash
./install-osrm-ngpm.sh update map --map-source https://download.geofabrik.de/asia/singapore-latest.osm.pbf
```
---
### 🔹 Hapus Semua
```bash
./install-osrm-ngpm.sh remove all
```
➡ Menghapus seluruh container dan folder setelah konfirmasi.
---
### 🔹 Dry Run Mode
```bash
./install-osrm-ngpm.sh install --dry-run
```
➡ Menampilkan semua aksi tanpa eksekusi aktual.
---
## 17️⃣ Non-Fungsional Requirements
* **OS:** Rocky Linux 10 (Primary), compatible with Ubuntu/Debian.
* **Scripting language:** Bash ≥ 5.0
* **Idempotent:** Dapat dijalankan berulang tanpa error duplikasi.
* **Readable code:** Semua internal variable pakai **underscore_case**.
* **No external config files.**
* **Self-contained single file.**
* **Fully portable** (tidak tergantung environment selain Docker).
---
## ✅ 18️⃣ Status
| Item | Status |
| -------------------- | ------------- |
| Specification | ✅ Finalized |
| SELinux support | ✅ Implemented |
| CLI arguments | ✅ Final |
| Logging | ✅ Defined |
| Domain/API structure | ✅ Confirmed |
| Mermaid workflow | ✅ Included |
---
Apakah kamu ingin saya jadikan **dokumen `.md` (Markdown)** dari daftar di atas, agar bisa langsung digunakan sebagai **AI-prompt atau dokumentasi teknis proyek**?
...@@ -2,26 +2,13 @@ ...@@ -2,26 +2,13 @@
# #
# install-osrm-ngpm.sh # install-osrm-ngpm.sh
# OSRM + NGPM installer & manager (Rocky Linux 10) # OSRM + NGPM installer & manager (Rocky Linux 10)
# Version: v1.0 (final draft based on spec v3.3) # Final self-contained script (v1.0.1 - Patched)
#
# 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 (internal variables use snake_case)
# ------------------------- # -------------------------
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"
...@@ -34,11 +21,11 @@ DEFAULT_BACKEND_REGISTRY="ghcr.io/project-osrm/osrm-backend" ...@@ -34,11 +21,11 @@ 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"
DEFAULT_FRONTEND_VERSION="latest" DEFAULT_FRONTEND_VERSION="latest"
DEFAULT_MAP_SOURCE="https://download.geofabrik.de/asia/indonesia-latest.osm.pbf" DEFAULT_MAP_SOURCE="https://download.geofrik.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
ya DEFAULT_FRONTEND_PORT=9966 DEFAULT_FRONTEND_PORT=9966
DEFAULT_BACKEND_DOMAIN="localhost" DEFAULT_BACKEND_DOMAIN="localhost"
DEFAULT_FRONTEND_DOMAIN="localhost" DEFAULT_FRONTEND_DOMAIN="localhost"
DEFAULT_BACKEND_PATH="/osrm" DEFAULT_BACKEND_PATH="/osrm"
...@@ -49,61 +36,62 @@ DEFAULT_NO_BACKEND=false ...@@ -49,61 +36,62 @@ DEFAULT_NO_BACKEND=false
# ------------------------- # -------------------------
# RUN-TIME VARIABLES (overridden by CLI) # RUN-TIME VARIABLES (overridden by CLI)
# ------------------------- # -------------------------
work-dir="${DEFAULT_WORK_DIR}" work_dir="${DEFAULT_WORK_DIR}"
map-dir="${DEFAULT_MAP_DIR}" map_dir="${DEFAULT_MAP_DIR}"
profile-dir="${DEFAULT_PROFILE_DIR}" profile_dir="${DEFAULT_PROFILE_DIR}"
patched-dir="${DEFAULT_PATCHED_DIR}" patched_dir="${DEFAULT_PATCHED_DIR}"
backup-dir="${DEFAULT_BACKUP_DIR}" backup_dir="${DEFAULT_BACKUP_DIR}"
log-dir="${DEFAULT_LOG_DIR}" log_dir="${DEFAULT_LOG_DIR}"
backend-registry="${DEFAULT_BACKEND_REGISTRY}" backend_registry="${DEFAULT_BACKEND_REGISTRY}"
frontend-registry="${DEFAULT_FRONTEND_REGISTRY}" frontend_registry="${DEFAULT_FRONTEND_REGISTRY}"
backend-version="${DEFAULT_BACKEND_VERSION}" backend_version="${DEFAULT_BACKEND_VERSION}"
frontend-version="${DEFAULT_FRONTEND_VERSION}" frontend_version="${DEFAULT_FRONTEND_VERSION}"
map-source="${DEFAULT_MAP_SOURCE}" map_source="${DEFAULT_MAP_SOURCE}"
network-name="${DEFAULT_NETWORK}" network_name="${DEFAULT_NETWORK}"
profiles-csv="${DEFAULT_PROFILES}" profiles_csv="${DEFAULT_PROFILES}"
backend-port="${DEFAULT_BACKEND_PORT}" backend_port="${DEFAULT_BACKEND_PORT}"
frontend-port="${DEFAULT_FRONTEND_PORT}" frontend_port="${DEFAULT_FRONTEND_PORT}"
backend-domain="${DEFAULT_BACKEND_DOMAIN}" backend_domain="${DEFAULT_BACKEND_DOMAIN}"
frontend-domain="${DEFAULT_FRONTEND_DOMAIN}" frontend_domain="${DEFAULT_FRONTEND_DOMAIN}"
backend-path="${DEFAULT_BACKEND_PATH}" backend_path="${DEFAULT_BACKEND_PATH}"
api-version="${DEFAULT_API_VERSION}" api_version="${DEFAULT_API_VERSION}"
no-frontend="${DEFAULT_NO_FRONTEND}" no_frontend="${DEFAULT_NO_FRONTEND}"
no-backend="${DEFAULT_NO_BACKEND}" no_backend="${DEFAULT_NO_BACKEND}"
dry-run=false dry_run=false
print-config=false print_config=false
yes-all=false yes_all=false
# update/remove flags (granular) # update/remove flags (granular)
do-update-backend=false do_update_backend=false
do-update-frontend=false do_update_frontend=false
do-update-map=false do_update_map=false
do-update-profile=false do_update_profile=false
do-update-all=false do_update_all=false
do-remove-backend=false do_remove_backend=false
do-remove-frontend=false do_remove_frontend=false
do-remove-profile=false do_remove_profile=false
do-remove-map=false do_remove_map=false
do-remove-all=false do_remove_all=false
do-purge=false do_purge=false
map-arg="" # explicit --map-source given (URL or path) override var map-source map_arg="" # explicit --map-source given (URL or path) override var map_source
# derived # derived
COMPOSE_FILE="" COMPOSE_FILE=""
LOGFILE="" LOGFILE=""
NGPM_HINTS="" NGPM_HINTS=""
MAP_PATH=""
# ------------------------- # -------------------------
# Logging helpers # Logging helpers
# ------------------------- # -------------------------
timestamp() { date '+%Y-%m-%d %H:%M:%S'; } timestamp() { date '+%Y-%m-%d %H:%M:%S'; }
logfile_init() { logfile_init() {
mkdir -p "${log-dir}" mkdir -p "${log_dir}"
LOGFILE="${log-dir}/osrm-installer-$(date '+%Y%m%d_%H%M%S').log" LOGFILE="${log_dir}/osrm-installer-$(date '+%Y%m%d_%H%M%S').log"
: > "${LOGFILE}" : > "${LOGFILE}"
} }
_log() { _log() {
...@@ -126,62 +114,42 @@ install-osrm-ngpm.sh - OSRM + NGPM installer & manager (Rocky Linux 10) ...@@ -126,62 +114,42 @@ install-osrm-ngpm.sh - OSRM + NGPM installer & manager (Rocky Linux 10)
Usage: Usage:
$0 <command> [options] $0 <command> [options]
Commands: (use one or combine with flags) Commands:
install Install and run OSRM backend + frontend (unless disabled) install Install and run OSRM backend + frontend (unless disabled)
update Use update flags (--update-backend, --update-frontend, --update-map, --update-profile, --update-all) update <component> Update a specific component (backend, frontend, map, profile, all)
remove Use remove flags (--remove-backend, --remove-frontend, --remove-profile, --remove-map, --remove-all) remove <component> Remove a specific component (backend, frontend, profile, all)
Common options (kebab-case): Install Options:
--work-dir <dir> Work dir (default: ${DEFAULT_WORK_DIR})
--map-source <url|path> Map URL or local file path (default: ${DEFAULT_MAP_SOURCE}) --map-source <url|path> Map URL or local file path (default: ${DEFAULT_MAP_SOURCE})
--map-dir <dir> Map directory (default: ${DEFAULT_MAP_DIR}) --profiles <csv> Profiles to enable (car,motorcycle,bike,foot). Default '${DEFAULT_PROFILES}'
--profiles <csv> Profiles to enable (car,motorcycle,bike,foot). Default 'car'
--profile-dir <dir> Profile Lua dir (default: ${DEFAULT_PROFILE_DIR})
--backend-registry <img> Backend registry (default: ${DEFAULT_BACKEND_REGISTRY})
--frontend-registry <img> Frontend registry (default: ${DEFAULT_FRONTEND_REGISTRY})
--backend-version <tag> Backend image tag (default: ${DEFAULT_BACKEND_VERSION})
--frontend-version <tag> Frontend image tag (default: ${DEFAULT_FRONTEND_VERSION})
--backend-port <port> Host start port for backend mapping (default: ${DEFAULT_BACKEND_PORT})
--frontend-port <port> Host port for frontend UI (default: ${DEFAULT_FRONTEND_PORT})
--network <name> Docker network name (default: ${DEFAULT_NETWORK})
--backend-domain <domain> Domain for backend URLs (default: ${DEFAULT_BACKEND_DOMAIN}) --backend-domain <domain> Domain for backend URLs (default: ${DEFAULT_BACKEND_DOMAIN})
--frontend-domain <domain> Domain for frontend (default: ${DEFAULT_FRONTEND_DOMAIN}) --frontend-domain <domain> Domain for frontend (default: ${DEFAULT_FRONTEND_DOMAIN})
--backend-path <path> Base path for backend (default: ${DEFAULT_BACKEND_PATH}) --backend-port <port> Host start port for backend mapping (default: ${DEFAULT_BACKEND_PORT})
--api-version <version> API version (default: ${DEFAULT_API_VERSION}) --frontend-port <port> Host port for frontend UI (default: ${DEFAULT_FRONTEND_PORT})
--no-frontend Do NOT install frontend (frontend is installed by default) --no-frontend Do NOT install frontend (frontend is installed by default)
--no-backend Do NOT install backend --no-backend Do NOT install backend
--dry-run Simulate steps --dry-run Simulate steps
--print-config Print resolved configuration and exit
--yes Non-interactive (answer Yes to all prompts) --yes Non-interactive (answer Yes to all prompts)
-h, --help Show this help
Update flags: Update Command:
--update-backend $0 update <backend|frontend|map|profile|all> [--map-source <url|path>]
--update-frontend
--update-map
--update-profile
--update-all
Remove flags: Remove Command:
--remove-backend $0 remove <backend|frontend|profile|all> [--purge]
--remove-frontend
--remove-profile
--remove-map
--remove-all
--purge Use with remove to delete data (dataset, profiles, compose)
Other:
-h, --help Show this help
Examples: Examples:
Install default: Install default:
$0 install $0 install
Install with profiles: Install with multiple profiles:
$0 install --profiles car,motorcycle,bike,foot $0 install --profiles car,motorcycle,bike,foot
Update only map (local file): Update only map (local file):
$0 update --update-map --map-source /data/osm/indonesia-latest.osm.pbf $0 update map --map-source /data/osm/indonesia-latest.osm.pbf
Remove all components and data:
$0 remove all --purge
EOF EOF
exit 0 exit 0
} }
...@@ -192,88 +160,77 @@ EOF ...@@ -192,88 +160,77 @@ EOF
if [ $# -eq 0 ]; then usage; fi if [ $# -eq 0 ]; then usage; fi
command_name="$1"; shift || true command_name="$1"; shift || true
# Sub-command for update/remove
sub_command=""
if [[ "${command_name}" == "update" || "${command_name}" == "remove" ]]; then
if [ $# -gt 0 ] && ! [[ "$1" =~ ^-- ]]; then
sub_command="$1"
shift
fi
fi
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-source) map-source="${2}"; map-arg="${2}"; 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 ;;
--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 ;; --backend-registry) backend_registry="$2"; shift 2 ;;
--frontend-registry) frontend-registry="$2"; shift 2 ;; --frontend-registry) frontend_registry="$2"; shift 2 ;;
--backend-version) backend-version="$2"; shift 2 ;; --backend-version) backend_version="$2"; shift 2 ;;
--frontend-version) frontend-version="$2"; shift 2 ;; --frontend-version) frontend_version="$2"; shift 2 ;;
--backend-port) backend-port="$2"; shift 2 ;; --backend-port) backend_port="$2"; shift 2 ;;
--frontend-port) frontend-port="$2"; shift 2 ;; --frontend-port) frontend_port="$2"; shift 2 ;;
--network) network-name="$2"; shift 2 ;; --network) network_name="$2"; shift 2 ;;
--backend-domain) backend-domain="$2"; shift 2 ;; --backend-domain) backend_domain="$2"; shift 2 ;;
--frontend-domain) frontend-domain="$2"; shift 2 ;; --frontend-domain) frontend_domain="$2"; shift 2 ;;
--backend-path) backend-path="$2"; shift 2 ;; --backend-path) backend_path="$2"; shift 2 ;;
--api-version) api-version="$2"; shift 2 ;; --api-version) api_version="$2"; shift 2 ;;
--no-frontend) no-frontend=true; shift ;; --no-frontend) no_frontend=true; shift ;;
--no-backend) no-backend=true; shift ;; --no-backend) no_backend=true; shift ;;
--dry-run) dry-run=true; shift ;; --dry-run) dry_run=true; shift ;;
--print-config) print-config=true; shift ;; --print-config) print_config=true; shift ;;
--yes) yes-all=true; shift ;; --purge) do_purge=true; shift ;;
--update-backend) do-update-backend=true; shift ;; --yes) yes_all=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 # normalize & mkdirs
work-dir="${work-dir/#\~/$HOME}" work_dir="${work_dir/#\~/$HOME}"
map-dir="${map-dir/#\~/$HOME}" map_dir="${map_dir/#\~/$HOME}"
profile-dir="${profile-dir/#\~/$HOME}" profile_dir="${profile_dir/#\~/$HOME}"
patched-dir="${patched-dir/#\~/$HOME}" patched_dir="${patched_dir/#\~/$HOME}"
backup-dir="${backup-dir/#\~/$HOME}" backup_dir="${backup_dir/#\~/$HOME}"
log-dir="${log-dir/#\~/$HOME}" log_dir="${log_dir/#\~/$HOME}"
mkdir -p "${work-dir}" "${map-dir}" "${profile-dir}" "${patched-dir}" "${backup-dir}" "${log-dir}" mkdir -p "${work_dir}" "${map_dir}" "${profile_dir}" "${patched_dir}" "${backup_dir}" "${log_dir}"
COMPOSE_FILE="${work-dir}/docker-compose.yml" COMPOSE_FILE="${work_dir}/docker-compose.yml"
NGPM_HINTS="${work-dir}/ngpm-config.txt" NGPM_HINTS="${work_dir}/ngpm-config.txt"
logfile_init logfile_init
# 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 "Log dir: ${log_dir}"
info "Profile dir: ${profile-dir}" info "Network: ${network_name}"
info "Patched profiles dir: ${patched-dir}"
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: ${profiles_csv}"
info "No frontend: ${no-frontend}" info "Dry run: ${dry_run}"
info "No backend: ${no-backend}" [ -n "${map_arg}" ] && info "Map source override: ${map_arg}"
info "Dry run: ${dry-run}"
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 & helpers # Helper functions
# ------------------------- # -------------------------
command_exists() { command -v "$1" >/dev/null 2>&1; } command_exists() { command -v "$1" >/dev/null 2>&1; }
...@@ -285,7 +242,7 @@ ensure_package() { ...@@ -285,7 +242,7 @@ ensure_package() {
fi fi
info "Dependency missing: ${pkg}" info "Dependency missing: ${pkg}"
if [ "$(id -u)" -ne 0 ]; then if [ "$(id -u)" -ne 0 ]; then
warn "Cannot auto-install ${pkg} because script not running as root. Please run: sudo dnf install -y ${pkg}" warn "Cannot auto-install ${pkg} because script is not running as root. Please run: sudo dnf install -y ${pkg}"
return 1 return 1
fi fi
info "Attempting to install ${pkg} via dnf..." info "Attempting to install ${pkg} via dnf..."
...@@ -300,21 +257,18 @@ ensure_package() { ...@@ -300,21 +257,18 @@ ensure_package() {
prereqs_check() { prereqs_check() {
local ok=0 local ok=0
# docker must be present but we can't auto-configure daemon here; just warn
if ! command_exists docker; then if ! command_exists docker; then
warn "docker not found. Please install docker and ensure docker daemon is running." err "docker not found. Please install docker and ensure the docker daemon is running."
ok=1 ok=1
fi fi
# docker compose plugin
if ! docker compose version >/dev/null 2>&1; then 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)." warn "docker compose plugin not found. Attempting to install docker-compose-plugin via dnf (requires root)."
if ! ensure_package docker-compose-plugin; then if ! ensure_package docker-compose-plugin; then
warn "docker compose plugin not available. Please install docker compose manually." err "docker compose plugin not available. Please install docker compose manually."
ok=1 ok=1
fi fi
fi fi
# other utils for util in curl wget tar awk sed; do
for util in curl jq tar awk sed wget; do
if ! command_exists "$util"; then if ! command_exists "$util"; then
info "Utility ${util} missing; attempting to install" info "Utility ${util} missing; attempting to install"
if ! ensure_package "$util"; then if ! ensure_package "$util"; then
...@@ -323,20 +277,23 @@ prereqs_check() { ...@@ -323,20 +277,23 @@ prereqs_check() {
fi fi
fi fi
done done
return ${ok} if [ ${ok} -ne 0 ]; then
err "Prerequisite checks failed. Aborting."
exit 1
fi
} }
# SELinux label detection for mounts # SELinux label detection for mounts
SELINUX_LABEL="" SELINUX_LABEL=""
if command_exists sestatus && sestatus 2>/dev/null | grep -qi enabled; then if command_exists sestatus && sestatus 2>/dev/null | grep -qi "SELinux status:.*enabled"; then
SELINUX_LABEL=":z" SELINUX_LABEL=":z"
info "SELinux is enabled. Will append :z to bind mounts." info "SELinux is enabled. Will append ':z' to bind mounts for context."
fi fi
# interactive prompt helper # prompt helper
ask_confirm() { ask_confirm() {
local prompt="$1"; shift local prompt="$1"; shift
if [ "${yes-all}" = true ]; then if [ "${yes_all}" = true ]; then
info "[auto-yes] ${prompt} -> yes" info "[auto-yes] ${prompt} -> yes"
return 0 return 0
fi fi
...@@ -353,52 +310,32 @@ ask_confirm() { ...@@ -353,52 +310,32 @@ ask_confirm() {
# ------------------------- # -------------------------
# Map selection logic # Map selection logic
# - accepts URL or local path
# - default file: ${map-dir}/indonesia-latest.osm.pbf
# ------------------------- # -------------------------
select_map() { select_map() {
info "Selecting map..." info "Selecting map..."
# if user provided local path (map-arg) and it exists -> use it local map_target_source="${map_arg:-${map_source}}"
if [ -n "${map-arg}" ]; then
if [[ "${map-arg}" =~ ^https?:// ]]; then if [[ "${map_target_source}" =~ ^https?:// ]]; then
MAP_PATH="${map-dir}/$(basename "${map-arg}")" MAP_PATH="${map_dir}/$(basename "${map_target_source}")"
download_map_if_needed "${map-arg}" "${MAP_PATH}" download_map_if_needed "${map_target_source}" "${MAP_PATH}"
return 0
fi
# local path provided
if [ -f "${map-arg}" ]; then
MAP_PATH="$(realpath "${map-arg}")"
info "Using user-provided local map: ${MAP_PATH}"
return 0
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 return 0
fi fi
# otherwise, if map-source is URL -> download if [ -f "${map_target_source}" ]; then
if [[ "${map-source}" =~ ^https?:// ]]; then MAP_PATH="$(realpath "${map_target_source}")"
MAP_PATH="${map-dir}/$(basename "${map-source}")" info "Using user-provided local map: ${MAP_PATH}"
download_map_if_needed "${map-source}" "${MAP_PATH}"
return 0 return 0
fi fi
# if map-source is local file path # Fallback to default if nothing works
if [ -f "${map-source}" ]; then local default_file="${map_dir}/$(basename "${DEFAULT_MAP_SOURCE}")"
MAP_PATH="$(realpath "${map-source}")" if [ -f "${default_file}" ]; then
info "Using map-source path: ${MAP_PATH}" MAP_PATH="${default_file}"
info "Found existing default map: ${MAP_PATH}"
return 0 return 0
fi fi
# else attempt to download from default URL MAP_PATH="${default_file}"
MAP_PATH="${map-dir}/$(basename "${DEFAULT_MAP_SOURCE}")"
download_map_if_needed "${DEFAULT_MAP_SOURCE}" "${MAP_PATH}" download_map_if_needed "${DEFAULT_MAP_SOURCE}" "${MAP_PATH}"
} }
...@@ -409,43 +346,41 @@ download_map_if_needed() { ...@@ -409,43 +346,41 @@ download_map_if_needed() {
info "Map already exists at ${dest}. Skipping download." info "Map already exists at ${dest}. Skipping download."
return 0 return 0
fi fi
if [ "${dry-run}" = true ]; then if [ "${dry_run}" = true ]; then
warn "[DRY RUN] Would download ${url} to ${dest}" warn "[DRY RUN] Would download ${url} to ${dest}"
# Create a dummy file for dry-run to proceed
touch "${dest}"
return 0 return 0
fi fi
mkdir -p "$(dirname "${dest}")" mkdir -p "$(dirname "${dest}")"
if command_exists curl; then info "Downloading with curl..."
info "Downloading with curl..." if ! curl -L --fail -o "${dest}.tmp" "${url}"; then
curl -L --fail -o "${dest}.tmp" "${url}" && mv "${dest}.tmp" "${dest}" rm -f "${dest}.tmp"
elif command_exists wget; then err "Download failed for ${url}"
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 exit 1
fi fi
mv "${dest}.tmp" "${dest}"
success "Map downloaded: ${dest}" success "Map downloaded: ${dest}"
} }
# ------------------------- # -------------------------
# Profile wrappers & patched profiles # Profile generation & patching
# - ensure default wrappers present
# - create patched copy with arg fallback and motorcycle modifications
# ------------------------- # -------------------------
generate_profile_wrappers() { generate_profile_wrappers() {
info "Generating profile wrappers in ${profile-dir} (if 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 "Profile exists: ${pf}" info "Profile exists: ${pf}"
continue continue
fi fi
case "${profile}" in case "${profile}" in
car|bicycle|bike|foot) car|bicycle|bike|foot)
info "Generating wrapper for ${profile} from builtin" info "Generating wrapper for ${profile}"
cat > "${pf}" <<'LUA' # This generic wrapper loads the built-in profile from the container
-- wrapper: attempt to load builtin profile cat > "${pf}" <<LUA
-- wrapper: attempt to load builtin profile '${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
...@@ -454,61 +389,50 @@ local function load_builtin(name) ...@@ -454,61 +389,50 @@ local function load_builtin(name)
end end
error("builtin profile not found: "..name) error("builtin profile not found: "..name)
end end
local base = load_builtin("car") return load_builtin('${profile}')
if type(base) == "table" and base.setup then return base end
if type(base) == "table" then return base end
if type(base) == "function" then return base() end
error("unexpected profile format")
LUA LUA
success "Generated wrapper: ${pf}" success "Generated wrapper: ${pf}"
;; ;;
motorcycle) motorcycle)
info "Generating motorcycle wrapper based on car" info "Generating motorcycle wrapper based on car"
# we'll create a motorcycle wrapper that loads car and patches properties # FIX: This version correctly modifies the car profile without breaking its internal scope.
cat > "${pf}" <<'LUA' cat > "${pf}" <<'LUA'
-- motorcycle wrapper derived from car profile -- motorcycle wrapper derived from car profile (v2 - Robust)
local function load_builtin(name) local function load_builtin(name)
local cands = {"/opt/profiles/"..name..".lua", "/opt/"..name..".lua"} local candidates = {
for _,p in ipairs(cands) do "/opt/profiles/" .. name .. ".lua",
local f = io.open(p,"r") "/opt/" .. name .. ".lua"
if f then f:close(); return dofile(p) end }
end for _, path in ipairs(candidates) do
error("builtin profile not found: "..name) local f = io.open(path, "r")
if f then
f:close()
return dofile(path)
end
end
error("Built-in profile not found: " .. name)
end end
local base = load_builtin("car") -- Load the entire module returned by the car profile
local base_profile local car_module = load_builtin('car')
if type(base) == "table" and base.setup then base_profile = base.setup()
elseif type(base) == "table" then base_profile = base -- Get the profile data table by calling its setup function
elseif type(base) == "function" then base_profile = base() local profile_table = car_module.setup()
else error("unexpected car profile") end
-- Modify the profile table in-place
local properties = base_profile.properties or {} profile_table.properties.max_speed = 100
local speed_profile = base_profile.speed_profile or {} profile_table.properties.u_turn_penalty = 60
local turn_penalty_profile = base_profile.turn_penalty_profile or {} profile_table.restrictions = profile_table.restrictions or {}
profile_table.restrictions["toll"] = "prohibited"
properties.max_speed = properties.max_speed or 100
properties.u_turn_penalty = 60
-- add motorcycle-specific restrictions
local restrictions = base_profile.restrictions or {}
-- set toll to prohibited
restrictions["toll"] = "prohibited"
local profile = {
properties = properties,
speed_profile = speed_profile,
turn_penalty_profile = turn_penalty_profile,
restrictions = restrictions,
excludable = base_profile.excludable or {}
}
if base.process_node then process_node = base.process_node end -- Redefine the setup function in the original module to return our modified profile
if base.process_way then process_way = base.process_way end car_module.setup = function()
if base.process_turn then process_turn = base.process_turn end return profile_table
end
function setup() return profile end -- Return the entire, modified module. This preserves all functions
return { setup = setup, process_node = process_node, process_way = process_way, process_turn = process_turn } -- like process_node, process_way, etc. in their original context.
return car_module
LUA LUA
success "Generated motorcycle wrapper: ${pf}" success "Generated motorcycle wrapper: ${pf}"
;; ;;
...@@ -520,15 +444,16 @@ LUA ...@@ -520,15 +444,16 @@ LUA
} }
create_patched_profiles() { create_patched_profiles() {
info "Creating patched profiles in ${patched-dir:-${patched-dir}} (safe copies with arg fallback)" info "Creating patched profiles in ${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)" warn "Profile source not found: ${src} (skipping patch)"
continue continue
fi fi
# This patch helps profiles find their own name if they rely on arg[0]
{ {
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}"
...@@ -541,12 +466,7 @@ create_patched_profiles() { ...@@ -541,12 +466,7 @@ create_patched_profiles() {
} }
# ------------------------- # -------------------------
# Docker Compose generation (absolute paths) # Docker Compose generation (absolute host paths)
# - Create preprocess and routed service per profile
# - Preprocess mounts patched-profiles and profile-data (rw)
# - Routed mounts profile-data (ro)
# - 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}"
...@@ -557,8 +477,8 @@ generate_docker_compose() { ...@@ -557,8 +477,8 @@ generate_docker_compose() {
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))
local pdata="${work-dir}/${profile}-data" local pdata="${work_dir}/${profile}-data"
mkdir -p "${pdata}" mkdir -p "${pdata}"
services+=" services+="
osrm-preprocess-${profile}: osrm-preprocess-${profile}:
...@@ -569,35 +489,35 @@ generate_docker_compose() { ...@@ -569,35 +489,35 @@ 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-dir}:/opt/custom_profiles:ro${selabel} - ${patched_dir}:/opt/custom_profiles:ro${selabel}
- ${pdata}:/data:rw${selabel} - ${pdata}:/data${selabel}
networks: networks:
- ${network-name} - ${network_name}
osrm-routed-${profile}: osrm-routed-${profile}:
image: ${BACKEND_IMAGE} image: ${BACKEND_IMAGE}
container_name: osrm-routed-${profile} container_name: osrm-routed-${profile}
restart: unless-stopped restart: unless-stopped
depends_on:
- osrm-preprocess-${profile}
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:
- ${pdata}:/data:ro${selabel} - ${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 [ "${no-frontend}" != true ]; then if [ "${no_frontend}" != true ]; then
# build list of backend urls for frontend environment variable
local backend_urls="" local backend_urls=""
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))
# backend url format: {backend-domain}:{backend-port}/osrm/{api-version}/{profile} local url="http://${backend_domain}:${host_port}${backend_path}/${api_version}/${profile}"
local url="http://${backend-domain}:${host_port}${backend-path}/${api-version}/${profile}"
if [ -z "${backend_urls}" ]; then if [ -z "${backend_urls}" ]; then
backend_urls="${url}" backend_urls="${url}"
else else
...@@ -611,423 +531,332 @@ generate_docker_compose() { ...@@ -611,423 +531,332 @@ generate_docker_compose() {
container_name: osrm-frontend container_name: osrm-frontend
restart: unless-stopped restart: unless-stopped
environment: environment:
- DEFAULT_BACKEND_URLS=${backend-urls} - VITE_DEFAULT_BACKEND_URLS=${backend_urls}
ports: ports:
- \"${frontend-port}:9966\" - \"${frontend_port}:9966\"
networks: networks:
- ${network-name} - ${network_name}
" "
fi fi
# FIX: Removed the obsolete 'version' attribute
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 exists # Ensure docker network
# ------------------------- # -------------------------
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 fi
} }
# ------------------------- # -------------------------
# Pull images (interactive) # Pull images
# ------------------------- # -------------------------
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"
return 0 return 0
fi fi
info "Pulling images..." info "Pulling latest images..."
if [ "${do-update-backend}" = true ] || [ "${do-update-all}" = true ] || [ "${command_name}" = "install" ]; then docker pull "${BACKEND_IMAGE}" || warn "docker pull backend failed"
info "Pull backend: ${BACKEND_IMAGE}" if [ "${no_frontend}" != true ]; then
docker pull "${BACKEND_IMAGE}" || warn "docker pull backend failed"
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" docker pull "${FRONTEND_IMAGE}" || warn "docker pull frontend failed"
fi fi
} }
# ------------------------- # -------------------------
# Preprocess sequentially (per profile) # Preprocess sequential
# ------------------------- # -------------------------
preprocess_sequential() { preprocess_sequential() {
info "Starting sequential preprocess for profiles: ${PROFILE_ARR[*]}" info "Starting sequential preprocessing for profiles: ${profiles_csv}"
mkdir -p "${work-dir}/dataset" if [ ! -f "${MAP_PATH}" ]; then
# ensure map copied into each profile-data as map.osm.pbf err "Map file not found at ${MAP_PATH}. Cannot preprocess."
safe_copy_map "${MAP_PATH}" "${work-dir}/dataset/map.osm.pbf" return 1
fi
local any_failed=false
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 pdata="${work-dir}/${profile}-data" local pdata="${work_dir}/${profile}-data"
mkdir -p "${pdata}" mkdir -p "${pdata}"
safe_copy_map "${work-dir}/dataset/map.osm.pbf" "${pdata}/map.osm.pbf" safe_copy_map "${MAP_PATH}" "${pdata}/map.osm.pbf"
local service_name="osrm-preprocess-${profile}"
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 preprocessing for ${profile}"
continue continue
fi fi
# run preprocess (compose creates container) # FIX: Use --exit-code-from for reliable error detection
if docker compose --project-directory "${work-dir}" up --build osrm-preprocess-"${profile}"; then info "Running preprocessor container for '${profile}'... This may take a long time."
success "Preprocess container exited 0 for: ${profile}" if docker compose -f "${COMPOSE_FILE}" up --build --exit-code-from "${service_name}" "${service_name}"; then
else success "Preprocessing for '${profile}' completed successfully."
err "Preprocess failed for: ${profile}. Showing logs:"
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
continue
fi
# cleanup container
docker compose --project-directory "${work-dir}" rm -fsv osrm-preprocess-"${profile}" || true
# 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 else
warn "Preprocess claimed success but no OSRM outputs found in ${pdata}" err "Preprocessing for '${profile}' FAILED. Check container logs above for details."
docker compose --project-directory "${work-dir}" logs osrm-preprocess-"${profile}" --no-log-prefix | sed -n '1,200p' || true any_failed=true
continue if ! ask_confirm "Preprocessing for '${profile}' failed. Continue with other profiles?"; then
err "Aborting installation due to preprocessing failure."
exit 1
fi
fi fi
sleep 1 # Clean up the one-shot container
docker compose -f "${COMPOSE_FILE}" rm -fsv "${service_name}" >/dev/null 2>&1 || true
done done
if [[ "${any_failed}" == "true" ]]; then
warn "One or more preprocessing steps failed. The corresponding routing services will not be started."
fi
} }
# -------------------------
# safe copy map helper (avoid unnecessary copies)
# -------------------------
safe_copy_map() { safe_copy_map() {
local src="$1"; local dest="$2" local src="$1"; local dest="$2"
if [ "${dry-run}" = true ]; then if [ "${dry_run}" = true ]; then
warn "[DRY RUN] Would cp ${src} -> ${dest}" warn "[DRY RUN] Would copy ${src} -> ${dest}"
return 0 return 0
fi fi
mkdir -p "$(dirname "${dest}")" mkdir -p "$(dirname "${dest}")"
if [ -e "${dest}" ] && [ -e "${src}" ]; then if [ ! -f "${src}" ]; then
if [ "$(realpath "${src}")" = "$(realpath "${dest}")" ]; then err "Map source file does not exist: ${src}"
info "Map already at target location; skipping copy" exit 1
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 fi
info "Copying map to processing directory: ${dest}"
cp -f "${src}" "${dest}" cp -f "${src}" "${dest}"
info "Copied map to ${dest}"
} }
# ------------------------- # -------------------------
# Start routed services (idempotent checks) # Start routed services
# ------------------------- # -------------------------
start_routed_services() { start_routed_services() {
info "Starting routed services for profiles: ${PROFILE_ARR[*]}" info "Starting routed services..."
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
local to_start=() local services_to_start=()
local idx=0
for profile in "${PROFILE_ARR[@]}"; do for profile in "${PROFILE_ARR[@]}"; do
idx=$((idx+1)) local pdata="${work_dir}/${profile}-data"
local pdata="${work-dir}/${profile}-data" # Check if preprocessing was successful by looking for the output files
if [ -f "${pdata}/map.osrm.properties" ] || ls "${pdata}"/*.osrm* >/dev/null 2>&1; then if [ -f "${pdata}/map.osrm" ]; then
to_start+=("osrm-routed-${profile}") services_to_start+=("osrm-routed-${profile}")
else else
warn "Skipping osrm-routed-${profile}: no OSRM outputs found in ${pdata}" warn "Skipping 'osrm-routed-${profile}' because preprocessed data was not found in ${pdata}"
fi fi
done done
if [ "${#to_start[@]}" -eq 0 ]; then if [ ${#services_to_start[@]} -eq 0 ]; then
err "No routed services to start." err "No successfully preprocessed profiles found. Cannot start any routing services."
return 1 return 1
fi fi
# detect existing containers and ask for recreate if present info "Starting services: ${services_to_start[*]}"
local will_start=() docker compose -f "${COMPOSE_FILE}" up -d --remove-orphans "${services_to_start[@]}"
for svc in "${to_start[@]}"; do success "Routed services started."
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[*]}"
local idx2=0
for profile in "${PROFILE_ARR[@]}"; do
idx2=$((idx2+1))
local host_port=$((backend-port + idx2 - 1))
info "Profile ${profile}: host port ${host_port} -> container osrm-routed-${profile}:5000"
done
} }
# ------------------------- # -------------------------
# Start frontend (idempotent) # Start frontend
# ------------------------- # -------------------------
start_frontend() { start_frontend() {
if [ "${no-frontend}" = true ]; then if [ "${no_frontend}" = true ]; then
info "Frontend installation skipped (--no-frontend)" 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
if docker ps -a --format '{{.Names}}' | grep -q "^osrm-frontend$"; then info "Starting frontend service..."
if ask_confirm "osrm-frontend container exists. Recreate?" ; then docker compose -f "${COMPOSE_FILE}" up -d --remove-orphans osrm-frontend
docker compose --project-directory "${work-dir}" rm -fsv osrm-frontend || true success "Frontend started on host port ${frontend_port}"
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 # NGPM hints
# ------------------------- # -------------------------
generate_ngpm_hints() { generate_ngpm_hints() {
info "Generating NGPM hints at ${NGPM_HINTS}" info "Generating NGPM hints at ${NGPM_HINTS}"
{ {
echo "NGPM (Nginx Proxy Manager) hints" echo "--- OSRM & NGPM (Nginx Proxy Manager) Configuration ---"
echo "Backend domain: ${backend-domain}" echo "Date: $(date)"
echo "Frontend domain: ${frontend-domain}" echo
echo "Backend path: ${backend-path}/${api-version}/{profile}" echo "This file provides hints for configuring your public-facing domains in NGPM."
echo "Ensure your NGPM container is connected to the '${network_name}' Docker network."
echo echo
printf "%-30s %-35s %s\n" "LOCATION PATH" "FORWARD HOSTNAME / CONTAINER" "HOST PORT" echo "BACKEND SERVICES:"
printf "%-15s | %-25s | %-35s\n" "Profile" "Container Target" "Public URL Example"
echo "--------------------------------------------------------------------------------"
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" "${backend-path}/${api-version}/${profile}" "osrm-routed-${profile}:5000" "${host_port}" local container_target="osrm-routed-${profile}:5000"
local url_example="https://${backend_domain}${backend_path}/${api_version}/${profile}"
printf "%-15s | %-25s | %-35s\n" "${profile}" "${container_target}" "${url_example}"
done done
if [ "${no-frontend}" != true ]; then echo
echo if [ "${no_frontend}" != true ]; then
echo "Frontend UI (container: osrm-frontend) -> host port ${frontend-port}" echo "FRONTEND UI:"
echo "Configure NGPM manually and attach it to docker network ${network-name}" printf "%-15s | %-25s | %-35s\n" "UI" "osrm-frontend:9966" "https://${frontend_domain}"
echo "--------------------------------------------------------------------------------"
fi fi
} > "${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' cat "${NGPM_HINTS}"
info "------------------------"
} }
# ------------------------- # -------------------------
# Print resolved configuration # Print config
# ------------------------- # -------------------------
print_configuration() { print_configuration() {
cat <<EOF echo "--- Installation Summary ---"
Resolved configuration: echo "Work Directory: ${work_dir}"
work-dir: ${work-dir} echo "Docker Compose: ${COMPOSE_FILE}"
map-dir: ${map-dir} echo "Docker Network: ${network_name}"
profile-dir: ${profile-dir} echo "Map Source Used: ${MAP_PATH}"
patched-dir: ${patched-dir} echo "Profiles Active: ${profiles_csv}"
backup-dir: ${backup-dir} echo
log-dir: ${log-dir} echo "ACCESS POINTS:"
docker-compose: ${COMPOSE_FILE} local idx=0
backend-image: ${BACKEND_IMAGE} for profile in "${PROFILE_ARR[@]}"; do
frontend-image: ${FRONTEND_IMAGE} idx=$((idx + 1))
profiles: ${PROFILE_ARR[*]} local host_port=$((backend_port + idx - 1))
network: ${network-name} echo " - Backend (${profile}): http://${backend_domain}:${host_port}${backend_path}/${api_version}/${profile}"
backend-port: ${backend-port} done
frontend-port: ${frontend-port} if [ "${no_frontend}" != true ]; then
backend-domain: ${backend-domain} echo " - Frontend UI: http://${frontend_domain}:${frontend_port}"
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
}
# -------------------------
# Update functions
# -------------------------
update_backend() {
info "Updating backend..."
pull_images
# recreate routed services for backend profiles
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
}
update_frontend() {
info "Updating frontend..."
pull_images
if [ "${no-frontend}" = true ]; then
info "--no-frontend set; skipping frontend update"
return 0
fi
if docker ps -a --format '{{.Names}}' | grep -q "^osrm-frontend$"; then
if ask_confirm "Recreate osrm-frontend with new image?" ; then
docker compose --project-directory "${work-dir}" rm -fsv osrm-frontend || true
else
info "Keeping existing frontend"
return 0
fi
fi
docker compose --project-directory "${work-dir}" up -d osrm-frontend || warn "Failed to start osrm-frontend"
success "Frontend updated"
}
update_map() {
info "Updating map dataset..."
# if map-source is url -> download
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 fi
elif [ -f "${map-source}" ]; then echo "--------------------------"
MAP_PATH="$(realpath "${map-source}")"
info "Using local map: ${MAP_PATH}"
else
warn "Map source not found: ${map-source}"
return 1
fi
# 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)" # Update logic
pull_images # -------------------------
update_map run_update() {
update_profile info "Update flow started for component: ${sub_command}"
update_backend prereqs_check
update_frontend
success "All components updated" case "${sub_command}" in
backend)
pull_images
start_routed_services
;;
frontend)
pull_images
start_frontend
;;
map)
select_map
preprocess_sequential
start_routed_services
;;
profile)
generate_profile_wrappers
create_patched_profiles
preprocess_sequential
start_routed_services
;;
all)
pull_images
generate_profile_wrappers
create_patched_profiles
select_map
preprocess_sequential
start_routed_services
start_frontend
;;
*)
err "Invalid update component '${sub_command}'. Use one of: backend, frontend, map, profile, all."
exit 1
;;
esac
success "Update flow completed."
} }
# ------------------------- # -------------------------
# Remove functions # Remove logic
# ------------------------- # -------------------------
backup_before_remove() { run_remove() {
local bfile="${backup-dir}/backup-$(date '+%Y%m%d_%H%M%S').tar.gz" info "Remove flow started for component: ${sub_command}"
info "Creating backup ${bfile}" if [ ! -f "${COMPOSE_FILE}" ]; then
tar -czf "${bfile}" -C "${work-dir}" . || warn "Backup failed" warn "Docker compose file not found at ${COMPOSE_FILE}. Assuming components are already removed."
info "Backup saved: ${bfile}"
}
remove_backend() {
info "Removing backend containers..."
for profile in "${PROFILE_ARR[@]}"; do
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 fi
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() { local services_to_remove=()
info "Removing frontend container..." case "${sub_command}" in
if docker ps -a --format '{{.Names}}' | grep -q '^osrm-frontend$'; then backend)
docker rm -f osrm-frontend || warn "Failed to remove osrm-frontend" for profile in "${PROFILE_ARR[@]}"; do
success "Removed osrm-frontend" services_to_remove+=("osrm-routed-${profile}")
else done
info "osrm-frontend not present" ;;
fi frontend)
} services_to_remove+=("osrm-frontend")
;;
remove_profile() { profile)
info "Removing profiles and patched-profiles..." if ask_confirm "This will delete all profile files from ${profile_dir} and ${patched_dir}. Continue?"; then
rm -rf "${profile-dir}"/* "${patched-dir}"/* || warn "Failed to remove some profile files" rm -rf "${profile_dir}" "${patched_dir}"
success "Profiles removed" success "Profile directories removed."
} fi
return
remove_map() { ;;
info "Removing map dataset..." all)
rm -rf "${map-dir}"/* || warn "Failed to remove map files" if ask_confirm "This will STOP and REMOVE ALL OSRM containers. Proceed?"; then
success "Map files removed" docker compose -f "${COMPOSE_FILE}" down -v --remove-orphans || warn "Could not stop all containers. They may not be running."
} success "All containers stopped and removed."
if [[ "${do_purge}" == "true" ]]; then
if ask_confirm "PURGE active. This will DELETE the entire work directory: ${work_dir}. THIS IS IRREVERSIBLE. Proceed?"; then
rm -rf "${work_dir}"
success "Work directory purged."
fi
fi
fi
return
;;
*)
err "Invalid remove component '${sub_command}'. Use one of: backend, frontend, profile, all."
exit 1
;;
esac
remove_all() { if [ ${#services_to_remove[@]} -gt 0 ]; then
info "Removing all OSRM components..." info "Stopping and removing services: ${services_to_remove[*]}"
backup_before_remove docker compose -f "${COMPOSE_FILE}" rm -fsv "${services_to_remove[@]}" || warn "Failed to remove some services."
remove_frontend if [[ "${do_purge}" == "true" ]]; then
remove_backend info "Purging data for removed services..."
remove_profile for profile in "${PROFILE_ARR[@]}"; do
remove_map if [[ "${sub_command}" == "backend" || "${sub_command}" == "all" ]]; then
rm -f "${COMPOSE_FILE}" "${NGPM_HINTS}" || true rm -rf "${work_dir}/${profile}-data"
success "All components removed" info "Purged ${work_dir}/${profile}-data"
fi
done
fi
fi
success "Remove flow completed."
} }
# ------------------------- # -------------------------
...@@ -1036,100 +865,38 @@ remove_all() { ...@@ -1036,100 +865,38 @@ remove_all() {
case "${command_name}" in case "${command_name}" in
install) install)
info "Install flow started" info "Install flow started"
# prereqs prereqs_check
prereqs_check || warn "Some dependencies missing; check above warnings"
# map selection
select_map select_map
# generate wrappers if missing
generate_profile_wrappers generate_profile_wrappers
# create patched profiles
create_patched_profiles create_patched_profiles
# pull images
pull_images pull_images
# generate compose
generate_docker_compose generate_docker_compose
# ensure network
ensure_network ensure_network
# preprocess sequential
preprocess_sequential preprocess_sequential
# start routed start_routed_services
start_routed_services || true
# start frontend
start_frontend start_frontend
# ngpm hints
generate_ngpm_hints generate_ngpm_hints
success "Install flow completed" success "Install flow completed"
info "NGPM hints saved: ${NGPM_HINTS}"
print_configuration print_configuration
exit 0
;; ;;
update) update)
info "Update flow started" run_update
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) remove)
info "Remove flow started" run_remove
# 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
;; ;;
*) *)
err "Unknown command: ${command_name}" err "Unknown command: ${command_name}"
usage usage
;; ;;
esac esac
\ No newline at end of file
# End of script
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