ChirpStack 4.19.1 (LoRaWAN network server) as the 14th package: official-image wrapper, digest-pinned, with only bash added to the upstream alpine runtime. Native OIDC login wired to the Cloudron platform provider (openid_connect backend, config regenerated every start); postgresql + redis addons; diesel migrations auto-run. Operator config (NetID, US915 region, gateway MQTT backend) lives in seeded files under /app/data/config. Build green; config generation validated through chirpstack's own TOML parser. Docs gardened to 14 packages. Ticket: https://projects.knownelement.com/issues/668 💘 Generated with Crush Assisted-by: Crush:glm-5.2
262 lines
7.9 KiB
Bash
Executable File
262 lines
7.9 KiB
Bash
Executable File
#!/bin/bash
|
|
set -euo pipefail
|
|
|
|
# ChirpStack runtime setup:
|
|
# 1. wait for the Cloudron postgresql + redis addons
|
|
# 2. persist the API JWT secret (rotating it would invalidate tokens)
|
|
# 3. regenerate /app/data/config/10-cloudron.toml on EVERY start so addon
|
|
# credentials and OIDC secrets are always current (Cloudron rotates
|
|
# addon passwords on restore / migration)
|
|
# 4. seed the operator-owned config fragments ONCE (network + region);
|
|
# these are meant to be edited with the Cloudron file manager and
|
|
# survive restarts
|
|
# 5. exec chirpstack (diesel migrations run automatically at startup)
|
|
#
|
|
# Config layout (chirpstack concatenates every *.toml in --config <dir>;
|
|
# tables must not repeat across files):
|
|
# 10-cloudron.toml generated: [logging] [postgresql] [redis] [api]
|
|
# [user_authentication] - DO NOT hand-edit
|
|
# 50-network.toml seeded once: [network] (net_id, enabled_regions)
|
|
# region_*.toml seeded once: [[regions]] blocks (gateway MQTT
|
|
# backend, channel plan, region network overrides)
|
|
|
|
CONFIG_DIR="/app/data/config"
|
|
GENERATED_CONF="${CONFIG_DIR}/10-cloudron.toml"
|
|
NETWORK_CONF="${CONFIG_DIR}/50-network.toml"
|
|
SECRET_FILE="/app/data/.api_jwt_secret"
|
|
|
|
mkdir -p "${CONFIG_DIR}"
|
|
|
|
# --- 1. wait for the addons -------------------------------------------------
|
|
wait_tcp() {
|
|
local host="$1" port="$2" name="$3"
|
|
echo "Waiting for ${name} at ${host}:${port} ..."
|
|
until (exec 3<>"/dev/tcp/${host}/${port}") 2>/dev/null; do
|
|
echo "${name} is unavailable - sleeping"
|
|
sleep 2
|
|
done
|
|
echo "${name} is up"
|
|
}
|
|
|
|
wait_tcp "${CLOUDRON_POSTGRESQL_HOST:-127.0.0.1}" "${CLOUDRON_POSTGRESQL_PORT:-5432}" "PostgreSQL"
|
|
wait_tcp "${CLOUDRON_REDIS_HOST:-127.0.0.1}" "${CLOUDRON_REDIS_PORT:-6379}" "Redis"
|
|
|
|
# --- 2. persistent API JWT secret --------------------------------------------
|
|
if [[ ! -s "${SECRET_FILE}" ]]; then
|
|
( umask 077; head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \n' > "${SECRET_FILE}" )
|
|
echo "Generated new API JWT secret"
|
|
fi
|
|
API_SECRET="$(cat "${SECRET_FILE}")"
|
|
|
|
# --- 3. generated platform config (rewritten on every start) -----------------
|
|
toml_escape() {
|
|
local s="$1"
|
|
s="${s//\\/\\\\}"
|
|
s="${s//\"/\\\"}"
|
|
printf '%s' "${s}"
|
|
}
|
|
|
|
DB_HOST="${CLOUDRON_POSTGRESQL_HOST:-127.0.0.1}"
|
|
DB_PORT="${CLOUDRON_POSTGRESQL_PORT:-5432}"
|
|
DB_NAME="${CLOUDRON_POSTGRESQL_DATABASE:-chirpstack}"
|
|
DB_USER="$(toml_escape "${CLOUDRON_POSTGRESQL_USERNAME:-chirpstack}")"
|
|
DB_PASSWORD="$(toml_escape "${CLOUDRON_POSTGRESQL_PASSWORD:-chirpstack}")"
|
|
PG_DSN="postgresql://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}?sslmode=disable"
|
|
|
|
# Cloudron redis requires auth; prefer the platform-provided URL.
|
|
if [[ -n "${CLOUDRON_REDIS_URL:-}" ]]; then
|
|
REDIS_SERVERS="$(toml_escape "${CLOUDRON_REDIS_URL}")"
|
|
else
|
|
REDIS_PASSWORD="$(toml_escape "${CLOUDRON_REDIS_PASSWORD:-}")"
|
|
REDIS_SERVERS="redis://:${REDIS_PASSWORD}@${CLOUDRON_REDIS_HOST:-127.0.0.1}:${CLOUDRON_REDIS_PORT:-6379}"
|
|
fi
|
|
|
|
AUTH_MODE="${CHIRPSTACK_AUTH_MODE:-openid_connect}"
|
|
OIDC_REGISTRATION="${CHIRPSTACK_OIDC_REGISTRATION:-true}"
|
|
LOG_LEVEL="${CHIRPSTACK_LOG_LEVEL:-info}"
|
|
OIDC_ISSUER="$(toml_escape "${CLOUDRON_OIDC_ISSUER:-}")"
|
|
OIDC_CLIENT_ID="$(toml_escape "${CLOUDRON_OIDC_CLIENT_ID:-}")"
|
|
OIDC_CLIENT_SECRET="$(toml_escape "${CLOUDRON_OIDC_CLIENT_SECRET:-}")"
|
|
APP_ORIGIN="$(toml_escape "${CLOUDRON_APP_ORIGIN:-}")"
|
|
|
|
cat > "${GENERATED_CONF}" <<EOF
|
|
# Generated by start.sh on every boot - manual edits will be lost.
|
|
# Operator config belongs in 50-network.toml / region_*.toml (seeded once).
|
|
|
|
[logging]
|
|
level = "${LOG_LEVEL}"
|
|
|
|
[postgresql]
|
|
dsn = "${PG_DSN}"
|
|
max_open_connections = 10
|
|
connection_recycling_method = "verified"
|
|
|
|
[redis]
|
|
servers = ["${REDIS_SERVERS}"]
|
|
max_open_connections = 100
|
|
|
|
[api]
|
|
bind = "0.0.0.0:8080"
|
|
secret = "${API_SECRET}"
|
|
|
|
[user_authentication]
|
|
enabled = "${AUTH_MODE}"
|
|
|
|
[user_authentication.openid_connect]
|
|
provider_url = "${OIDC_ISSUER}"
|
|
client_id = "${OIDC_CLIENT_ID}"
|
|
client_secret = "${OIDC_CLIENT_SECRET}"
|
|
redirect_url = "${APP_ORIGIN}/auth/oidc/callback"
|
|
logout_url = "${APP_ORIGIN}"
|
|
login_redirect = true
|
|
login_label = "Cloudron"
|
|
registration_enabled = ${OIDC_REGISTRATION}
|
|
assume_email_verified = false
|
|
scopes = ["openid", "email", "profile"]
|
|
EOF
|
|
chmod 600 "${GENERATED_CONF}"
|
|
echo "Generated platform config at ${GENERATED_CONF} (auth mode: ${AUTH_MODE})"
|
|
|
|
# --- 4. operator config (seeded once, survives restarts) ----------------------
|
|
if [[ ! -f "${NETWORK_CONF}" ]]; then
|
|
cat > "${NETWORK_CONF}" <<'EOF'
|
|
# Operator configuration - seeded on first start, safe to edit with the
|
|
# Cloudron file manager (changes apply on restart).
|
|
#
|
|
# Do NOT add [postgresql], [redis], [api], [user_authentication] or
|
|
# [logging] here: those tables are owned by the generated 10-cloudron.toml
|
|
# and chirpstack fails to parse duplicate tables.
|
|
|
|
[network]
|
|
# NetID (3 bytes, hex) - MUST be changed to a unique value for this
|
|
# network. 000000-0000FF is reserved for private / experimental networks
|
|
# (see LoRa Alliance NetID assignments).
|
|
net_id = "000001"
|
|
|
|
# Enabled regions; each entry must match the id of a [[regions]] block in
|
|
# one of the region_*.toml files in this directory. More region files can
|
|
# be copied from the upstream repo (chirpstack/configuration/).
|
|
enabled_regions = ["us915_0"]
|
|
EOF
|
|
echo "Seeded network config at ${NETWORK_CONF}"
|
|
fi
|
|
|
|
if [[ ! -f "${CONFIG_DIR}/region_us915_0.toml" ]]; then
|
|
cat > "${CONFIG_DIR}/region_us915_0.toml" <<'EOF'
|
|
# US915 region (channels 0-7 + 64) - the standard US915 sub-band plan.
|
|
# Verbatim from upstream chirpstack/configuration/region_us915_0.toml;
|
|
# edit the gateway MQTT backend below to point at your broker.
|
|
|
|
[[regions]]
|
|
id = "us915_0"
|
|
description = "US915 (channels 0-7 + 64)"
|
|
common_name = "US915"
|
|
user_info = ""
|
|
|
|
[regions.gateway]
|
|
force_gws_private = false
|
|
|
|
[regions.gateway.backend]
|
|
enabled = "mqtt"
|
|
|
|
[regions.gateway.backend.mqtt]
|
|
topic_prefix = "us915_0"
|
|
share_name = "chirpstack"
|
|
server = "tcp://localhost:1883"
|
|
username = ""
|
|
password = ""
|
|
qos = 0
|
|
clean_session = false
|
|
client_id = ""
|
|
keep_alive_interval = "30s"
|
|
ca_cert = ""
|
|
tls_cert = ""
|
|
tls_key = ""
|
|
|
|
[[regions.gateway.channels]]
|
|
frequency = 902300000
|
|
bandwidth = 125000
|
|
modulation = "LORA"
|
|
spreading_factors = [7, 8, 9, 10]
|
|
|
|
[[regions.gateway.channels]]
|
|
frequency = 902500000
|
|
bandwidth = 125000
|
|
modulation = "LORA"
|
|
spreading_factors = [7, 8, 9, 10]
|
|
|
|
[[regions.gateway.channels]]
|
|
frequency = 902700000
|
|
bandwidth = 125000
|
|
modulation = "LORA"
|
|
spreading_factors = [7, 8, 9, 10]
|
|
|
|
[[regions.gateway.channels]]
|
|
frequency = 902900000
|
|
bandwidth = 125000
|
|
modulation = "LORA"
|
|
spreading_factors = [7, 8, 9, 10]
|
|
|
|
[[regions.gateway.channels]]
|
|
frequency = 903100000
|
|
bandwidth = 125000
|
|
modulation = "LORA"
|
|
spreading_factors = [7, 8, 9, 10]
|
|
|
|
[[regions.gateway.channels]]
|
|
frequency = 903300000
|
|
bandwidth = 125000
|
|
modulation = "LORA"
|
|
spreading_factors = [7, 8, 9, 10]
|
|
|
|
[[regions.gateway.channels]]
|
|
frequency = 903500000
|
|
bandwidth = 125000
|
|
modulation = "LORA"
|
|
spreading_factors = [7, 8, 9, 10]
|
|
|
|
[[regions.gateway.channels]]
|
|
frequency = 903700000
|
|
bandwidth = 125000
|
|
modulation = "LORA"
|
|
spreading_factors = [7, 8, 9, 10]
|
|
|
|
[[regions.gateway.channels]]
|
|
frequency = 903000000
|
|
bandwidth = 500000
|
|
modulation = "LORA"
|
|
spreading_factors = [8]
|
|
|
|
[regions.network]
|
|
installation_margin = 10
|
|
rx_window = 0
|
|
rx1_delay = 1
|
|
rx1_dr_offset = 0
|
|
rx2_dr = 8
|
|
rx2_frequency = 923300000
|
|
rx2_prefer_on_rx1_dr_lt = 0
|
|
rx2_prefer_on_link_budget = false
|
|
downlink_tx_power = -1
|
|
adr_disabled = false
|
|
min_dr = 0
|
|
max_dr = 3
|
|
enabled_uplink_channels = [0, 1, 2, 3, 4, 5, 6, 7, 64]
|
|
|
|
[regions.network.rejoin_request]
|
|
enabled = false
|
|
max_count_n = 0
|
|
max_time_n = 0
|
|
|
|
[regions.network.class_b]
|
|
ping_slot_dr = 8
|
|
ping_slot_frequency = 0
|
|
EOF
|
|
echo "Seeded region config at ${CONFIG_DIR}/region_us915_0.toml"
|
|
fi
|
|
|
|
# --- 5. run -------------------------------------------------------------------
|
|
# chirpstack applies the embedded diesel migrations on startup, then serves
|
|
# the web UI + gRPC/REST API on 0.0.0.0:8080.
|
|
echo "Starting ChirpStack ..."
|
|
exec /usr/bin/chirpstack --config "${CONFIG_DIR}"
|