diff --git a/src/pages/help/troubleshooting-client.mdx b/src/pages/help/troubleshooting-client.mdx index 12abc84b..2d42e9b8 100644 --- a/src/pages/help/troubleshooting-client.mdx +++ b/src/pages/help/troubleshooting-client.mdx @@ -187,6 +187,60 @@ issue that recovers when restarting the client.

Once the bundle generation is complete, you can click on `Copy Key` to get the uploaded key and share with NetBird\'s team. +### Remote debug bundle generation + +Administrators can remotely request debug bundles from peer clients through the Management API or Dashboard. This is +particularly useful when troubleshooting issues on remote machines where local access is limited or when working with +end-users who may not be familiar with command-line tools. + +When a remote debug bundle is requested: +1. The management server sends a job request to the target peer +2. The peer client receives the job and generates the debug bundle automatically +3. The generated bundle is uploaded to a centralized location +4. The administrator receives the upload key to access the bundle + +#### Using the Management API + +You can also trigger remote debug bundles programmatically via the Management API. + +See the [Peers API documentation](/ipa/resources/peers#create-peer-debug-bundle-job) for complete API reference, including: +- Creating debug bundle jobs +- Listing all jobs for a peer +- Getting job status and upload keys + +#### Using the Dashboard + +You can trigger remote debug bundles directly from the NetBird Dashboard without requiring CLI access. + +**To generate a remote debug bundle:** + +1. Navigate to **Peers** in the dashboard +2. Click on the peer you want to troubleshoot +3. Click the **Run Remote Job** button (the peer must be online and connected) +4. Select **Debug Bundle** from the dropdown menu +5. Configure the debug bundle options: + - **Log File Count**: Number of log files to include (1-50, default: 10) + - **Enable Bundle Duration** (optional): Collect logs for a specific time period (1-5 minutes) before generating the bundle + - **Anonymize Log Data**: Remove sensitive information like IP addresses and domains +6. Click **Create Debug Bundle** + +**Viewing job status and results:** + +Once triggered, the job appears in the **Remote Jobs** section on the peer details page. The table shows: +- **Type**: The job type (Debug Bundle) +- **Status**: Pending (yellow), Completed (green), or Failed (red) +- **Created/Updated**: Timestamps for job lifecycle +- **Output**: Once completed, displays the upload key that you can copy and share with NetBird support + +The upload key is automatically copyable by clicking on it. Share this key through GitHub Issues, Slack, or support channels. + +#### Limitations + +- The peer must be online and connected to the management server to receive the job +- Debug bundle generation may take a few seconds to a few minutes depending on log size and system information +- Bundles are automatically uploaded to NetBird's secure storage (or your configured upload endpoint for self-hosted deployments) +- Upload keys expire after 30 days for security + ## Enabling debug logs on agent Logs can be temporarily set using the following command. diff --git a/src/pages/ipa/resources/peers.mdx b/src/pages/ipa/resources/peers.mdx index a8667fb1..d3d19a96 100644 --- a/src/pages/ipa/resources/peers.mdx +++ b/src/pages/ipa/resources/peers.mdx @@ -1597,8 +1597,807 @@ echo $response; } ``` - - + + + + + +--- + + +## Create Peer Debug Bundle Job {{ tag: 'POST' , label: '/api/peers/{peerId}/jobs' }} + + + + Triggers a remote debug bundle generation on the specified peer. The peer must be online and connected to receive + the job. Once generated, the bundle is automatically uploaded to the configured storage location. + + ### Path Parameters + + + + The unique identifier of a peer + + + + ### Request-Body Parameters + + + + The job workload configuration + + + + + #### Workload Object + + + + Job type. Use "bundle" for debug bundle generation + + + + + Job-specific parameters + + + + + #### Parameters Object (for debug bundle) + + + + Anonymize IP addresses and non-netbird.io domains in logs and status output (default: false) + + + + + Enable time-based log collection before generating bundle (default: false) + + + + + Duration in minutes for log collection (1-5 minutes). Only used if bundle_for is true + + + + + Number of log files to include (1-1000, default: 10) + + + + + + + + + +```bash {{ title: 'cURL' }} +curl -X POST https://api.netbird.io/api/peers/{peerId}/jobs \ +-H 'Accept: application/json' \ +-H 'Content-Type: application/json' \ +-H 'Authorization: Token ' \ +--data-raw '{ + "workload": { + "type": "bundle", + "parameters": { + "anonymize": true, + "bundle_for": true, + "bundle_for_time": 2, + "log_file_count": 10 + } + } +}' +``` + +```js +const axios = require('axios'); +let data = JSON.stringify({ + "workload": { + "type": "bundle", + "parameters": { + "anonymize": true, + "bundle_for": true, + "bundle_for_time": 2, + "log_file_count": 10 + } + } +}); +let config = { + method: 'post', + maxBodyLength: Infinity, + url: '/api/peers/{peerId}/jobs', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'Authorization': 'Token ' + }, + data : data +}; + +axios(config) +.then((response) => { + console.log(JSON.stringify(response.data)); +}) +.catch((error) => { + console.log(error); +}); +``` + +```python +import requests +import json + +url = "https://api.netbird.io/api/peers/{peerId}/jobs" +payload = json.dumps({ + "workload": { + "type": "bundle", + "parameters": { + "anonymize": True, + "bundle_for": True, + "bundle_for_time": 2, + "log_file_count": 10 + } + } +}) +headers = { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'Authorization': 'Token ' +} + +response = requests.request("POST", url, headers=headers, data=payload) + +print(response.text) +``` + +```go +package main + +import ( + "fmt" + "strings" + "net/http" + "io/ioutil" +) + +func main() { + + url := "https://api.netbird.io/api/peers/{peerId}/jobs" + method := "POST" + + payload := strings.NewReader(`{ + "workload": { + "type": "bundle", + "parameters": { + "anonymize": true, + "bundle_for": true, + "bundle_for_time": 2, + "log_file_count": 10 + } + } +}`) + client := &http.Client { + } + req, err := http.NewRequest(method, url, payload) + + if err != nil { + fmt.Println(err) + return + { + + req.Header.Add("Content-Type", "application/json") + req.Header.Add("Accept", "application/json") + req.Header.Add("Authorization", "Token ") + + res, err := client.Do(req) + if err != nil { + fmt.Println(err) + return + } + defer res.Body.Close() + + body, err := ioutil.ReadAll(res.Body) + if err != nil { + fmt.Println(err) + return + } + fmt.Println(string(body)) +} +``` + +```ruby +require "uri" +require "json" +require "net/http" + +url = URI("https://api.netbird.io/api/peers/{peerId}/jobs") + +https = Net::HTTP.new(url.host, url.port) +https.use_ssl = true + +request = Net::HTTP::Post.new(url) +request["Content-Type"] = "application/json" +request["Accept"] = "application/json" +request["Authorization"] = "Token " + +request.body = JSON.dump({ + "workload": { + "type": "bundle", + "parameters": { + "anonymize": true, + "bundle_for": true, + "bundle_for_time": 2, + "log_file_count": 10 + } + } +}) +response = https.request(request) +puts response.read_body +``` + +```java +OkHttpClient client = new OkHttpClient().newBuilder() + .build(); +MediaType mediaType = MediaType.parse("application/json"); +RequestBody body = RequestBody.create(mediaType, '{ + "workload": { + "type": "bundle", + "parameters": { + "anonymize": true, + "bundle_for": true, + "bundle_for_time": 2, + "log_file_count": 10 + } + } +}'); +Request request = new Request.Builder() + .url("https://api.netbird.io/api/peers/{peerId}/jobs") + .method("POST", body) + .addHeader("Content-Type", "application/json") + .addHeader("Accept", "application/json") + .addHeader("Authorization: Token ") + .build(); +Response response = client.newCall(request).execute(); +``` + +```php + 'https://api.netbird.io/api/peers/{peerId}/jobs', + CURLOPT_RETURNTRANSFER => true, + CURLOPT_ENCODING => '', + CURLOPT_MAXREDIRS => 10, + CURLOPT_TIMEOUT => 0, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, + CURLOPT_CUSTOMREQUEST => 'POST', + CURLOPT_POSTFIELDS => '{ + "workload": { + "type": "bundle", + "parameters": { + "anonymize": true, + "bundle_for": true, + "bundle_for_time": 2, + "log_file_count": 10 + } + } +}', + CURLOPT_HTTPHEADER => array( + 'Content-Type: application/json', + 'Accept: application/json', + 'Authorization: Token ' + ), +)); + +$response = curl_exec($curl); + +curl_close($curl); +echo $response; +``` + + + + + +```json {{ title: 'Example' }} +{ + "id": "chacbco6lnnbn6cg5s91", + "peer_id": "chacbco6lnnbn6cg5s90", + "status": "pending", + "created_at": "2026-01-21T10:30:00.000Z", + "updated_at": "2026-01-21T10:30:00.000Z", + "workload": { + "type": "bundle", + "parameters": { + "anonymize": true, + "bundle_for": true, + "bundle_for_time": 2, + "log_file_count": 10 + } + } +} +``` +```json {{ title: 'Schema' }} +{ + "id": "string", + "peer_id": "string", + "status": "string", + "created_at": "string", + "updated_at": "string", + "workload": { + "type": "string", + "parameters": { + "anonymize": "boolean", + "bundle_for": "boolean", + "bundle_for_time": "number", + "log_file_count": "number" + }, + "result": { + "upload_key": "string" + } + } +} +``` + + + + + + +--- + + +## List Peer Jobs {{ tag: 'GET' , label: '/api/peers/{peerId}/jobs' }} + + + + Returns a list of all jobs for the specified peer, including debug bundle generation jobs. + + ### Path Parameters + + + + The unique identifier of a peer + + + + + + +```bash {{ title: 'cURL' }} +curl -X GET https://api.netbird.io/api/peers/{peerId}/jobs \ +-H 'Accept: application/json' \ +-H 'Authorization: Token ' +``` + +```js +const axios = require('axios'); + +let config = { + method: 'get', + maxBodyLength: Infinity, + url: '/api/peers/{peerId}/jobs', + headers: { + 'Accept': 'application/json', + 'Authorization': 'Token ' + } +}; + +axios(config) +.then((response) => { + console.log(JSON.stringify(response.data)); +}) +.catch((error) => { + console.log(error); +}); +``` + +```python +import requests +import json + +url = "https://api.netbird.io/api/peers/{peerId}/jobs" + +headers = { + 'Accept': 'application/json', + 'Authorization': 'Token ' +} + +response = requests.request("GET", url, headers=headers) + +print(response.text) +``` + +```go +package main + +import ( + "fmt" + "strings" + "net/http" + "io/ioutil" +) + +func main() { + + url := "https://api.netbird.io/api/peers/{peerId}/jobs" + method := "GET" + + client := &http.Client { + } + req, err := http.NewRequest(method, url, nil) + + if err != nil { + fmt.Println(err) + return + { + + req.Header.Add("Accept", "application/json") + req.Header.Add("Authorization", "Token ") + + res, err := client.Do(req) + if err != nil { + fmt.Println(err) + return + } + defer res.Body.Close() + + body, err := ioutil.ReadAll(res.Body) + if err != nil { + fmt.Println(err) + return + } + fmt.Println(string(body)) +} +``` + +```ruby +require "uri" +require "json" +require "net/http" + +url = URI("https://api.netbird.io/api/peers/{peerId}/jobs") + +https = Net::HTTP.new(url.host, url.port) +https.use_ssl = true + +request = Net::HTTP::Get.new(url) +request["Accept"] = "application/json" +request["Authorization"] = "Token " + +response = https.request(request) +puts response.read_body +``` + +```java +OkHttpClient client = new OkHttpClient().newBuilder() + .build(); + +Request request = new Request.Builder() + .url("https://api.netbird.io/api/peers/{peerId}/jobs") + .method("GET") + .addHeader("Accept", "application/json") + .addHeader("Authorization: Token ") + .build(); +Response response = client.newCall(request).execute(); +``` + +```php + 'https://api.netbird.io/api/peers/{peerId}/jobs', + CURLOPT_RETURNTRANSFER => true, + CURLOPT_ENCODING => '', + CURLOPT_MAXREDIRS => 10, + CURLOPT_TIMEOUT => 0, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, + CURLOPT_CUSTOMREQUEST => 'GET', + CURLOPT_HTTPHEADER => array( + 'Accept: application/json', + 'Authorization: Token ' + ), +)); + +$response = curl_exec($curl); + +curl_close($curl); +echo $response; +``` + + + + + +```json {{ title: 'Example' }} +[ + { + "id": "chacbco6lnnbn6cg5s91", + "peer_id": "chacbco6lnnbn6cg5s90", + "status": "succeeded", + "created_at": "2026-01-21T10:30:00.000Z", + "updated_at": "2026-01-21T10:31:15.000Z", + "workload": { + "type": "bundle", + "parameters": { + "anonymize": true, + "bundle_for": true, + "bundle_for_time": 2, + "log_file_count": 10 + }, + "result": { + "upload_key": "1234567890ab27fb37c88b3b4be7011e22aa2e5ca6f38ffa9c4481884941f726/12345678-90ab-cdef-1234-567890abcdef" + } + } + }, + { + "id": "chacbco6lnnbn6cg5s92", + "peer_id": "chacbco6lnnbn6cg5s90", + "status": "pending", + "created_at": "2026-01-21T11:00:00.000Z", + "updated_at": "2026-01-21T11:00:00.000Z", + "workload": { + "type": "bundle", + "parameters": { + "anonymize": false, + "log_file_count": 10 + } + } + } +] +``` +```json {{ title: 'Schema' }} +[ + { + "id": "string", + "peer_id": "string", + "status": "string", + "created_at": "string", + "updated_at": "string", + "workload": { + "type": "string", + "parameters": { + "anonymize": "boolean", + "bundle_for": "boolean", + "bundle_for_time": "number", + "log_file_count": "number" + }, + "result": { + "upload_key": "string" + } + } + } +] +``` + + + + + + +--- + + +## Get Peer Job {{ tag: 'GET' , label: '/api/peers/{peerId}/jobs/{jobId}' }} + + + + Returns details of a specific job for the peer, including the upload key if the debug bundle generation is complete. + + ### Path Parameters + + + + The unique identifier of a peer + + + The unique identifier of a job + + + + + + +```bash {{ title: 'cURL' }} +curl -X GET https://api.netbird.io/api/peers/{peerId}/jobs/{jobId} \ +-H 'Accept: application/json' \ +-H 'Authorization: Token ' +``` + +```js +const axios = require('axios'); + +let config = { + method: 'get', + maxBodyLength: Infinity, + url: '/api/peers/{peerId}/jobs/{jobId}', + headers: { + 'Accept': 'application/json', + 'Authorization': 'Token ' + } +}; + +axios(config) +.then((response) => { + console.log(JSON.stringify(response.data)); +}) +.catch((error) => { + console.log(error); +}); +``` + +```python +import requests +import json + +url = "https://api.netbird.io/api/peers/{peerId}/jobs/{jobId}" + +headers = { + 'Accept': 'application/json', + 'Authorization': 'Token ' +} + +response = requests.request("GET", url, headers=headers) + +print(response.text) +``` + +```go +package main + +import ( + "fmt" + "strings" + "net/http" + "io/ioutil" +) + +func main() { + + url := "https://api.netbird.io/api/peers/{peerId}/jobs/{jobId}" + method := "GET" + + client := &http.Client { + } + req, err := http.NewRequest(method, url, nil) + + if err != nil { + fmt.Println(err) + return + { + + req.Header.Add("Accept", "application/json") + req.Header.Add("Authorization", "Token ") + + res, err := client.Do(req) + if err != nil { + fmt.Println(err) + return + } + defer res.Body.Close() + + body, err := ioutil.ReadAll(res.Body) + if err != nil { + fmt.Println(err) + return + } + fmt.Println(string(body)) +} +``` + +```ruby +require "uri" +require "json" +require "net/http" + +url = URI("https://api.netbird.io/api/peers/{peerId}/jobs/{jobId}") + +https = Net::HTTP.new(url.host, url.port) +https.use_ssl = true + +request = Net::HTTP::Get.new(url) +request["Accept"] = "application/json" +request["Authorization"] = "Token " + +response = https.request(request) +puts response.read_body +``` + +```java +OkHttpClient client = new OkHttpClient().newBuilder() + .build(); + +Request request = new Request.Builder() + .url("https://api.netbird.io/api/peers/{peerId}/jobs/{jobId}") + .method("GET") + .addHeader("Accept", "application/json") + .addHeader("Authorization: Token ") + .build(); +Response response = client.newCall(request).execute(); +``` + +```php + 'https://api.netbird.io/api/peers/{peerId}/jobs/{jobId}', + CURLOPT_RETURNTRANSFER => true, + CURLOPT_ENCODING => '', + CURLOPT_MAXREDIRS => 10, + CURLOPT_TIMEOUT => 0, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, + CURLOPT_CUSTOMREQUEST => 'GET', + CURLOPT_HTTPHEADER => array( + 'Accept: application/json', + 'Authorization: Token ' + ), +)); + +$response = curl_exec($curl); + +curl_close($curl); +echo $response; +``` + + + + + +```json {{ title: 'Example' }} +{ + "id": "chacbco6lnnbn6cg5s91", + "peer_id": "chacbco6lnnbn6cg5s90", + "status": "succeeded", + "created_at": "2026-01-21T10:30:00.000Z", + "updated_at": "2026-01-21T10:31:15.000Z", + "workload": { + "type": "bundle", + "parameters": { + "anonymize": true, + "bundle_for": true, + "bundle_for_time": 2, + "log_file_count": 10 + }, + "result": { + "upload_key": "1234567890ab27fb37c88b3b4be7011e22aa2e5ca6f38ffa9c4481884941f726/12345678-90ab-cdef-1234-567890abcdef" + } + } +} +``` +```json {{ title: 'Schema' }} +{ + "id": "string", + "peer_id": "string", + "status": "string", + "created_at": "string", + "updated_at": "string", + "workload": { + "type": "string", + "parameters": { + "anonymize": "boolean", + "bundle_for": "boolean", + "bundle_for_time": "number", + "log_file_count": "number" + }, + "result": { + "upload_key": "string" + } + } +} +``` + + + diff --git a/src/pages/manage/activity/index.mdx b/src/pages/manage/activity/index.mdx index eb7183ea..36ac7c2c 100644 --- a/src/pages/manage/activity/index.mdx +++ b/src/pages/manage/activity/index.mdx @@ -23,7 +23,7 @@ The current version of NetBird tracks a wide range of network changes that occur
Click here to view the full list of tracked events - - **Peer Management:** + - **Peer Management:** - Peer added by user - Peer added with setup key - Peer removed by user @@ -33,6 +33,9 @@ The current version of NetBird tracks a wide range of network changes that occur - Peer login expiration enabled - Peer login expiration disabled + - **Remote Job Management:** + - Remote job created for peer + - **User Management:** - User joined - User invited