diff --git a/JOURNAL.md b/JOURNAL.md
index 99641f6..2d60deb 100644
--- a/JOURNAL.md
+++ b/JOURNAL.md
@@ -4,7 +4,7 @@
**Project**: TSYSDevStack-SupportStack-Cloudron
**Goal**: Package ~57 applications for Cloudron PaaS platform
**Start Date**: 2025-01-24
-**Current Status**: 13/~57 packages completed (~23%)
+**Current Status**: 14/~57 packages completed (~25%)
## Completed Packages
@@ -1038,6 +1038,103 @@ nothing)
---
+### 14. ChirpStack (Infrastructure) ✅
+
+**Application**: ChirpStack — open-source LoRaWAN network-server (web UI +
+gRPC/REST on one port, PostgreSQL storage, Redis sessions/dedup, external
+MQTT broker for gateways and integrations). Upstream:
+https://github.com/chirpstack/chirpstack (MIT), v4.19.1.
+
+**Ticket**: [#668](https://projects.knownelement.com/issues/668)
+
+**Pattern**: official-image wrapper. A from-source build would drag the
+whole Rust workspace + pnpm UI through a multi-GB compile; upstream ships
+a supported image (`chirpstack/chirpstack`) whose final stage is alpine +
+one static musl binary + ca-certificates, run as nobody:nogroup. Wrapping
+it costs one `apk add bash` and an ENTRYPOINT override — 83.4MB final
+image, the smallest package in the workspace so far.
+
+**Auth gate verdict**: ✅ OIDC preferred. ChirpStack 4 has a native
+OpenID Connect backend — `[user_authentication]` `enabled="openid_connect"`
+plus `[user_authentication.openid_connect]` (provider_url, client_id,
+client_secret, redirect_url, scopes; PKCE + nonce state stored in Redis).
+`start.sh` regenerates this block on every start from
+`CLOUDRON_OIDC_ISSUER` / `CLOUDRON_OIDC_CLIENT_ID` /
+`CLOUDRON_OIDC_CLIENT_SECRET` with
+`redirect_url = ${CLOUDRON_APP_ORIGIN}/auth/oidc/callback`.
+
+**Key findings / decisions**:
+
+- **Config model**: `chirpstack --config
` concatenates EVERY `*.toml`
+ in the dir (read_dir order is unsorted, so tables must be disjoint across
+ files — duplicates are a parse error) and substitutes `${ENV}` vars after
+ concatenation. Split into `10-cloudron.toml` (generated every boot:
+ logging, postgresql, redis, api, user_authentication — addon credentials
+ stay current across Cloudron password rotations) and operator-owned
+ `50-network.toml` + `region_us915_0.toml` seeded once, editable via the
+ file manager.
+- **Migrations**: embedded diesel migrations run automatically in
+ `storage::setup()` at startup and seed an internal `admin` user
+ (email `admin`, password `admin`, is_admin). No manual migrate step.
+- **Admin bootstrap gap**: users auto-registered via OIDC are non-admin,
+ and the internal login form is disabled in openid_connect mode. Document
+ path: `CHIRPSTACK_AUTH_MODE=internal` → login admin/admin → set a real
+ password + your SSO email → back to openid_connect. ChirpStack links an
+ OIDC identity to an existing user BY EMAIL, which transfers the admin
+ role to the SSO login.
+- **API JWT secret**: `api.secret` signs login tokens; persisted at
+ `/app/data/.api_jwt_secret` so restarts don't invalidate sessions.
+- **Ports**: single listener `api.bind 0.0.0.0:8080` (UI + gRPC + REST +
+ `/auth/oidc/*`). Gateways do NOT dial the app: ChirpStack 4 consumes an
+ external MQTT broker configured per region
+ (`[regions.gateway.backend.mqtt]`); US915 region file seeded as default
+ (Texas), operator points it at their broker.
+- **Redis addon first use** in this repo: `CLOUDRON_REDIS_URL` feeds
+ `redis.servers` directly (auth embedded in the URL).
+
+**Challenges & solutions**:
+
+- **Hub API digest mismatch**: the Docker Hub tags API reported an index
+ digest for `4.19.1` that BuildKit refused (`not found` when used as
+ `tag@digest`). `docker manifest inspect --verbose` gave the real
+ registry digest (amd64 manifest `sha256:c74901…`); pinned
+ tag+that-digest and the build resolved. Lesson: trust the registry, not
+ the Hub API, when pinning.
+- **Addon wait without clients**: the wrapper image has no psql/redis-cli,
+ and alpine package names drift between versions. Used bash `/dev/tcp`
+ probes instead — no extra packages, no version pinning headaches.
+- **Secret escaping into TOML**: generated DSN/OIDC values pass through a
+ `toml_escape` (backslash + double-quote) helper; verified with an
+ adversarial password containing both characters — chirpstack's own TOML
+ parser accepted the generated files (run reached DB connect, i.e. past
+ config load, by design of the test).
+
+**Verification**:
+
+- `docker build --cgroup-parent ukrrs-batch.slice` green; image 83.4MB;
+ `chirpstack --version` → 4.19.1 inside the image.
+- start.sh executed against a scratch `/app/data`: config + seeds written,
+ chirpstack parsed all TOML and proceeded to storage setup (failed only
+ at the intentionally absent DB — the expected boundary of a
+ no-addons smoke test).
+
+**Files Created**:
+
+- Dockerfile (official-image wrapper, digest-pinned, bash added)
+- CloudronManifest.json (manifestVersion 2, port 8080, localstorage +
+ postgresql 16 + redis addons)
+- start.sh (addon waits, JWT secret persistence, config generation,
+ seeding, exec) — committed executable
+- README.md (auth story, admin bootstrap, config layout, MQTT note)
+- CHANGELOG.md
+- .env.example (CHIRPSTACK_AUTH_MODE / OIDC_REGISTRATION / LOG_LEVEL)
+- .dockerignore (excludes the cloned repo/ from the build context)
+- logo.png (from upstream ui/public/logo.png)
+
+**Commit**: `feat: add ChirpStack Cloudron package (Infrastructure) [#668]`
+
+---
+
## Packaging Pattern: Download Pre-Compiled Binaries
### When to Use
diff --git a/Package-Workspace/Infrastructure/chirpstack/.dockerignore b/Package-Workspace/Infrastructure/chirpstack/.dockerignore
new file mode 100644
index 0000000..7d37f1a
--- /dev/null
+++ b/Package-Workspace/Infrastructure/chirpstack/.dockerignore
@@ -0,0 +1 @@
+repo/
diff --git a/Package-Workspace/Infrastructure/chirpstack/.env.example b/Package-Workspace/Infrastructure/chirpstack/.env.example
new file mode 100644
index 0000000..4c2e754
--- /dev/null
+++ b/Package-Workspace/Infrastructure/chirpstack/.env.example
@@ -0,0 +1,13 @@
+# ChirpStack Cloudron package - optional env knobs
+# (set via `cloudron env set`, restart to apply)
+#
+# Authentication backend. Default: openid_connect (Cloudron SSO).
+# Use "internal" ONLY for the one-time admin bootstrap described in
+# README.md, then switch back.
+CHIRPSTACK_AUTH_MODE=openid_connect
+
+# Auto-register users on first SSO login (true/false).
+CHIRPSTACK_OIDC_REGISTRATION=true
+
+# Log level: trace | debug | info | warn | error.
+CHIRPSTACK_LOG_LEVEL=info
diff --git a/Package-Workspace/Infrastructure/chirpstack/CHANGELOG.md b/Package-Workspace/Infrastructure/chirpstack/CHANGELOG.md
new file mode 100644
index 0000000..d4cb2cf
--- /dev/null
+++ b/Package-Workspace/Infrastructure/chirpstack/CHANGELOG.md
@@ -0,0 +1,17 @@
+# Changelog
+
+## 1.0.0 — 2026-09-01
+
+Initial Cloudron package (ChirpStack 4.19.1, ticket
+[#668](https://projects.knownelement.com/issues/668)).
+
+- Official-image wrapper around `chirpstack/chirpstack:4.19.1` (digest
+ pinned); only addition to the runtime image is `bash` for `start.sh`.
+- Native OIDC login against the Cloudron platform identity provider;
+ admin bootstrap path documented (temporary `internal` auth mode).
+- PostgreSQL + Redis addons; diesel migrations auto-run at startup;
+ persistent API JWT secret under `/app/data`.
+- Config split into a per-boot generated fragment and operator-owned
+ `50-network.toml` + `region_us915_0.toml` under `/app/data/config/`.
+- Env knobs: `CHIRPSTACK_AUTH_MODE`, `CHIRPSTACK_OIDC_REGISTRATION`,
+ `CHIRPSTACK_LOG_LEVEL`.
diff --git a/Package-Workspace/Infrastructure/chirpstack/CloudronManifest.json b/Package-Workspace/Infrastructure/chirpstack/CloudronManifest.json
new file mode 100644
index 0000000..b002f0c
--- /dev/null
+++ b/Package-Workspace/Infrastructure/chirpstack/CloudronManifest.json
@@ -0,0 +1,25 @@
+{
+ "manifestVersion": 2,
+ "type": "app",
+ "id": "io.cloudron.chirpstack",
+ "title": "ChirpStack",
+ "description": "Open-source LoRaWAN network server: manage gateways, devices, tenants and integrations from a web interface. Users log in via OIDC (Cloudron single sign-on); PostgreSQL stores all state, Redis handles sessions and de-duplication. LoRaWAN gateways connect through an external MQTT broker configured per region.",
+ "author": "Orne Brocaar",
+ "website": "https://www.chirpstack.io/",
+ "contactEmail": "cloudron@tsys.dev",
+ "tagline": "LoRaWAN network server with web UI",
+ "version": "4.19.1",
+ "healthCheckPath": "/",
+ "httpPort": 8080,
+ "memoryLimit": 512,
+ "addons": {
+ "localstorage": true,
+ "postgresql": {
+ "version": "16"
+ },
+ "redis": {}
+ },
+ "mediaLinks": [],
+ "changelog": "Initial Cloudron package for ChirpStack 4.19.1 (official-image wrapper). Native OIDC login wired to the Cloudron platform identity provider (user_authentication.openid_connect from CLOUDRON_OIDC_* env); PostgreSQL addon for storage with auto-run diesel migrations; Redis addon for sessions and de-duplication. Config is split into a generated platform fragment (rewritten each start) and operator-owned files under /app/data/config (network NetID, regions, gateway MQTT backend) editable with the Cloudron file manager. US915 region seeded by default.",
+ "icon": "file://logo.png"
+}
diff --git a/Package-Workspace/Infrastructure/chirpstack/Dockerfile b/Package-Workspace/Infrastructure/chirpstack/Dockerfile
new file mode 100644
index 0000000..6a9ec3e
--- /dev/null
+++ b/Package-Workspace/Infrastructure/chirpstack/Dockerfile
@@ -0,0 +1,51 @@
+# ChirpStack Cloudron Package
+#
+# ChirpStack is an open-source LoRaWAN network-server: web UI + gRPC/REST
+# API on a single port, PostgreSQL for storage, Redis for sessions /
+# deduplication / OIDC state, and an external MQTT broker for gateway
+# connectivity and integrations (configured per region, not embedded).
+#
+# Upstream: https://github.com/chirpstack/chirpstack
+# - Official Docker image chirpstack/chirpstack:4.19.1 (alpine, single
+# static musl binary /usr/bin/chirpstack, upstream runs it as
+# nobody:nogroup with ENTRYPOINT /usr/bin/chirpstack)
+# - Takes a config DIRECTORY via `chirpstack --config `; every *.toml
+# in it is concatenated (tables must not collide across files) and
+# ${ENV_VAR} placeholders are substituted
+# - DB schema migrations (diesel, embedded) run automatically at startup
+# and seed an internal `admin` user
+#
+# Authentication: NATIVE OIDC (preferred). start.sh regenerates
+# /app/data/config/10-cloudron.toml on every start, wiring the Cloudron
+# platform OIDC provider (CLOUDRON_OIDC_ISSUER / CLIENT_ID / CLIENT_SECRET)
+# into [user_authentication.openid_connect]. CHIRPSTACK_AUTH_MODE=internal
+# is kept as an operator escape hatch for admin bootstrap only (see README).
+#
+# Pattern: official-image wrapper. Building the Rust workspace + pnpm UI
+# from source is a multi-GB compile; the upstream image is the supported
+# distribution channel. Image pinned by tag AND digest (amd64 manifest
+# digest of the 4.19.1 tag, verified via docker manifest inspect).
+FROM chirpstack/chirpstack:4.19.1@sha256:c749015e640b8cf33338c08b12922896b17636feb06e421abdd3cc80f1cdc6b9
+
+# bash is the only addition: start.sh uses it for the addon wait loops
+# (bash /dev/tcp) and TOML generation. Kept as root only for apk; the
+# runtime user stays the upstream nobody:nogroup.
+USER root
+RUN apk add --no-cache bash
+
+# start.sh waits for the postgresql + redis addons, seeds the persistent
+# config fragments under /app/data/config/ and execs chirpstack.
+# Made executable on the host, not at build time (Cloudron builds hit
+# permission errors on RUN chmod).
+COPY start.sh /app/start.sh
+
+WORKDIR /app/data
+
+# Cloudron exposes the web UI / REST / gRPC on this port (api.bind in the
+# generated config). No other TCP listener is enabled by default: gateway
+# connectivity is outbound MQTT to an external broker.
+EXPOSE 8080
+
+USER nobody:nogroup
+
+ENTRYPOINT ["/bin/bash", "/app/start.sh"]
diff --git a/Package-Workspace/Infrastructure/chirpstack/README.md b/Package-Workspace/Infrastructure/chirpstack/README.md
new file mode 100644
index 0000000..b7ec85e
--- /dev/null
+++ b/Package-Workspace/Infrastructure/chirpstack/README.md
@@ -0,0 +1,90 @@
+# ChirpStack — Cloudron Package
+
+[ChirpStack](https://www.chirpstack.io/) is an open-source LoRaWAN
+network-server: it manages gateways, devices, tenants, device-profiles and
+integrations, and exposes a web UI plus gRPC / REST APIs from a single
+port. Packaged as an **official-image wrapper** around the upstream
+`chirpstack/chirpstack:4.19.1` image (pinned by digest).
+
+- **Upstream:** https://github.com/chirpstack/chirpstack (MIT)
+- **Ticket:** [#668](https://projects.knownelement.com/issues/668)
+- **Category:** Infrastructure · **Pattern:** official-image wrapper +
+ `start.sh` config generation
+
+## Authentication (auth gate verdict: ✅ OIDC preferred)
+
+ChirpStack 4 ships a **native OpenID Connect backend**
+(`[user_authentication.openid_connect]`). `start.sh` wires the Cloudron
+platform OIDC provider (`CLOUDRON_OIDC_ISSUER` / `CLOUDRON_OIDC_CLIENT_ID`
+/ `CLOUDRON_OIDC_CLIENT_SECRET`) with
+`redirect_url = ${CLOUDRON_APP_ORIGIN}/auth/oidc/callback`, so logins go
+through Cloudron SSO. Registration is enabled: the first SSO login
+auto-creates the user.
+
+### Admin bootstrap (one-time)
+
+The DB migration seeds an internal `admin` user (email `admin`,
+password `admin`, `is_admin = true`). Users created via OIDC registration
+are regular (non-admin) users. To become admin over SSO:
+
+1. Set the env `CHIRPSTACK_AUTH_MODE=internal` and restart the app.
+2. Log in as `admin` / `admin`, immediately set a strong password, and
+ change the account email to your Cloudron login email.
+3. Set `CHIRPSTACK_AUTH_MODE` back to `openid_connect` (or remove it) and
+ restart.
+4. Log in via SSO: ChirpStack links the OIDC identity to the existing
+ user **by email**, granting the admin role.
+
+Until step 2 is done the seeded `admin` account keeps its default
+password — do the bootstrap right after installing.
+
+## Addons & ports
+
+| Concern | Cloudron wiring |
+|---------|-----------------|
+| Storage | `postgresql` addon (diesel migrations auto-run at startup) |
+| Sessions / dedup / OIDC state | `redis` addon (`CLOUDRON_REDIS_URL`) |
+| Files | `localstorage` (`/app/data`) |
+| Web UI + gRPC + REST | single HTTP port `8080` (`api.bind`) |
+
+LoRaWAN gateways do **not** connect to this app directly: ChirpStack 4
+consumes an **external MQTT broker** per region (see
+`/app/data/config/region_*.toml`). Point `regions.gateway.backend.mqtt`
+at your broker (e.g. a Mosquitto container/app) and configure integrations
+the same way.
+
+## Configuration layout
+
+`chirpstack --config ` concatenates every `*.toml` in the directory;
+tables must not repeat across files. `/app/data/config/` is split:
+
+| File | Written | Owns |
+|------|---------|------|
+| `10-cloudron.toml` | every start | `[logging]` `[postgresql]` `[redis]` `[api]` `[user_authentication]` — regenerated, never hand-edit |
+| `50-network.toml` | first start | `[network]` (NetID, enabled regions) |
+| `region_us915_0.toml` | first start | US915 `[[regions]]` block incl. gateway MQTT backend |
+
+Operator knobs live in `50-network.toml` and the region files — edit them
+with the Cloudron file manager; changes apply on restart. **Change
+`net_id`** from the seeded `000001` to a unique value for your network,
+and point the region MQTT backend at a real broker. The API JWT secret is
+persisted at `/app/data/.api_jwt_secret`.
+
+## Environment knobs (.env.example)
+
+| Variable | Default | Purpose |
+|----------|---------|---------|
+| `CHIRPSTACK_AUTH_MODE` | `openid_connect` | `internal` only for admin bootstrap (see above) |
+| `CHIRPSTACK_OIDC_REGISTRATION` | `true` | auto-register unknown SSO users |
+| `CHIRPSTACK_LOG_LEVEL` | `info` | trace / debug / info / warn / error |
+
+## Build & install
+
+```bash
+docker build --cgroup-parent ukrrs-batch.slice -t chirpstack-cloudron:test \
+ Package-Workspace/Infrastructure/chirpstack/
+```
+
+`cloudron build && cloudron install` on the Cloudron VPS for real
+deployment. First start waits for PostgreSQL + Redis, seeds config and
+runs migrations automatically.
diff --git a/Package-Workspace/Infrastructure/chirpstack/logo.png b/Package-Workspace/Infrastructure/chirpstack/logo.png
new file mode 100644
index 0000000..24264fe
Binary files /dev/null and b/Package-Workspace/Infrastructure/chirpstack/logo.png differ
diff --git a/Package-Workspace/Infrastructure/chirpstack/start.sh b/Package-Workspace/Infrastructure/chirpstack/start.sh
new file mode 100755
index 0000000..84d9812
--- /dev/null
+++ b/Package-Workspace/Infrastructure/chirpstack/start.sh
@@ -0,0 +1,261 @@
+#!/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 ;
+# 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}" < "${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}"
diff --git a/README.md b/README.md
index 2ce1dec..9a41d2f 100644
--- a/README.md
+++ b/README.md
@@ -9,9 +9,9 @@ The Cloudron component focuses on packaging upstream free/libre/open application
### 📊 Current Progress
- **Total Applications**: ~57 (see [GitUrlList.txt](GitUrlList.txt))
-- **Completed Packages**: 13/~57 (~23%)
+- **Completed Packages**: 14/~57 (~25%)
- **Packaging Templates**: Created ✅
-- **Packages Committed & Pushed**: 13 ✅
+- **Packages Committed & Pushed**: 14 ✅
- **Build Tickets**: 46 filed (#633-#678, umbrella
[#632](https://projects.knownelement.com/issues/632), Redmine project 55);
grist-core excluded (packaged upstream)
@@ -35,6 +35,7 @@ The Cloudron component focuses on packaging upstream free/libre/open application
| 11 | Rathole | Infrastructure | 3.51GB | 8000, 2333, 5200-5299 | localstorage (auth proxy) | ✅ Committed |
| 12 | Database Gateway | Infrastructure | 93.7MB | 8080 | localstorage, postgresql | ✅ Committed |
| 13 | FX | DevOps-Tools | 3.55GB | 8000 | localstorage (auth proxy) | ✅ Committed |
+| 14 | ChirpStack | Infrastructure | 83.4MB | 8080 | localstorage, postgresql, redis | ✅ Committed |
### 📦 Packages in Development
@@ -150,7 +151,7 @@ Applications are organized by function rather than programming language:
| [Docker DrawIO](https://github.com/jgraph/docker-drawio) | [GitHub](https://github.com/jgraph/docker-drawio) | Dockerized version of Draw.io diagramming tool | Documentation-Tools | ✅ Packaged |
| [SigNoz](https://github.com/SigNoz/signoz) | [GitHub](https://github.com/SigNoz/signoz) | Open-source observability platform | Monitoring |
| [Sentry](https://github.com/getsentry/sentry) | [GitHub](https://github.com/getsentry/sentry) | Error tracking and performance monitoring | Monitoring |
-| [ChirpStack](https://github.com/chirpstack/chirpstack) | [GitHub](https://github.com/chirpstack/chirpstack) | Open-source LoRaWAN network server | Infrastructure |
+| [ChirpStack](https://github.com/chirpstack/chirpstack) | [GitHub](https://github.com/chirpstack/chirpstack) | Open-source LoRaWAN network server | Infrastructure | ✅ Packaged |
| [eLabFTW](https://github.com/elabftw/elabftw) | [GitHub](https://github.com/elabftw/elabftw) | Electronic lab notebook for research teams | Business-Apps |
| [PLMore](https://github.com/PLMore/PLMore) | [GitHub](https://github.com/PLMore/PLMore) | Business process management platform | Business-Apps |
| [Jamovi](https://github.com/jamovi/jamovi) | [GitHub](https://github.com/jamovi/jamovi) | Statistical spreadsheet software | Scientific-Computing |
diff --git a/STATUS.md b/STATUS.md
index ef90f69..d7a1d37 100644
--- a/STATUS.md
+++ b/STATUS.md
@@ -3,20 +3,21 @@
> **Human read-only. Agents maintain this file automatically after each work
> session.** Do not edit by hand — the next agent run will overwrite it.
>
-> **Last updated:** 2026-09-01 by Crush (GLM-5.2) — FX packaged (#640,
-> DevOps-Tools, 13th package); auth gate verdict: no user concept —
-> httpAuth proxy gates the landing page (CLI-workstation pattern).
+> **Last updated:** 2026-09-01 by Crush (GLM-5.2) — ChirpStack packaged
+> (#668, Infrastructure, 14th package); auth gate verdict: native OIDC
+> (`user_authentication.openid_connect`) wired to the platform provider;
+> postgresql + redis addons, official-image wrapper of chirpstack 4.19.1.
## Current State: STABLE (packaging phase, ongoing)
-Cloudron packaging pipeline is operational. 13 of ~57 upstream applications are
+Cloudron packaging pipeline is operational. 14 of ~57 upstream applications are
packaged, committed, and pushed. Packaging templates exist for the core
patterns. The gardening protocol (this file + AGENTS.md) keeps docs in sync.
All remaining apps now carry build tickets (#633-#678) under umbrella
[#632](https://projects.knownelement.com/issues/632) in Redmine project 55 —
ready for the sequential grind-driver pattern.
-## Completed Packages (13)
+## Completed Packages (14)
| # | Application | Category | Pattern | Port(s) | Addons |
|---|-------------|----------|---------|---------|--------|
@@ -33,6 +34,7 @@ ready for the sequential grind-driver pattern.
| 11 | Rathole | Infrastructure | Pre-compiled binaries + auth proxy | 8000, 2333, 5200-5299 | localstorage |
| 12 | Database Gateway | Infrastructure | Multi-stage (Go, CGO) | 8080 | localstorage, postgresql |
| 13 | FX | DevOps-Tools | Pre-compiled binaries + auth proxy | 8000 | localstorage |
+| 14 | ChirpStack | Infrastructure | Official-image wrapper | 8080 | localstorage, postgresql, redis |
Each package lives in `Package-Workspace///` and contains a
`Dockerfile`, `CloudronManifest.json`, `README.md`, `CHANGELOG.md`, `logo.png`,
@@ -121,7 +123,7 @@ Full write-ups of each pattern + challenges are in [`JOURNAL.md`](JOURNAL.md).
| DevOps-Tools | 1 | 1/1 (100%) ✅ | fx done |
| Financial-Payments | 1 | 0/1 | |
| Financial-Trading | 1 | 0/1 | |
-| Infrastructure | 6 | 3/6 | easy-gate, rathole, database-gateway done |
+| Infrastructure | 6 | 4/6 | easy-gate, rathole, database-gateway, chirpstack done |
| Legal | 1 | 0/1 | |
| Project-Management | 1 | 0/1 | |
| Scientific-Computing | 2 | 0/2 | |
@@ -135,7 +137,7 @@ Auth capability is a hard gate before packaging (see
LDAP acceptable (risk flag), 🔄 = auth-proxy (no users), ❌ = local-only
(unacceptable / blocked-on-auth).
-### Completed packages (13)
+### Completed packages (14)
| App | OIDC | LDAP | Verdict | Note |
|-----|------|------|---------|------|
@@ -152,6 +154,7 @@ LDAP acceptable (risk flag), 🔄 = auth-proxy (no users), ❌ = local-only
| Rathole | n/a | n/a | 🔄 proxy | **Packaged** with `httpAuth.type=proxy` on the status page; tunnels secured by mandatory per-service tokens (Noise/TLS optional) |
| Database Gateway | yes | no | ✅ preferred | **Packaged**; native OIDC-only app — platform provider env (`CLOUDRON_OIDC_*`) seeded into config.json; roles from the `groups` claim |
| FX | n/a | n/a | 🔄 proxy | **Packaged**; CLI-only FaaS tool with no user concept — pinned binary + workspace driven from the Cloudron terminal; landing page gated by `httpAuth.type=proxy` |
+| ChirpStack | yes | no | ✅ preferred | **Packaged**; native `[user_authentication.openid_connect]` wired to `CLOUDRON_OIDC_*`; OIDC-registered users are non-admin — one-time `CHIRPSTACK_AUTH_MODE=internal` bootstrap links the seeded `admin` to your SSO email (README) |
### Candidates researched