Skip to content

Deutsche Bahn on a self-hosted MOTIS, official Timetables API, import-time probe - #617

Merged
keysersoft merged 3 commits into
mainfrom
keysersoft/motis-deutsche-bahn
Sep 15, 2026
Merged

keysersoft merged 3 commits into
mainfrom
keysersoft/motis-deutsche-bahn

Conversation

@keysersoft

Copy link
Copy Markdown
Contributor

Why

Deutsche Bahn is the most-installed adapter (82 installs across 76 organisations) and had not returned a successful call from the cloud since 30 August. db-rest scrapes bahn.de; Deutsche Bahn blocks datacenter IPs; even /locations answered 500. The library's own README now recommends a self-hosted MOTIS.

What

Deutsche Bahn → MOTIS + open data

  • deploy/motis: official MOTIS image + entrypoint that downloads the gtfs.de long-distance and regional feeds (CC BY 4.0, ~12 MB), imports in ~1 s, polls GTFS-RT for live delays/cancellations, re-imports weekly. Verified locally: 24 of 30 board rows carry live data, delays visible, Freiburg→Berlin plans with ICE legs.
  • Cloud compose runs it as the internal motis service in db-rest's slot; self-host gets a motis profile.
  • Adapter rewritten against /api/v1/geocode, /stoptimes, /plan with JMESPath mappings (board 12 KB → 2 KB). baseUrl is {{MOTIS_URL}}, operator-provided via MOTIS_INTERNAL_URL and hidden from the form when set. Transitous is never the default (non-commercial policy).
  • New deutsche-bahn-timetables adapter on DB's official Timetables API (XML). The REST engine now parses XML responses.
  • scripts/ops/migrate-deutsche-bahn-cloud.mjs prints the SQL to move installed connectors (same rules as re-sync).

Keyless claim

  • scripts/probe-keyless.mjs + weekly workflow: one real call per keyless adapter from a runner IP, builds MOTIS from deploy/motis to cover DB. Fails on bot-block/auth.
  • selfHostOnly for adapters that only work from residential IPs (vinted, untappd, dpd-germany, idealista, reddit, sorare, trenitalia, opentable): hidden on cloud, out of the count. Count is 189 adapters / 20 keyless; README, banner, server.json, glama.json etc. regenerated.

Activation

  • Import runs the adapter's probe with the entered credentials and reports on the install form.
  • product_events table + POST /api/product-events; the post-attach page reports copy/tab/key/leave events.
  • Activation reminder links to the MCP server page with connect-a-client copy when one exists.
  • Cron marks trials past expiresAt as expired (after the lifecycle email).
  • Re-sync applies a catalog responseMapping to tools that have none.

Verification

  • Backend: 4171 tests pass, typecheck and lint clean. deutsche-bahn.live.spec.ts run against a MOTIS built from deploy/motis (DB_LIVE_MOTIS_URL): all five tools, live rows present.
  • Frontend: tsc and eslint clean.
  • node scripts/adapter-count.mjs --check and validate-adapters.mjs pass.
  • probe-keyless.mjs --all from this machine: 6 ok, vinted bot-blocked, opentable 401, trenitalia 412 (both now selfHostOnly).

After merge

Release, then on the cloud: deploy-cloud (builds the motis image), run the migration SQL, restart the app, verify a real MCP call, run the probe from the droplet.

…e official Timetables API, probe connectors at import

Deutsche Bahn is the most-installed adapter (82 installs, 76 organisations)
and it had not returned a successful call from the cloud since 30 August:
db-rest scrapes bahn.de, Deutsche Bahn blocks datacenter IPs, and even
/locations answered 500. db-vendo-client's own README now recommends a
self-hosted MOTIS. This does that.

Deutsche Bahn
- deploy/motis: official MOTIS image plus an entrypoint that downloads the
  gtfs.de long-distance and regional feeds (CC BY 4.0, ~12 MB), imports them
  in about a second, polls the GTFS-RT feed for live delays and cancellations
  and re-imports weekly. Trains only on purpose: the full feed with every bus
  peaks at 7 GB on import.
- docker-compose.cloud.yml runs it as the internal `motis` service in
  db-rest's place; docker-compose.yml offers it as the `motis` profile.
- The adapter now targets the MOTIS v1 API (/geocode, /stoptimes, /plan) with
  JMESPath response mappings, which cut a departure board from 12 KB to 2 KB.
  baseUrl is {{MOTIS_URL}}: operator-provided (MOTIS_INTERNAL_URL) and hidden
  from the install form where the operator runs MOTIS, asked for otherwise.
  Never Transitous by default: its policy forbids commercial use.
- New deutsche-bahn-timetables adapter on DB's official Timetables API
  (CC BY 4.0, free key, 60 req/min) for station boards and the live change
  feed. The REST engine now parses XML responses into objects so response
  mapping applies to them.
- scripts/ops/migrate-deutsche-bahn-cloud.mjs generates the SQL that moves
  installed connectors to the new schema, following the re-sync rules.

Keyless claim
- scripts/probe-keyless.mjs calls one real tool of every keyless adapter and
  fails CI when one is bot-blocked or auth-gated; runs weekly from a runner
  (a datacenter address, like the cloud) with a MOTIS built from deploy/motis.
- Adapters that only answer residential IPs are marked selfHostOnly: hidden
  from the cloud catalog and excluded from the advertised count, which is now
  20, not 26. Adapters declare a `probe` call for this and for import.

Activation
- Import runs the adapter's probe with the credentials just entered and
  reports the result on the install form; 63 stuck workspaces had only ever
  learned their token was wrong from the agent.
- product_events records what users do on the post-attach page (copied the
  endpoint, opened a client, generated a key, left without copying), so the
  74% who never send a request can be read step by step.
- The activation reminder sends users who already have an MCP server to its
  page with connect-a-client copy, not to the tool tester.
- The cron flips trials past expiresAt to status=expired; 1,021 of 1,094
  "active" trials had ended, and every status-based count was wrong.
- Catalog re-sync applies a catalog responseMapping to tools that have none.
Comment thread deploy/motis/Dockerfile Fixed
A new adapter, a new engine feature (XML responses), a new table and the
first infrastructure service the cloud compose has needed since db-rest: a
minor, not a patch.
Comment thread packages/backend/src/audit/product-event.service.ts Fixed
Comment thread packages/backend/src/audit/product-event.service.ts Fixed
Comment thread scripts/probe-keyless.mjs Fixed
Comment thread scripts/probe-keyless.mjs Fixed
…OTIS image, CodeQL config

CodeQL flagged the product-event metadata copy as remote property
injection (the client chose the property names). A fixed key set is the
right shape anyway: the events carry a client name and an id, nothing else.
The probe script is excluded from CodeQL by config: its job is to send
requests to URLs read from our own adapter catalog, which the
file-access-to-http query cannot tell from untrusted input.
@keysersoft
keysersoft merged commit eeb743b into main Sep 15, 2026
12 checks passed
@keysersoft
keysersoft deleted the keysersoft/motis-deutsche-bahn branch September 15, 2026 14:01
@github-actions github-actions Bot locked and limited conversation to collaborators Sep 15, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants