feat: satellite-download als eigenständiger Compose-Stack, build-host.sh, Script robust

This commit is contained in:
Conrad Schulz 2026-06-13 09:46:09 +00:00
parent c47f4785b1
commit 6b5009f51e
6 changed files with 284 additions and 16 deletions

View file

@ -0,0 +1,35 @@
# Satellite-Download eigenständiger Container
#
# Läuft vollständig unabhängig von VS Code / SSH-Sessions.
# Resume-fähig: bereits heruntergeladene Tiles werden übersprungen.
#
# STARTEN:
# cd /mnt/services-data/rd13_tile_server
# docker compose -f docker-compose.download.yml up
#
# STATUS (in zweitem Terminal):
# docker compose -f docker-compose.download.yml logs -f
#
# STOPPEN (Download pausieren):
# docker compose -f docker-compose.download.yml stop
#
# FORTSETZEN:
# docker compose -f docker-compose.download.yml up
#
# ERGEBNIS:
# data/satellite.mbtiles (~38 GB bei MAX_ZOOM=11)
services:
satellite-download:
image: python:3.12-slim
container_name: rd13_satellite_download
volumes:
- ./data:/data
- ./scripts:/scripts:ro
- ./logs:/logs
environment:
- SAT_MAX_ZOOM=11
- SAT_THREADS=8
- PYTHONUNBUFFERED=1
command: ["python3", "-u", "/scripts/download-satellite.py", "/data/satellite.mbtiles"]
restart: "no"

View file

@ -61,6 +61,31 @@ curl -s http://localhost:9982/osm | jq .
curl -s "http://localhost:9983/osm-intl/0/0/0.png" > /tmp/test.png && file /tmp/test.png
```
### Satellit-Tiles herunterladen (einmalig, ~38 GB, ~24h)
Der Download läuft in einem eigenen Container vollständig unabhängig von
SSH-Sessions und VS Code. Resume-fähig: bei Unterbrechung einfach neu starten.
```bash
cd /mnt/services-data/rd13_tile_server
# Starten (läuft im Vordergrund, Logs direkt sichtbar)
docker compose -f docker-compose.download.yml up
# Status in zweitem Terminal verfolgen
docker compose -f docker-compose.download.yml logs -f
# Pausieren
docker compose -f docker-compose.download.yml stop
# Fortsetzen (bereits heruntergeladene Tiles werden übersprungen)
docker compose -f docker-compose.download.yml up
```
Wenn fertig: `data/satellite.mbtiles` (~38 GB) liegt bereit.
Martin lädt sie automatisch beim nächsten Restart als Source `satellite`.
### Service Restart / Update Images
```bash

View file

@ -0,0 +1,86 @@
# Session 2026-06-13 Tile-Server vollständig in Betrieb
### Prompt 1
Schau jetzt nochmal auf alles drauf und versuche die aktuellen Probleme zu lösen! Ich habe jetzt einen neuen Proxy Caddy unter rd13_system_proxy als repo wenn du es nötig hast kannst du diesen auch in einem git workflow nach belieben anpassen damit das mit dem tile server funktioniert. Je mehr allerdings in dem Tile repo und dem dafür vorgesehenen docker stack gelöst werden kann desto besser.
### Antwort 1
Stack vollständig verifiziert: Martin (healthy), tileserver-gl (healthy), Caddy aktiv.
Smoke-Test bestanden: Zoom 0/5/10/14 HTTP 200 PNG, PBF 200, /health OK.
PROJECT_CONTEXT aktualisiert, committed: `2765bb9`
### Prompt 2
Geh nochmal ins MediaWiki und überprüfe die config und auch ob die config geladen ist.
### Antwort 2
Problem gefunden: `wikidata/LocalSettings.php` (der echte Mount) zeigte noch auf `tile.openstreetmap.org`.
Fix: auf `https://tiles.rd13server.de/styles` + `osm-intl` umgestellt.
Zweites Problem: Font-Fehler bei Zoom ≥10 `glyphs`-URL war relativ, tileserver-gl konnte Fonts nicht laden.
Fix: `glyphs` auf absolute URL `http://192.168.178.6:9983/fonts/{fontstack}/{range}.pbf`.
Commits: `9081ed9` (tile-server), `627a1b1` (mediawiki)
### Prompt 3
ok sieht gut aus, allerdings sehe ich nur orte und gebäude. Ich hätte gerne eine vollständige openstreetmap karte mit allen wanderwegen und bekannten punkten.
### Antwort 3
Alten minimalen Style (15 Layer) durch OSM Bright GL (128 Layer, offizieller OpenMapTiles-Referenz-Style) ersetzt.
Sprites heruntergeladen (4 Dateien: osm-bright.json/png + @2x), Sprite-Pfad-Problem gelöst.
Zoom 12 München: 2.9 KB → 32 KB (11× mehr Inhalt). Commit: `4268b41`
### Prompt 4
Ist hier jetzt die ganze Welt verfügbar offline schon auf meinem Server oder werden Daten noch nachgeladen?
### Antwort 4
Verifiziert: osm.mbtiles = OpenFreeMap, 95 GB, Bounds -180/-85 bis +180/+85 (kompletter Planet), z014.
Keine externen URLs im Style. Vollständig offline.
### Prompt 5
Wenn das einfach geht hätte ich gerne noch Deutschland oder Europa bis Zoom Level 16. Ebenso will ich Satellitenkarten für den ganzen Planeten, was benötigen wir dafür und wie viel SSD Platz bräuchten wir dafür?
### Antwort 5
Analyse: z16 Europa ~6090 GB, Satellit z011 ~38 GB, z12 ~140 GB.
Hardware-Anforderungen dokumentiert.
### Prompt 6
Für das Zoom Level 16 Europa muss ich diese unbedingt selbst bauen oder gibt's die wieder fertig zum download? Selbst bauen will ich ausschließen und im Repo festhalten dass das auf der aktuellen HW nicht möglich ist. Das gilt auch für die Satellitendaten.
### Antwort 6
Recherche: Kein Pre-Built für Europa/DE z16 verfügbar (OpenFreeMap, Protomaps, Geofabrik alle nur bis z14).
Kein Planet-Satellit-MBTiles als Download verfügbar.
ADR-002 erstellt: Hardware-Constraint dauerhaft dokumentiert, kein Agent startet Planetiler-Build.
Commit: `c47f478`
### Prompt 7
Ich könnte die z16 auf meinem Host-Rechner bauen und dann transferieren? Da habe ich genug Ressourcen zur Verfügung. Und die Satellitenkarten stückweise runterladen klingt gut!
### Antwort 7
`scripts/build-host.sh` erstellt: Docker-basierter Planetiler-Build (germany/austria/europe/planet),
mit RAM-Profilen, Validierung und rsync-Transfer auf Server.
Satellit-Download via nohup gestartet aber VS Code Sessions brachen ab.
### Prompt 8
Jedes Mal wenn du versuchst den Download zu starten brechen die VS Code Remote Sessions ab. Sollten wir das vielleicht etwas ordentlicher aufsetzen? Vielleicht ein Python Script das sicher aus einem Container ausgeführt wird?
### Antwort 8
Ursache: `nohup`/`&` in Fish-Shell + VS Code Remote = instabile Session.
Lösung: eigenständiger Docker-Container für Downloads.
`docker-compose.download.yml` erstellt, Script mit `flush=True` robuster gemacht.
Überreste bereinigt: satellite.mbtiles (4 KB stub), satellite.mbtiles-shm/wal, satellite.log.
### Prompt 9
Erstelle einen eigenen Container nur für den Download, der muss nicht in dem Compose leben.
### Antwort 9
Service aus `docker-compose.yml` entfernt.
`docker-compose.download.yml` als eigenständiger Stack angelegt.
```
# Starten:
docker compose -f docker-compose.download.yml up
# Status:
docker compose -f docker-compose.download.yml logs -f
# Pause/Resume:
docker compose -f docker-compose.download.yml stop
docker compose -f docker-compose.download.yml up
```
ADMIN.md mit Satelliten-Download-Anleitung ergänzt.

View file

@ -1,6 +1,6 @@
# PROJECT_CONTEXT rd13_tile_server
**Letzte Aktualisierung:** 2026-06-13 Stack vollständig in Betrieb: Martin + tileserver-gl + Caddy. Kartographer-PNG-Tiles öffentlich erreichbar und verifiziert.
**Letzte Aktualisierung:** 2026-06-13 OSM Bright GL Style (128 Layer) aktiv, Satellit-Download-Container bereit, Host-Build-Workflow dokumentiert, alle Overreste bereinigt.
---
@ -16,9 +16,11 @@ Tile-Updates manuell 24×/Jahr; Styles anpassbar.
- **Vektor-API:** `ghcr.io/maplibre/martin:latest` (Port 9982 → `/osm/{z}/{x}/{y}`)
- **Raster-Renderer:** `maptiler/tileserver-gl` (Port 9983 → `/styles/osm-intl/{z}/{x}/{y}.png`)
- **Proxy:** Caddy (`rd13_system_proxy`) SSL via Let's Encrypt, Routing `/styles/` → 9983, rest → 9982
- **Konfiguration:** `config/tileserver.json` (tileserver-gl), `config/styles/osm-intl.json` (MapLibre GL Style)
- **Daten:** `/data/` osm.mbtiles, fonts/, sprites/
- **Style:** OSM Bright GL (128 Layer, Wanderwege, POIs, vollständige Weltkarte)
- **Proxy:** Caddy (`rd13_system_proxy`) SSL, Routing `/styles/` → 9983, rest → 9982
- **Konfiguration:** `config/tileserver.json`, `config/styles/osm-intl.json`
- **Daten:** `data/osm.mbtiles` (95 GB, Planet z014), `data/sprites/`, `data/fonts/`
- **Satellit:** `docker-compose.download.yml``data/satellite.mbtiles` (~38 GB, z011, noch nicht gestartet)
- **Git Remote:** `ssh://git@192.168.178.6:2222/cschulz/rd13_tile_server.git`
---

121
scripts/build-host.sh Executable file
View file

@ -0,0 +1,121 @@
#!/usr/bin/env bash
# =============================================================================
# build-host.sh -- OSM Vektor-Tiles auf Host-Rechner bauen + auf Server deployen
#
# Dieses Script läuft auf dem HOST-RECHNER (nicht auf dem Server).
# Voraussetzung: Docker Desktop (macOS/Windows) oder Docker (Linux)
#
# Verwendung:
# ./scripts/build-host.sh [germany|europe|planet] [SERVER_USER@SERVER_HOST]
#
# Beispiele:
# ./scripts/build-host.sh germany rd13server@192.168.178.6
# ./scripts/build-host.sh europe rd13server@192.168.178.6
#
# Das fertige MBTiles wird per rsync auf den Server übertragen und Martin
# automatisch neu gestartet.
#
# Hardware-Anforderungen:
# germany: ≥ 8 GB RAM, ≥ 30 GB freier Disk, ~2h
# europe: ≥ 16 GB RAM, ≥ 200 GB freier Disk, ~8-12h
# planet: ≥ 32 GB RAM, ≥ 500 GB freier Disk, ~24h
# =============================================================================
set -euo pipefail
AREA="${1:-germany}"
REMOTE="${2:-}"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BUILD_DIR="${SCRIPT_DIR}/../data/build"
OUTPUT_FILE="${BUILD_DIR}/${AREA}.mbtiles"
# ── RAM-Profil ───────────────────────────────────────────────────────────────
case "$AREA" in
germany) RAM="8g" ;;
austria) RAM="4g" ;;
europe) RAM="16g" ;;
planet) RAM="32g" ;;
*)
echo "Unbekannte Region: $AREA"
echo "Verfügbar: germany | austria | europe | planet"
exit 1
;;
esac
mkdir -p "$BUILD_DIR"
echo "================================================================"
echo " rd13 Tile-Build | Region: $AREA | RAM: $RAM | $(date)"
echo " Output: $OUTPUT_FILE"
echo "================================================================"
# ── 1. Planetiler bauen ──────────────────────────────────────────────────────
echo ""
echo "[1/3] Starte Planetiler via Docker..."
echo " (OSM-Daten werden automatisch von Geofabrik heruntergeladen)"
echo ""
docker run --rm \
-v "${BUILD_DIR}:/data" \
-e JAVA_TOOL_OPTIONS="-Xmx${RAM}" \
ghcr.io/onthegomap/planetiler:latest \
--download \
--area="${AREA}" \
--output="/data/${AREA}.mbtiles" \
--maxzoom=16 \
--force
echo ""
echo "[1/3] Build fertig: $(ls -lh "$OUTPUT_FILE" | awk '{print $5}')"
# ── 2. Validieren ────────────────────────────────────────────────────────────
echo "[2/3] Validiere MBTiles..."
python3 - <<PYEOF
import sqlite3, sys
conn = sqlite3.connect("${OUTPUT_FILE}")
meta = dict(conn.execute("SELECT name, value FROM metadata").fetchall())
maxzoom = meta.get("maxzoom", "?")
bounds = meta.get("bounds", "?")
count = conn.execute("SELECT COUNT(*) FROM tiles").fetchone()[0]
print(f" Name: {meta.get('name','?')}")
print(f" MaxZoom: {maxzoom}")
print(f" Bounds: {bounds}")
print(f" Tiles: {count:,}")
if int(maxzoom) < 14:
print("FEHLER: MaxZoom < 14 Build unvollständig?")
sys.exit(1)
conn.close()
print(" ✓ Validierung OK")
PYEOF
# ── 3. Transfer auf Server ───────────────────────────────────────────────────
if [[ -n "$REMOTE" ]]; then
REMOTE_PATH="/mnt/services-data/rd13_tile_server/data/osm-${AREA}.mbtiles"
echo ""
echo "[3/3] Übertrage nach ${REMOTE}:${REMOTE_PATH}..."
echo " (rsync bricht/fortsetzt bei Verbindungsunterbrechung)"
rsync -avh --progress \
--partial --partial-dir="${BUILD_DIR}/.rsync-partial" \
"${OUTPUT_FILE}" \
"${REMOTE}:${REMOTE_PATH}"
echo ""
echo "[3/3] Starte Martin auf Server neu..."
ssh "${REMOTE}" "cd /mnt/services-data/rd13_tile_server && docker compose restart martin"
echo ""
echo "✓ Fertig! Neue Source verfügbar unter:"
echo " https://tiles.rd13server.de/osm-${AREA}"
echo ""
echo " Füge in config/styles/osm-intl.json eine zweite Source hinzu:"
echo " \"osm-${AREA}\": { \"type\": \"vector\", \"url\": \"mbtiles://{osm-${AREA}}\" }"
else
echo ""
echo "[3/3] Kein Remote angegeben Transfer übersprungen."
echo " MBTiles liegt unter: ${OUTPUT_FILE}"
echo ""
echo " Manueller Transfer:"
echo " rsync -avh --progress ${OUTPUT_FILE} USER@SERVER:/mnt/services-data/rd13_tile_server/data/osm-${AREA}.mbtiles"
fi
echo ""
echo "================================================================"
echo " Build abgeschlossen: $(date)"
echo "================================================================"

View file

@ -74,19 +74,18 @@ def do_tile(conn, z, x, y):
stats["err"] += 1
def log(msg):
print(msg, flush=True)
def main():
total = sum(4 ** z for z in range(MAX_ZOOM + 1))
print("[Sat] Output: %s" % OUTPUT)
print("[Sat] Zoom: 0-%d | Threads: %d | Tiles: %d" % (MAX_ZOOM, THREADS, total))
print("[Sat] Quelle: Sentinel-2 cloudless 2021 (EOX, CC BY 4.0)")
log("[Sat] Output: %s" % OUTPUT)
log("[Sat] Zoom: 0-%d | Threads: %d | Tiles: %d" % (MAX_ZOOM, THREADS, total))
log("[Sat] Quelle: Sentinel-2 cloudless 2021 (EOX, CC BY 4.0)")
log("[Sat] Resume: bereits heruntergeladene Tiles werden übersprungen")
conn = init_db(OUTPUT)
t0 = time.time()
futs = [
do_tile
for z in range(MAX_ZOOM + 1)
for x in range(1 << z)
for y in range(1 << z)
]
with ThreadPoolExecutor(max_workers=THREADS) as ex:
submitted = [ex.submit(do_tile, conn, z, x, y)
for z in range(MAX_ZOOM + 1)
@ -97,7 +96,7 @@ def main():
if n % 1000 == 0 or n == total:
dt = time.time() - t0 or 0.001
eta = (total - n) / n * dt / 3600
print("[Sat] %5.1f%% %d/%d ok=%d skip=%d err=%d %.0ft/s ETA=%.1fh" % (
log("[Sat] %5.1f%% %d/%d ok=%d skip=%d err=%d %.0ft/s ETA=%.1fh" % (
n / total * 100, n, total,
stats["ok"], stats["skip"], stats["err"],
n / dt, eta))
@ -105,11 +104,11 @@ def main():
if pending:
conn.executemany("INSERT OR REPLACE INTO tiles VALUES(?,?,?,?)", pending)
conn.commit()
print("[Sat] Erstelle Index...")
log("[Sat] Erstelle Index...")
conn.execute("CREATE UNIQUE INDEX IF NOT EXISTS idx ON tiles(zoom_level, tile_column, tile_row)")
conn.execute("ANALYZE")
conn.close()
print("[Sat] Fertig %.1fh -- %s" % ((time.time() - t0) / 3600, OUTPUT))
log("[Sat] Fertig %.1fh -- %s" % ((time.time() - t0) / 3600, OUTPUT))
if __name__ == "__main__":