Skip to content

fix(chirpstack): make gateway bridges config-free (env-only) so install doesn't crash+rollback - #27

Merged
madejackson merged 1 commit into
unstablefrom
fix/chirpstack-install-gateway-bridge
Oct 27, 2026
Merged

madejackson merged 1 commit into
unstablefrom
fix/chirpstack-install-gateway-bridge

Conversation

@madejackson

Copy link
Copy Markdown

What's broken

Installing ChirpStack from the market fails with a rollback (Gateway Timeout / HTTP002). The failing step is the ChirpStack-gateway-bridge post-install:

[ERROR] Rolling back changes because of -- Post install error:
Error response from daemon: Container ... is restarting, wait until the container is running

Root cause

Both gateway bridge services ({ServiceName}-gateway-bridge and -gateway-bridge-basicstation) were launched with:

"command": "-c /etc/chirpstack-gateway-bridge/chirpstack-gateway-bridge.toml"

but the config file was written by post_install — which Cosmos executes after the container has already started. On first boot the volume is empty, the config file does not exist, and chirpstack-gateway-bridge exits fatally:

level=fatal msg="error loading config file" error="open ...: no such file or directory"

The container enters a restart loop, so Cosmos's post-install exec fails with "container is restarting" and the entire stack is rolled back.

The fix

ChirpStack v4's gateway-bridge (Go binary) reads configuration from environment variables (KEY__NESTED → key.nested via viper) and a missing config file is not fatal when -c is not passed. I verified by booting the real images:

  • gateway-bridge: State: running, backend/semtechudp: starting gateway udp listener 🎉 (only MQTT DNS retries until mosquitto is reachable — harmless)
  • gateway-bridge-basicstation: State: running the same way (its concentrators are defined via gateway config in ChirpStack v4; empty list is acceptable at boot)

So both gateway bridges are now configured purely via environment — command and post_install removed:

Service Before After
gateway-bridge -c missing.toml → fatal INTEGRATION__MQTT__AUTH__GENERIC__SERVERS, region topic templates, BACKEND__TYPE=semtech_udp
gateway-bridge-basicstation -c missing-{region}.toml → fatal same MQTT env + BACKEND__TYPE=basic_station, BACKEND__BASIC_STATION__BIND=:3001

Additionally for the main chirpstack service:

  • The toml is now generated inline in post_install instead of downloading an external artefact (v4 substitutes $ENV in the toml at load — verified in chirpstack/src/config.rs).
  • api.secret is now a real password ({Passwords.1}) instead of the placeholder you-must-replace-this (otherwise the API/healthcheck misbehaves).
  • Added API_SECRET env var.

Validation

  • Reproduced the exact fatal on the real chirpstack/chirpstack-gateway-bridge:4 image with an empty volume + -c.
  • Verified the fix boots clean on both bridge images.
  • Verified the main chirpstack (v4 Rust binary) loads the generated toml, substitutes env, and reaches postgres (fails only on DNS because my test net has no {ServiceName}-postgres).
  • The patched cosmos-compose.json renders + parses as valid JSON using the store's own whiskers validator.

…ll no longer crashes

Root cause: gateways used command '-c <toml>' but the toml was written by
post_install, which Cosmos runs AFTER first container start. At first boot
the config file does not exist and both chirpstack-gateway-bridge binaries
exit fatally -> restart loop -> Cosmos post_install exec fails -> rollback
of the whole stack (Gateway Timeout / HTTP002).

Fix: configure both gateway bridges purely via ChirpStack v4 environment
variables (INTEGRATION__MQTT__*, BACKEND__TYPE, BACKEND__BASIC_STATION__*,
BACKEND__SEMTECH__UDP__*) and drop command + post_install for them entirely.
Verified by booting the actual images: semtech_udp listener starts, process
stays up (only MQTT broker DNS retries until mosquitto is reachable).

Also:
- main chirpstack: stop depending on an external artefact; generate the
  chirpstack.toml inline in post_install (works for v4, which substitutes
  $ENV in toml at load), set api.secret from Passwords.1 (was the
  placeholder 'you-must-replace-this'), keep DB password from Passwords.0.
- add API_SECRET env.

Validated with the store's own whiskers render + JSON parse.
@madejackson
madejackson merged commit a4f13a9 into unstable Oct 27, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant