Conversation
WalkthroughThis PR introduces a complete token rotation system for the FixedIT Data Agent. It includes documentation, Telegraf configurations for metrics input/output, a shell script for token-authenticated HTTP requests, a Flask test server for token generation and metrics endpoints, and configurations for periodic token updates with persistent token storage. Changes
Sequence Diagram(s)sequenceDiagram
participant Device
participant Server as Example Server
participant Store as Token Store
rect rgb(200, 220, 240)
Note over Device,Server: Bootstrap & Initial Registration
Device->>Server: POST /generate-token<br/>(BOOTSTRAP_TOKEN)
Server->>Server: Extract device serial from header
Server->>Server: Validate bootstrap token
Server->>Store: Generate UUID token for device
Server->>Server: Store token mapping
Server-->>Device: JSON: [serial, token]
Device->>Device: Persist token to token.txt
end
rect rgb(220, 240, 200)
Note over Device,Server: Metrics Submission & Token Refresh
Device->>Server: POST /metrics<br/>(Bearer: current_token)
Server->>Server: Authenticate using generated token
Server-->>Device: 200 Success
Device->>Device: Every 5s: Request new token
Device->>Server: POST /generate-token<br/>(Bearer: current_token)
Server->>Store: Generate new UUID token
Server-->>Device: JSON: [serial, new_token]
Device->>Device: Atomically update token.txt
end
rect rgb(240, 220, 200)
Note over Device,Server: Retry on Auth Failure
Device->>Server: POST /metrics<br/>(Bearer: stale_token)
Server-->>Device: 401 Unauthorized
Device->>Device: Retry with bootstrap token
Device->>Server: POST /metrics<br/>(Bearer: BOOTSTRAP_TOKEN)
Server->>Server: Generate new token for device
Server-->>Device: 200 Success
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (1 inconclusive)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
🎨 Code Formatting RequiredTrailing Whitespace IssuesSome files have trailing whitespace (spaces/tabs at end of lines). Files with trailing whitespace:
To fix: Remove the trailing spaces/tabs at the end of lines in your favorite editor. After fixing:
|
1088e09 to
0236735
Compare
|
|
@coderabbitai full review |
✅ Actions performedFull review triggered. |
| BOOTSTRAP_TOKEN = "bootstrap-key-12345" | ||
|
|
||
| # Dictionary to store generated tokens for each device (serial -> token) | ||
| device_tokens = {} |
There was a problem hiding this comment.
Bug: Token Rotation Race Leads to Authentication Errors
The device_tokens dictionary stores only one token per device, but the device keeps the last 2 tokens in its file. When token rotation occurs, the server immediately invalidates the previous token, creating a race condition where metrics sent with the old token (still valid on the device side) get rejected. The server should maintain at least the last 2 tokens per device to match the device's token file behavior and prevent authentication failures during rotation.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (6)
project-token-rotation/output.conf (1)
4-9: Prefer passing script and arguments directly instead ofsh -cUsing
sh -cis slightly more brittle (quoting, shell parsing). You can let Telegraf exec the script directly:-[[outputs.exec]] - # Send all metrics except from the metric in the 'update_token.conf' file. - namedrop = ["device_token"] - # Send the metrics as JSON to the script. - command = ["sh", "-c", "${HELPER_FILES_DIR}/send_http_request.sh --url http://${SERVER_URL}:8000/metrics --send-metrics"] - data_format = "json" +[[outputs.exec]] + # Send all metrics except from the metric in the 'update_token.conf' file. + namedrop = ["device_token"] + # Send the metrics as JSON to the script. + command = ["${HELPER_FILES_DIR}/send_http_request.sh", "--url", "http://${SERVER_URL}:8000/metrics", "--send-metrics"] + data_format = "json"This avoids an extra shell layer and handles spaces in paths/args more predictably.
project-token-rotation/README.md (1)
1-6: Align README with template and product-link / messaging guidelinesA few small tweaks will make this README match the shared guidelines:
- First mention of the FixedIT Data Agent should link to the product page.
- The "here" link text should be descriptive (markdownlint MD059).
- The template expects clearer intro/background + troubleshooting/developer-notes and explicit “no C/C++ development required” messaging.
For example:
-# Example of token rotation for the FixedIT Data Agent +# Example of token rotation for the [FixedIT Data Agent](https://fixedit.ai/products-data-agent/)-This project was created during a screencast that can be found [here](https://www.youtube.com/watch?v=zeWdbDNvj_w). +This project was created during a screencast, available on [YouTube](https://www.youtube.com/watch?v=zeWdbDNvj_w).And before merging, consider:
- Replacing
Minimum AXIS OS version: TBDwith a concrete version once you’ve verified theshcompatibility.- Adding brief troubleshooting and developer-oriented notes as per
.project_readme_template.md(host testing limits, how to extend the example, “no C/C++ development required” note).Also applies to: 24-36, 52-62
project-token-rotation/send_http_request.sh (2)
7-17: Tidy exit codes and clarify environment-variable expectationsA couple of small cleanups would make this script easier to maintain and use:
EXIT_EMPTY_TOKEN_FILEandEXIT_FAILED_TO_PARSE_TOKENare never used; either wire them intoget_tokenfor more granular errors or drop them to avoid confusion.- At the top of the file, it helps to explicitly document the required environment variables (even though you validate them later), per the project shell guidelines.
- Currently
SERVER_URLis only used for the “not set” check; the actual URL always comes from--url. Either mention that in the comments or consider usingSERVER_URLto build the default URL so there is a single source of truth.Example header improvement:
-#!/bin/sh -# Script to make HTTP requests with token-based authentication -# Supports two modes: +#!/bin/sh +# Script to make HTTP requests with token-based authentication +# +# Environment variables: +# SERVER_URL - base URL of the example server (used for validation) +# DEVICE_PROP_SERIAL - device serial sent as the 'serial' HTTP header +# HELPER_FILES_DIR - directory where token.txt is stored +# +# Supports two modes:Also applies to: 176-190
24-50: Improve POSIX portability and avoid logging full token valuesTwo related points around portability and secrecy:
- The functions (
get_token,send_http_request,make_request_with_retry,handle_response) all uselocal, which is not defined in pure POSIXsh. The README already notes this, but for maximum AXIS OS compatibility it’s better to avoidlocaland instead use function-specific global variable names.get_tokenand later logs print the full bearer token in debug messages, which can leak credentials into logs even in a “demo” setup.You can keep behavior but address both concerns like this for
get_token(and apply the same pattern to other functions):-get_token() { - local token_file="${HELPER_FILES_DIR}/token.txt" - local token="" +get_token() { + get_token_token_file="${HELPER_FILES_DIR}/token.txt" + get_token_token="" @@ - echo "[DEBUG] Looking for token file at: $token_file" >&2 + echo "[DEBUG] Looking for token file at: $get_token_token_file" >&2 @@ - if [ -f "$token_file" ] && [ -s "$token_file" ]; then + if [ -f "$get_token_token_file" ] && [ -s "$get_token_token_file" ]; then @@ - token=$(tail -n 1 "$token_file" | jq -r '.fields.token' 2>/dev/null) - if [ -n "$token" ] && [ "$token" != "null" ]; then - echo "[DEBUG] Token found: $(echo "$token")..." >&2 - echo "$token" + get_token_token=$(tail -n 1 "$get_token_token_file" | jq -r '.fields.token' 2>/dev/null) + if [ -n "$get_token_token" ] && [ "$get_token_token" != "null" ]; then + echo "[DEBUG] Token found in file" >&2 + echo "$get_token_token"Similarly, you can change the later debug lines to avoid echoing the token itself:
-echo "[DEBUG] Final token: $(echo "$TOKEN")..." >&2 +echo "[DEBUG] Final token selected (from file or default token)" >&2This keeps debugging useful without putting sensitive values into logs while improving /bin/sh portability.
Also applies to: 52-79, 83-101, 106-126, 193-210
project-token-rotation/test_scripts/example_server.py (2)
91-115: Avoid unusedserialvariables in decoratorsBoth decorators unpack
serialfrom the auth helpers but never use it, which triggers linters and adds noise:def require_generated_token(f): @@ - def decorated(*args, **kwargs): - is_valid, message, serial = authenticate_generated_token_only() + def decorated(*args, **kwargs): + is_valid, message, _serial = authenticate_generated_token_only() @@ def require_bootstrap_or_token(f): @@ - def decorated(*args, **kwargs): - is_valid, message, serial = authenticate_bootstrap_or_generated() + def decorated(*args, **kwargs): + is_valid, message, _serial = authenticate_bootstrap_or_generated()This keeps the auth helper signatures intact while satisfying Ruff.
12-14: Clarify demo-only choices for bootstrap token and server exposureGiven this is a demo server, a couple of points are worth calling out explicitly:
BOOTSTRAP_TOKENis hardcoded in the script and referenced in the README. That’s fine for an example, but for any non-demo use you’ll want to pass this via environment/config instead.app.run(host="0.0.0.0", port=8000, debug=False)binds to all interfaces. Again, this is convenient for local testing, but in real deployments you’d normally rely on a fronting reverse proxy or restrict access with firewall rules.- The broad
except Exceptionblocks inreceive_metricsandgenerate_tokenare acceptable for a small example, but if this grows it may be worth catching narrower exceptions or logging full tracebacks.No changes are strictly required for the example, but adding a short comment in the file or README that these are “demo defaults, not production settings” would help set expectations.
Also applies to: 143-165, 167-179
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (1)
project-token-rotation/.images/diagram.pngis excluded by!**/*.png
📒 Files selected for processing (6)
project-token-rotation/README.md(1 hunks)project-token-rotation/inputs.conf(1 hunks)project-token-rotation/output.conf(1 hunks)project-token-rotation/send_http_request.sh(1 hunks)project-token-rotation/test_scripts/example_server.py(1 hunks)project-token-rotation/update_token.conf(1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
project-*/**
⚙️ CodeRabbit configuration file
project-*/**: Each example project for the FixedIT Data Agent should be self-contained and designed as a standalone example / tutorial showing how to use the FixedIT Data Agent. The project should be well documented and include all the necessary files to run the example. A reader should be able to understand the background and the purpose of the project, and be able to understand the implementation details and how to use it. If the project includes a dashboard, it should be placed in a new folder in the repo-root/dashboard-deployments/ directory instead of being included in the project folder. In the README for the project, the first time the FixedIT Data Agent is mentioned, we should link to the product page: https://fixedit.ai/products-data-agent/ Emphasize that these solutions are accessible to system integrators and IT professionals without requiring complex C/C++ ACAP development. Highlight that custom edge intelligence can be implemented using familiar IT tools (configuration files, shell scripts, REST APIs) rather than traditional embedded programming approaches. Note that bash does not exist on the Axis devices, so .sh scripts should be written in portable shell syntax.
README Structure: All project README files should follow the standardized template at.project_readme_template.md. This template ensures consistent documentation that serves both audiences: (1) IT professionals who want plug-and-play solutions, and (2) developers who want to customize and extend the examples. The template enforces a logical structure: 1) Introduction and background (project purpose, how it works, why choose this approach) 2) Technical explanation and quick start guide (compatibility, setup, troubleshooting) 3) Developer-oriented notes (local testing, implementation details)
Key README Requirements: - Include compatibility sections for both AXIS OS and FixedIT Data Agent versions - Explain host testing and its limitations and why certain parts require actual Axis d...
Files:
project-token-rotation/README.mdproject-token-rotation/output.confproject-token-rotation/inputs.confproject-token-rotation/send_http_request.shproject-token-rotation/update_token.confproject-token-rotation/test_scripts/example_server.py
**/*.md
⚙️ CodeRabbit configuration file
**/*.md: Documentation files should clearly communicate the dual audience: (1) server-side dashboard users who want to keep agent with bundled configs, and (2) edge device developers who want to customize agent behavior. Ensure examples and instructions are appropriate for the intended skill level and use case. Since this is a public repository, we should not include any sensitive information, the instructions should be easily understandable for a wide audience, and we should avoid using any jargon or technical terms that are not commonly used.
Headings should not include special characters like emojis or backticks. The table of contents should be generated with themarkdown-toctool, meaning that we should use the<!-- toc -->and<!-- tocstop -->tags.
Files:
project-token-rotation/README.md
**/*
⚙️ CodeRabbit configuration file
This repository serves FixedIT Data Agent users across a spectrum from plug-and-play dashboard deployment to advanced edge device customization. Consider whether changes maintain accessibility for both DevOps professionals (server-side focus) and developers (edge customization focus). If new features are added or existing ones changed significantly, ensure documentation clearly explains the intended audience and usage level. We use prettier for formatting of common file formats like markdown, yaml, json, etc. Example projects should be placed in the repo-root in a directory named project-*/. Whenever referencing Axis device model names like "D4100-VE mk II" or "M3045", prefix it with "AXIS" in capital letters (e.g. "AXIS D4100-VE mk II"). When using Axis as a company name, use "Axis Communications", note that all-caps is only used when "AXIS" is used in their product names, not their company name. When using the name of an Axis product, google on the name to verify that it is correctly identified. Avoid using 'cameras' or 'Axis cameras' unless the solution in related to visual analytics, otherwise prefer using 'Axis devices' to show that the FixedIT Data Agent also works with strobes, speakers, door stations, etc. Images used for the README should be placed in a directory called .images/ in the affected project folder. These images might also be referred to from other sources like the top-level README.md file.
Files:
project-token-rotation/README.mdproject-token-rotation/output.confproject-token-rotation/inputs.confproject-token-rotation/send_http_request.shproject-token-rotation/update_token.confproject-token-rotation/test_scripts/example_server.py
project-*/**/*.sh
⚙️ CodeRabbit configuration file
Shell scripts should be written in portable shell (/bin/sh) syntax since bash is not installed on the Axis devices. Since
localis not supported in all cameras (like AXIS OS 9.80), it should be avoided unless the AXIS OS limitations are explicitly mentioned. If not usinglocal, it can be good to use a function specific prefix for the variable names to avoid conflicts with other variables. Clearly document in the head of the file which environment variables they expect and if they are optional or required. It's normally good to useset -eufor stricter error handling, but-o pipefailis not supported in the Axis devices' shell.
Files:
project-token-rotation/send_http_request.sh
🧠 Learnings (2)
📓 Common learnings
Learnt from: daniel-falk
Repo: fixedit-ai/fixedit-data-agent-examples PR: 18
File: dashboard-deployments/system-monitoring-influxdb2-flux-grafana/provisioning/dashboards/overview_of_devices.json:1121-1130
Timestamp: 2025-09-03T14:18:52.406Z
Learning: When fixing unit mismatches in Grafana dashboards, daniel-falk prefers changing the panel unit configuration to match the data rather than transforming the query values, choosing simplicity over data conversion when both approaches are valid.
📚 Learning: 2025-10-15T11:36:24.102Z
Learnt from: daniel-falk
Repo: fixedit-ai/fixedit-data-agent-examples PR: 24
File: project-hello-world/README.md:156-179
Timestamp: 2025-10-15T11:36:24.102Z
Learning: The hello-world project (project-hello-world) for the FixedIT Data Agent has no differences between host and device behavior. It works identically on both because it only uses basic echo commands and standard Telegraf features.
Applied to files:
project-token-rotation/inputs.conf
🪛 ast-grep (0.39.9)
project-token-rotation/test_scripts/example_server.py
[warning] 178-178: Running flask app with host 0.0.0.0 could expose the server publicly.
Context: app.run(host="0.0.0.0", port=8000, debug=False)
Note: [CWE-668]: Exposure of Resource to Wrong Sphere [OWASP A01:2021]: Broken Access Control [REFERENCES]
https://owasp.org/Top10/A01_2021-Broken_Access_Control
(avoid_app_run_with_bad_host-python)
🪛 markdownlint-cli2 (0.18.1)
project-token-rotation/README.md
5-5: Link text should be descriptive
(MD059, descriptive-link-text)
🪛 Ruff (0.14.4)
project-token-rotation/test_scripts/example_server.py
1-1: Shebang is present but file is not executable
(EXE001)
13-13: Possible hardcoded password assigned to: "BOOTSTRAP_TOKEN"
(S105)
96-96: Unpacked variable serial is never used
Prefix it with an underscore or any other dummy variable pattern
(RUF059)
109-109: Unpacked variable serial is never used
Prefix it with an underscore or any other dummy variable pattern
(RUF059)
137-137: Consider moving this statement to an else block
(TRY300)
138-138: Do not catch blind exception: Exception
(BLE001)
161-161: Consider moving this statement to an else block
(TRY300)
162-162: Do not catch blind exception: Exception
(BLE001)
179-179: Possible binding to all interfaces
(S104)
🪛 Shellcheck (0.11.0)
project-token-rotation/send_http_request.sh
[warning] 10-10: EXIT_EMPTY_TOKEN_FILE appears unused. Verify use (or export if used externally).
(SC2034)
[warning] 11-11: EXIT_FAILED_TO_PARSE_TOKEN appears unused. Verify use (or export if used externally).
(SC2034)
[warning] 28-28: In POSIX sh, 'local' is undefined.
(SC3043)
[warning] 29-29: In POSIX sh, 'local' is undefined.
(SC3043)
[warning] 55-55: In POSIX sh, 'local' is undefined.
(SC3043)
[warning] 56-56: In POSIX sh, 'local' is undefined.
(SC3043)
[warning] 57-57: In POSIX sh, 'local' is undefined.
(SC3043)
[warning] 58-58: In POSIX sh, 'local' is undefined.
(SC3043)
[warning] 84-84: In POSIX sh, 'local' is undefined.
(SC3043)
[warning] 85-85: In POSIX sh, 'local' is undefined.
(SC3043)
[warning] 86-86: In POSIX sh, 'local' is undefined.
(SC3043)
[warning] 87-87: In POSIX sh, 'local' is undefined.
(SC3043)
[warning] 88-88: In POSIX sh, 'local' is undefined.
(SC3043)
[warning] 107-107: In POSIX sh, 'local' is undefined.
(SC3043)
[warning] 108-108: In POSIX sh, 'local' is undefined.
(SC3043)
[warning] 109-109: In POSIX sh, 'local' is undefined.
(SC3043)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Cursor Bugbot
🔇 Additional comments (2)
project-token-rotation/inputs.conf (1)
1-24: Mock inputs config looks good for the exampleThe agent, tags, and mock inputs (exec + sine_wave) are coherent and match the described token-rotation test flow. No changes needed here.
project-token-rotation/update_token.conf (1)
4-39: Token rotation workflow and atomic token.txt update look solidThe exec input/output wiring, JSON parsing, and
{ tail ...; cat; } > tmp && mvpattern correctly implement:
- Fetching a new token every 5s via
/generate-token.- Emitting a
device_tokenmetric withserialandtokenfields.- Maintaining a small JSONL
token.txtfile with the previous + latest token via an atomic move.- Ensuring downstream readers see updates quickly (
flush_interval = "1s",metric_batch_size = 1,use_batch_format = false).No changes needed here.
| From the start, all devices share a bootstrapping key. The first time a new device connects to the server, it will be issued a new token. After this, the bootstrap token will no longer be accepted by the server for this particular device. | ||
|
|
||
| The device has a workflow that will request the latest active token from the server's `/generate-token` endpoint every 5 seconds. The workflow will then save this token to the `${HELPER_FILES_DIR}/token.txt` file. | ||
|
|
||
| All the workflows in the device that are pushing data to the server will make use of the `send_metrics.sh` script which will read the token from the `${HELPER_FILES_DIR}/token.txt` file and use it to authenticate the request. | ||
|
|
There was a problem hiding this comment.
🧩 Analysis chain
Fix script name in authentication flow description
The script referenced in the auth-flow description doesn’t match the actual filename:
-All the workflows in the device that are pushing data to the server will make use of the `send_metrics.sh` script which will read the token from the `${HELPER_FILES_DIR}/token.txt` file and use it to authenticate the request.
+All the workflows in the device that are pushing data to the server will make use of the `send_http_request.sh` script which will read the token from the `${HELPER_FILES_DIR}/token.txt` file and use it to authenticate the request.Without this change, readers might look for a non-existent send_metrics.sh.
I'll verify the script name claim by checking what files actually exist in the project and which scripts are referenced throughout the codebase.
---
🏁 Script executed:
```shell
#!/bin/bash
# Find all shell scripts in project-token-rotation directory
find project-token-rotation -name "*.sh" -type f | head -20
Length of output: 43
🏁 Script executed:
#!/bin/bash
# Search for references to both script names in the project
echo "=== Searching for send_metrics.sh references ==="
rg -n "send_metrics\.sh" project-token-rotation/ || echo "No matches found"
echo ""
echo "=== Searching for send_http_request.sh references ==="
rg -n "send_http_request\.sh" project-token-rotation/ || echo "No matches found"Length of output: 1202
🏁 Script executed:
#!/bin/bash
# Check the content of the README to see the actual lines mentioned
sed -n '40,60p' project-token-rotation/README.mdLength of output: 2006
Fix script name in authentication flow description
The verification confirms the issue: the only script in the project is send_http_request.sh, but line 50 of README.md incorrectly references send_metrics.sh. The correct script name is used elsewhere in the documentation (lines 42, 58) and in the configuration files.
-All the workflows in the device that are pushing data to the server will make use of the `send_metrics.sh` script which will read the token from the `${HELPER_FILES_DIR}/token.txt` file and use it to authenticate the request.
+All the workflows in the device that are pushing data to the server will make use of the `send_http_request.sh` script which will read the token from the `${HELPER_FILES_DIR}/token.txt` file and use it to authenticate the request.📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| From the start, all devices share a bootstrapping key. The first time a new device connects to the server, it will be issued a new token. After this, the bootstrap token will no longer be accepted by the server for this particular device. | |
| The device has a workflow that will request the latest active token from the server's `/generate-token` endpoint every 5 seconds. The workflow will then save this token to the `${HELPER_FILES_DIR}/token.txt` file. | |
| All the workflows in the device that are pushing data to the server will make use of the `send_metrics.sh` script which will read the token from the `${HELPER_FILES_DIR}/token.txt` file and use it to authenticate the request. | |
| From the start, all devices share a bootstrapping key. The first time a new device connects to the server, it will be issued a new token. After this, the bootstrap token will no longer be accepted by the server for this particular device. | |
| The device has a workflow that will request the latest active token from the server's `/generate-token` endpoint every 5 seconds. The workflow will then save this token to the `${HELPER_FILES_DIR}/token.txt` file. | |
| All the workflows in the device that are pushing data to the server will make use of the `send_http_request.sh` script which will read the token from the `${HELPER_FILES_DIR}/token.txt` file and use it to authenticate the request. |
🤖 Prompt for AI Agents
In project-token-rotation/README.md around lines 46 to 51, the README
incorrectly references send_metrics.sh in the device authentication flow while
the actual script is send_http_request.sh; update the sentence to use the
correct script name (send_http_request.sh) so it matches the rest of the docs
and configuration, and verify no other references in this block remain
incorrect.

Note
Introduces a token-rotation example with a Flask server, Telegraf configs, and a helper script to rotate tokens and send metrics securely.
project-token-rotation/):test_scripts/example_server.py):POST /generate-token(accepts bootstrap or existing token, issues new UUID token) andPOST /metrics(requires generated token).serialheader) and auth decorators for bootstrap vs. generated tokens.send_http_request.sh):serialheader, JSON body; retries on 401 using--default-token.${HELPER_FILES_DIR}/token.txt(JSONL), robust error handling and exit codes.inputs.conf: mock metrics (exec hello, sine wave) at 1s.output.conf: sends metrics as JSON via helper script tohttp://${SERVER_URL}:8000/metrics, dropsdevice_token.update_token.conf: every 5s calls/generate-token, stores latest token to${HELPER_FILES_DIR}/token.txtand logs to stdout.README.md):Written by Cursor Bugbot for commit 0236735. This will update automatically on new commits. Configure here.
Summary by CodeRabbit
Documentation
New Features