No description
  • JavaScript 89%
  • HTML 11%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-09-23 16:54:28 +02:00
deploy Add Salzburg Verkehr vehicle map and deployment configurations 2026-09-23 16:54:14 +02:00
lib Add Salzburg Verkehr vehicle map and deployment configurations 2026-09-23 16:54:14 +02:00
public Add Salzburg Verkehr vehicle map and deployment configurations 2026-09-23 16:54:14 +02:00
scripts Add Salzburg Verkehr vehicle map and deployment configurations 2026-09-23 16:54:14 +02:00
test Add Salzburg Verkehr vehicle map and deployment configurations 2026-09-23 16:54:14 +02:00
.env.example Add Salzburg Verkehr vehicle map and deployment configurations 2026-09-23 16:54:14 +02:00
.gitignore Add Salzburg Verkehr vehicle map and deployment configurations 2026-09-23 16:54:14 +02:00
package-lock.json Add Salzburg Verkehr vehicle map and deployment configurations 2026-09-23 16:54:14 +02:00
package.json Add Salzburg Verkehr vehicle map and deployment configurations 2026-09-23 16:54:14 +02:00
README.md Use direct Node.js and systemd deployment on main 2026-09-23 16:54:28 +02:00
server.js Add Salzburg Verkehr vehicle map and deployment configurations 2026-09-23 16:54:14 +02:00

Salzburg Verkehr

Branch main runs directly with Node.js and systemd. For Docker Compose deployment, use git switch docker.

A responsive bus and train map for the entire state of Salzburg, using hafas-client with the Salzburg Verkehr profile, Leaflet, and OpenStreetMap. German UI with line/destination search, mode filters, vehicle details, and explicit stale/partial-data states.

Data limitations

The client uses the bundled svv profile for https://fahrplan.salzburg-verkehr.at/bin/mgate.exe, including its VAO authentication and product mapping. Radar is explicitly enabled (and live-tested), since the bundled profile does not advertise it. The separate salzburg profile points to a different hostname.

This shows the vehicles returned by the Salzburg Verkehr HAFAS radar. It cannot guarantee every bus/train or actual GPS locations. Positions may be calculated from timetable/prognosis data; missing vehicles do not mean no service exists. A separate operator vehicle-position feed would be needed to guarantee GPS tracking.

The query rectangle (46.95–48.05° N, 12.05–13.99° E) covers the state with a margin and includes adjacent Austrian/German areas. It is not clipped to the administrative boundary. Twelve sequential tiles reduce per-query truncation; hitting the 256-result limit still triggers a coverage warning. Trips are deduplicated across tiles. Each scan takes at least 12 seconds; the next begins 60 seconds after completion. Browser polling every 15 seconds reads a shared cache and does not trigger upstream calls. There is no invented movement between snapshots.

Upstream requests have a 12-second timeout. Three initial failures stop a scan early. A complete outage retains the previous scan and marks it stale. A partially successful scan replaces it with only that scan's returned vehicles, marked incomplete. Data also becomes stale 3 minutes after scan start. /healthz reports process health, not upstream availability; /api/vehicles includes feed status and scan timestamps. No API credentials are required by the bundled profile, but endpoint availability can change.

See hafas-client radar documentation and the Salzburg Verkehr profile. The upstream project notes that use of mobile endpoints requires operator permission; arrange suitable access for your deployment. Public map tiles are subject to the OpenStreetMap tile usage policy; for substantial traffic, use an appropriate tile provider.

Run locally

Requires Node.js 24 or newer.

npm ci
cp .env.example .env
# Edit .env: set HAFAS_USER_AGENT to your real contact email or project URL.
npm start

Open http://localhost:3000. On Windows PowerShell, use Copy-Item .env.example .env and npm.cmd if execution policy blocks npm.ps1.

npm test
npm run check:live
npm run check:live -- --statewide

The live check makes one small Salzburg city request and prints the returned modes and two sample vehicles. Use it to verify upstream access from your server. Tests use fixture data; they do not prove live operator coverage.

Add -- --statewide to scan all twelve areas and report deduplicated bus/train counts, failed queries, and result limits. It exits with a nonzero status if coverage is incomplete.

To check the UI, keep the app running, install a browser with npx playwright install chromium, then run npm run test:browser. Alternatively, set BROWSER_CHANNEL=msedge to use an installed Microsoft Edge. Browser checks use fixture vehicles and replace tile requests, covering filters, search, popups, safe rendering of operator text, and mobile layout.

Deploy without Docker (Ubuntu/Debian Lightsail)

Install Node.js 24 LTS and npm system-wide. Check node --version and command -v node. The service below expects /usr/bin/node; if your system-wide installation uses another path, edit ExecStart before installing the service. A Node installation inside your home directory will not work with this service's ProtectHome setting.

Copy or clone the project to /opt/salzburg-verkehr using your normal deployment account, then run:

cd /opt/salzburg-verkehr
npm ci --omit=dev
cp .env.example .env
nano .env

Set HAFAS_USER_AGENT to a real contact email or project URL, keep PORT=3000, and add HOST=127.0.0.1 to .env. This keeps the Node server local to your reverse proxy. The service reads .env directly; use plain KEY=value entries without export.

Create the dedicated service account and install the unit:

sudo useradd --system --user-group --no-create-home --shell /usr/sbin/nologin salzburg-verkehr
sudo chgrp salzburg-verkehr .env
chmod 640 .env
sudo install -m 644 deploy/salzburg-verkehr.service /etc/systemd/system/salzburg-verkehr.service
sudo systemctl daemon-reload
sudo systemctl enable --now salzburg-verkehr
curl http://127.0.0.1:3000/healthz

Create the account only once. Ensure the service user can traverse /opt/salzburg-verkehr and read the application and node_modules; normal directory permissions of 755 and file permissions of 644 are sufficient. Keep code owned by your deployment account. The service starts at boot and restarts after failures. Inspect it with sudo systemctl status salzburg-verkehr and sudo journalctl -u salzburg-verkehr -n 50 --no-pager.

For public access, install Caddy using its official Ubuntu/Debian instructions. On a server with no existing Caddy configuration:

sudo install -m 644 deploy/Caddyfile /etc/caddy/Caddyfile
sudo nano /etc/caddy/Caddyfile
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy

Keep :80 to use http://YOUR_STATIC_IP, or replace it with your domain for automatic HTTPS after pointing DNS at your Lightsail static IP. Open TCP 80 and 443 in the Lightsail and host firewalls; keep port 3000 closed publicly. If you already use Caddy, merge the site block into your configuration. If you already use Nginx or Apache, configure that proxy to forward your site's requests to http://127.0.0.1:3000 instead.

After updating the code, run npm ci --omit=dev in the project directory and sudo systemctl restart salzburg-verkehr. For a clean dependency update, stop the service first with sudo systemctl stop salzburg-verkehr, then start it again after installation. No build step or database is required. These service files must be validated on your Linux server; they cannot be run on the Windows development machine.