diff --git a/documentation/part-5-bsn-cloud/02-automated-provisioning.md b/documentation/part-5-bsn-cloud/02-automated-provisioning.md index 3598bc1..3cf0b1f 100644 --- a/documentation/part-5-bsn-cloud/02-automated-provisioning.md +++ b/documentation/part-5-bsn-cloud/02-automated-provisioning.md @@ -1,11 +1,302 @@ # Automated Provisioning -[← Back to Part 5: BSN Cloud](README.md) | [↑ Main](../../README.md) +[<- Back to Part 5: BSN Cloud](README.md) | [^ Main](../../README.md) --- -*Content coming soon.* +## Overview + +BrightSign players include a built-in provisioning and recovery system that enables zero-touch deployment and remote content management. This system works independently of BSN.cloud and can be integrated with custom provisioning servers, making it suitable for both cloud-managed and self-hosted deployment scenarios. + +The provisioning system uses a **recovery URL** (`ru`) to download autorun scripts from a remote server. The player makes HTTP GET requests to this URL at different stages of its lifecycle, allowing your server to push updates, monitor health, and recover from failures. + +**Key capabilities:** +- Zero-touch deployment via DHCP Option 43 +- Automatic content updates on boot and during runtime +- Emergency recovery from storage failures or runtime errors +- Fleet-wide configuration without per-device setup +- Custom provisioning server integration + +--- + +## Recovery URL Configuration + +### The Registry Key: `ru` + +The primary registry key for recovery is **`ru`** -- the provisioning/recovery URL used by the player to download an autorun script. Related keys include: + +| Key | Purpose | +|-----|---------| +| `ru` | Recovery/provisioning URL (main endpoint) | +| `eu` | Error notification URL | +| `cu` | Crash report URL | +| `ub` | URL prefix applied to `ru`, `eu`, and `cu` (unless value contains a colon) | +| `p` | Password for the recovery handler | + +These keys live in the **"networking"** section of the player registry, accessible via the BrightScript `roRegistrySection` object or through BrightAuthor:connected setup. + +--- + +### How the Player Finds the Recovery URL at Boot + +When a BrightSign player boots and obtains an IP address, it checks for a recovery URL (`ru`) using this priority order: + +1. **DHCP Option 43** -- if a recovery URL is found via DHCP Option 43, it overrides any registry value for `ru` until the end of the DHCP lease +2. **Registry value** -- if no DHCP Option 43 RU is found, the `ru` value stored in the registry is used +3. **Well-known host / DNS mapping** -- if the registry value is blank, the player attempts "well-known host provisioning" by checking if `brightsign.b-deploy` is mapped in DNS +4. **mDNS** -- the player automatically checks `http://brightsign.b-deploy.local/` for a local provisioning server + +--- + +### Configuration Methods + +**Option A -- BrightAuthor:connected Setup** + +During player setup, the provisioning/recovery URL is one of the fields configured in the setup wizard before writing to the SD card. + +**Option B -- DHCP Option 43 (network-wide)** + +The `ru` value in the registry can be overridden using DHCP Option 43, meaning that with properly configured network infrastructure, players can set themselves up and begin playing content without manual on-site configuration. This is the preferred method for large deployments since it applies to all players on the network segment without touching each device. + +The recovery URL in the DHCP Option 43 payload is prepended with `U` + the ASCII character representing the URL's length. For example, if the URL is 64 characters long, the value would be prepended with `U@` (where `@` is decimal 64 in ASCII). + +**Option C -- Direct registry edit via Diagnostic Web Server (DWS)** + +Access the player's DWS at `http:///` and navigate to the registry editor to set the `ru` key manually. + +--- + +### Configuration Summary + +| Where | How | +|-------|-----| +| Player registry (`ru` key) | Set during BrightAuthor:connected setup or via DWS | +| DHCP Option 43 | Network-level override; no per-device config needed | +| DNS mapping | Zero-config fallback using `brightsign.b-deploy` hostname | +| mDNS | Auto-discovery of `brightsign.b-deploy.local` on local network | + +> **Best Practice:** The **DHCP Option 43 approach** is the most robust for emergency/fallback scenarios since it works even if the player's registry was wiped (e.g., after a factory reset), as long as the player gets a DHCP lease. + +--- + +## Recovery Modes + +There are three recovery modes, all of which hit the same `ru` endpoint. The modes are distinguished by the `recoverymode` header value the player sends in its HTTP GET request, which tells your request handler server exactly what situation it's dealing with. + +### Mode 1: Override (`recoverymode: override`) + +This mode fires on **every boot** when the player finds a valid autorun on its storage device. It's not really a "recovery" in the emergency sense -- it's the normal check-in that gives your server the opportunity to push updated content before the local autorun executes. + +**Sequence of events:** + +Once the boot process completes, the player scans each storage device to determine if one or more devices contain an autorun file. If at least one device contains an autorun, the player **delays executing it** while a background process sends an HTTP GET to the provisioning/recovery URL. + +If the request handler responds with HTTP 200 and a non-empty body, the player uses the response body as the new autorun script. If the handler responds with HTTP 204, or HTTP 200 with an empty body, the player executes the pre-existing local autorun as normal. + +**Timeout behavior:** + +If no response is received within 20 seconds, the pre-existing autorun will be executed. The player will then send another request to the request handler after five minutes, then after 10 minutes, and then once every two hours after that. + +**Storage device priority:** + +If multiple storage devices contain an autorun file, the player selects the first autorun in this order: USB, SD, SD2, SSD. Note that if it takes too long to check the integrity of a storage device, that device might be skipped -- so don't build a system that relies on this order. + +**Key design implication:** Override mode is your primary mechanism for remote content updates in normal operation. Your server can respond with 204 to leave a healthy player alone, or 200 + a new autorun to push an update. The 20-second timeout is important to design around -- your server must respond quickly or the local autorun just runs anyway. + +--- + +### Mode 2: Periodic (`recoverymode: periodic`) + +This mode fires **while the player is running normally** -- it's the recurring heartbeat check-in that happens after the initial override check on boot. + +**When it fires:** + +The player periodically sends an HTTP GET to the provisioning/recovery URL to determine if the device should be updated. The check-in interval defaults to two hours, but the server can control this via a `Retry-After` header in the response (value in seconds). + +**Response handling:** + +If no response is received, the pre-existing autorun continues without interruption. If the handler responds with HTTP 204, or HTTP 200 with an empty body, the pre-existing autorun also continues without interruption. If the handler responds with HTTP 200 and a non-empty body, the player uses the response body as the new autorun script. + +**Key design implication:** Periodic mode is how you push updates to players that are already up and running healthy. It's also your opportunity to detect a player that's been running stale content. The `Retry-After` header is powerful here -- you can dial the check-in frequency up or down dynamically. For example, you could respond with a short `Retry-After` when you know a content update is imminent, and a long one during stable periods to reduce server load. + +--- + +### Mode 3: Last Resort (`recoverymode: last_resort`) + +This is the true emergency recovery mode. It triggers when the player genuinely cannot play anything on its own. + +**What triggers it:** + +Last resort recovery begins when none of the attached storage devices contain an autorun file, **or** when the designated autorun encounters an `autorun load error` or `autorun runtime error`. + +The `storagestatus` header in the request will tell your server exactly what happened on each device. Possible status values per device are: + +| Status | Meaning | +|--------|---------| +| `none` | Device not detected | +| `storage` | Device present but no autorun found | +| `autorun` | Device present with valid autorun | +| `autorun load error` | Autorun found but failed to load | +| `autorun runtime error` | Autorun found but crashed at runtime | +| `error` | Device present but failed filesystem check, including failed repair attempts | + +**Response handling:** + +If the handler responds with HTTP 200 and a non-empty body, the player uses the response body as the new autorun script. Importantly, **the autorun script must reboot the player itself** once it performs the necessary recovery tasks -- the player will not automatically reboot. If the handler responds with HTTP 204 or HTTP 200 with an empty body, the player immediately reboots. + +**Timeout and retry behavior:** + +If the request handler does not respond, the player will continue sending requests for **30 minutes** before rebooting. However, if the autorun encountered a runtime error (rather than a missing autorun), the reboot occurs after only **5 minutes**. This process cycles indefinitely until an autorun script is received from the request handler. + +**Key design implication:** Last resort is where your server needs to be smart. Because the `storagestatus` header tells you the condition of every storage device, your request handler can make decisions: send a lightweight recovery autorun that reformats the SD card and re-downloads content for a filesystem corruption case, or send a diagnostic autorun for a runtime error case. The fact that the cycle repeats indefinitely means a player in this state will keep trying -- but it's also why your recovery autorun *must* explicitly reboot, otherwise the player just sits in the recovery script forever. + +--- + +## Recovery Mode Comparison + +| | Override | Periodic | Last Resort | +|---|---|---|---| +| **Trigger** | Boot, valid autorun found | Running normally, interval elapsed | No autorun / autorun error | +| **`recoverymode` header value** | `override` | `periodic` | `last_resort` | +| **Player state at trigger** | Healthy, about to run local autorun | Healthy, playing content | Broken -- cannot play | +| **Timeout if no server response** | 20 seconds -> runs local autorun | No timeout -> continues as-is | 30 min (5 min for runtime error) -> reboots | +| **200 + body response** | Replaces local autorun | Replaces running autorun | Executes recovery autorun | +| **204 / empty 200 response** | Runs local autorun normally | Continues current autorun | Immediately reboots | +| **Loops indefinitely?** | No | No | Yes, until autorun received | +| **Must script explicit reboot?** | No | No | **Yes** | + +--- + +## Building a Recovery Server + +### Minimal HTTP Handler + +Your recovery server needs to handle HTTP GET requests and inspect the `recoverymode` header to determine the appropriate response: + +```python +from flask import Flask, request, Response + +app = Flask(__name__) + +@app.route('/recovery') +def recovery_handler(): + mode = request.headers.get('recoverymode', 'unknown') + serial = request.headers.get('serial', 'unknown') + storage = request.headers.get('storagestatus', '') + + # Log the check-in + print(f"Player {serial}: mode={mode}, storage={storage}") + + if mode == 'override': + # Player just booted, decide if we want to update it + if should_update_player(serial): + return Response(get_autorun_script(serial), mimetype='text/plain') + else: + return Response('', status=204) # Use existing autorun + + elif mode == 'periodic': + # Heartbeat check-in during normal operation + if has_pending_update(serial): + return Response(get_autorun_script(serial), mimetype='text/plain') + else: + return Response('', status=204, headers={'Retry-After': '7200'}) + + elif mode == 'last_resort': + # Emergency recovery needed + recovery_script = generate_recovery_script(serial, storage) + return Response(recovery_script, mimetype='text/plain') + + return Response('', status=204) +``` + +### Request Headers + +The player sends several headers with each recovery request: + +| Header | Description | +|--------|-------------| +| `recoverymode` | One of: `override`, `periodic`, `last_resort` | +| `serial` | Player serial number | +| `model` | Player model (e.g., `XT2145`) | +| `version` | BrightSign OS version | +| `storagestatus` | Comma-separated status of each storage device (USB, SD, SD2, SSD) | + +### Response Headers + +Your server can control player behavior with these response headers: + +| Header | Purpose | +|--------|---------| +| `Retry-After` | Number of seconds until next periodic check-in (default: 7200) | +| `Content-Type` | Should be `text/plain` when returning an autorun script | + +--- + +## Best Practices + +### Fleet Management + +1. **Track player state** -- use the serial number and headers to maintain a database of player status +2. **Version control autoruns** -- keep track of which version each player is running +3. **Staged rollouts** -- push updates to a subset of players first, monitor for issues +4. **Fast response times** -- the override mode has a 20-second timeout, ensure your server responds quickly + +### Error Handling + +1. **Parse `storagestatus` carefully** -- it tells you exactly what went wrong +2. **Send appropriate recovery scripts** -- filesystem errors need different recovery than runtime errors +3. **Log all recovery requests** -- last resort mode indicates a problem that needs investigation +4. **Include diagnostics in recovery scripts** -- have the player report detailed error information + +### Security + +1. **Use HTTPS** -- protect autorun scripts in transit +2. **Authenticate players** -- verify the serial number and model before sending scripts +3. **Use the `p` registry key** -- require a password for recovery requests +4. **Rate limiting** -- prevent abuse from rogue players + +### Testing + +1. **Test all three modes** -- simulate boot, periodic, and failure scenarios +2. **Test timeout behavior** -- ensure your server responds within the timeout windows +3. **Test storage failures** -- verify last resort recovery works when storage is corrupt +4. **Monitor production** -- track recovery mode frequencies to detect fleet issues + +--- + +## Example: DHCP Option 43 Configuration + +### ISC DHCP Server + +``` +option space brightsign; +option brightsign.recovery-url code 1 = text; + +class "brightsign-players" { + match if substring(option vendor-class-identifier, 0, 10) = "BrightSign"; + vendor-option-space brightsign; + option brightsign.recovery-url "https://provisioning.example.com/recovery"; +} +``` + +### Windows DHCP Server + +1. Open DHCP management console +2. Right-click IPv4 -> Set Predefined Options +3. Add new option: + - Name: BrightSign Recovery URL + - Data type: String + - Code: 43 + - Value: `U@https://provisioning.example.com/recovery` (where `@` is chr(64) for a 64-character URL) + +--- + +## Next Steps + +- [Per-Player Control](03-per-player-control.md) -- Remote commands and monitoring +- [BSN.content](04-bsn-content.md) -- Content management and scheduling +- [Code Examples](examples/README.md) -- Recovery server implementations --- -[↑ Part 5: BSN Cloud](README.md) +[^ Part 5: BSN Cloud](README.md) diff --git a/howto-articles/01-setting-up-development-environment.md b/howto-articles/01-setting-up-development-environment.md index 1c9a123..d03427b 100644 --- a/howto-articles/01-setting-up-development-environment.md +++ b/howto-articles/01-setting-up-development-environment.md @@ -494,7 +494,176 @@ script # Return to BrightScript debugger **Exception - "Insecured" Mode:** -Players can be put into "insecured" mode for native extension development. In this mode, `exit` from the shell does NOT reboot, allowing you to return to a login prompt. This is required when developing custom C/C++ extensions. +Players can be put into "insecured" mode for native extension development. In this mode, `exit` from the shell does NOT reboot — instead it drops to a Linux shell prompt. See [Insecuring a Player for Extension Development](#insecuring-a-player-for-extension-development) below. + +--- + +## Insecuring a Player for Extension Development + +"Insecuring" a player means permanently disabling secure boot so the player will load unsigned OS extensions (native C/C++ `.so` libraries). This is required for developing and testing custom extensions — there is no other way to run unsigned code at the OS level. + +**When you need this:** +- Building custom native extensions (C/C++ code that runs as part of the OS) +- Accessing the Linux kernel shell directly over SSH +- Deep system-level debugging that the BrightScript shell cannot reach + +**When you do NOT need this:** +- BrightScript development +- HTML5/JavaScript/Node.js development +- Standard DWS, SSH, or serial console access + +### Critical Caveats + +> **This action is irreversible under all circumstances.** Once secure boot is disabled, it cannot be re-enabled — not by factory reset, not by OS update, not by any means. The player will permanently operate in an insecure state. + +> **This voids the player's warranty.** Use a dedicated development unit, not a production device. + +> **SSH disables serial console access.** When SSH is enabled, the serial port no longer provides an interactive console. All future console access must happen over SSH. + +> **Developer settings (console, script debug) reset on OS update or factory reset.** The insecure state persists, but you will need to re-enable console and script debug after any OS update. + +> **The SVC button only works without an SD card.** With an SD card inserted, SVC will not drop into the debugger or shell unless `autorun.brs` contains only `end`. + +### Step 1: Enable the BrightSign Console + +The console must be enabled before secure boot can be disabled. It enables shell, debugger, and log access over the serial port. + +1. Connect your serial cable and open a terminal session (115200 baud, 8N1). +2. **Remove any SD card from the player.** +3. Power the player off. +4. **Hold the SVC button** while applying power. Within 2–5 seconds you will see: + ``` + Hit key to stop autoboot (CTRL+C): 3 + ``` +5. **Press Ctrl-C** within the countdown. You will land at a bootloader prompt — one of: + ``` + bolt> + secure> + insecure> + ``` +6. Enable the console persistently: + ``` + console on + reboot + ``` + +> Holding SVC enables console for one boot only. `console on` + `reboot` makes it permanent until `console off` is run or a factory reset is performed. + +### Step 2: Disable Secure Boot + +1. With **no SD card inserted**, power cycle while holding SVC. +2. Press **Ctrl-C** at the countdown. +3. At the bootloader prompt, try the primary command (use whichever does not error): + ``` + disable_secure_boot + ``` + or + ``` + set env insecure + ``` +4. Confirm the irreversible action when prompted. +5. Reboot: + ``` + reboot + ``` + +**Fallback** — if the above commands error, use the environment variable approach: +``` +setenv SECURE_CHECKS 0 +saveenv +boot +``` + +### Step 3: Enable the BrightScript Debugger + +1. Allow the player to boot fully with **no SD card inserted**. Wait 30–60 seconds after the splash screen. +2. Give a single firm press of the **SVC button** (do not hold). You should land at: + ``` + BrightSign> + ``` + If you land in a debugger prompt instead, type `exit` first. +3. Enable script debug: + ``` + script debug on + reboot + ``` + +### Step 4: Verify Insecure Status + +1. With **no SD card inserted**, let the player boot fully (30–60 seconds after splash). +2. Press **SVC** once. +3. If you see a Linux shell prompt (`#` or `$`) after typing `exit` from the BrightSign prompt, the player is confirmed insecure. ✅ +4. If the player **reboots** instead, revisit Step 2 using the `setenv SECURE_CHECKS 0` fallback. + +### Development Mode autorun.brs + +Once insecured, use this `autorun.brs` to configure a player for development in a single boot. It enables all the tools you need — DWS (unauthenticated), SSH, BrightScript debug, curl debug, and Prometheus metrics — then displays a message prompting for a manual reboot to apply registry changes. + +```brightscript +Sub Main() + ' Enable BrightScript debug mode + regB = CreateObject("roRegistrySection", "brightscript") + regB.Write("debug", "1") + regB.Flush() + + reg = CreateObject("roRegistrySection", "networking") + reg.Write("bbhf", "on") + ' Enable Local DWS + reg.Write("dwse", "yes") + ' Enable curl verbose logging for HTTP debugging + reg.Write("curl_debug", "1") + ' Expose Prometheus node exporter for metrics scraping + reg.Write("prometheus-node-exporter-port", "9100") + ' Enable SSH on port 22 + reg.Write("ssh", "22") + ' Maximum telnet log verbosity + reg.Write("telnet_log_level", "7") + reg.Flush() + + ' Open DWS on port 80 with no authentication + CreateObject("roNetworkConfiguration", 0).SetupDWS({port: "80", open: "none"}) + + ' Set SSH/shell login password to empty + n = CreateObject("roNetworkConfiguration", 0) + n.SetLoginPassword("none") + n.Apply() + + ShowMessage("Setup complete -- manually reboot the player to apply settings") + + sleep(50000) +End Sub + +Sub ShowMessage(msg) + gaa = GetGlobalAA() + + videoMode = CreateObject("roVideoMode") + resX = videoMode.GetResX() + resY = videoMode.GetResY() + videoMode = invalid + + r = CreateObject("roRectangle", 0, resY / 2 - resY / 64, resX, resY / 32) + + twParams = CreateObject("roAssociativeArray") + twParams.LineCount = 1 + twParams.TextMode = 2 + twParams.Rotation = 0 + twParams.Alignment = 1 + + gaa.tw = CreateObject("roTextWidget", r, 1, 2, twParams) + gaa.tw.PushString(msg) + gaa.tw.Show() + + print msg +End Sub +``` + +> **Security note:** This autorun sets DWS to unauthenticated and SSH to no password. Use only on isolated development networks — never on production or shared infrastructure. + +After running this autorun and manually rebooting, the player will have: +- Local DWS accessible at `http:///` with no login +- SSH accessible at port 22 with no password +- BrightScript debug and verbose logging enabled +- Prometheus metrics at port 9100 ---