public inbox for linux-bluetooth@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH BlueZ v1 1/3] github: Add YAML issue template and btsnoop-analyzer workflow
@ 2026-04-15 18:22 Luiz Augusto von Dentz
  2026-04-15 18:22 ` [PATCH BlueZ v1 2/3] github: Make result posting robust against action failures Luiz Augusto von Dentz
                   ` (2 more replies)
  0 siblings, 3 replies; 4+ messages in thread
From: Luiz Augusto von Dentz @ 2026-04-15 18:22 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

Replace the markdown issue template with a structured YAML form that
includes fields for description, reproduction steps, btmon trace upload,
analysis focus area selection, and privacy consent checkboxes.

Add a btsnoop-analyzer workflow that automatically analyzes btsnoop
traces attached to new issues using Vudentz/btsnoop-analyzer action,
posting results as issue comments.
---
 .github/ISSUE_TEMPLATE/issue.md        |  41 -----
 .github/ISSUE_TEMPLATE/issue.yml       | 108 +++++++++++++
 .github/workflows/btsnoop-analyzer.yml | 202 +++++++++++++++++++++++++
 3 files changed, 310 insertions(+), 41 deletions(-)
 delete mode 100644 .github/ISSUE_TEMPLATE/issue.md
 create mode 100644 .github/ISSUE_TEMPLATE/issue.yml
 create mode 100644 .github/workflows/btsnoop-analyzer.yml

diff --git a/.github/ISSUE_TEMPLATE/issue.md b/.github/ISSUE_TEMPLATE/issue.md
deleted file mode 100644
index ba384e120bac..000000000000
--- a/.github/ISSUE_TEMPLATE/issue.md
+++ /dev/null
@@ -1,41 +0,0 @@
----
-name: New issue
-about: 'Report a bug or other problem'
-title: ''
-labels: ''
-assignees: ''
-
----
-
-### Description
-
-<!-- A clear and concise description of what the bug is -->
-
-<!-- A clear and concise description of what you expected to happen -->
-
-<!-- Include any other relevant details -->
-
-### To reproduce
-<!-- Steps to reproduce the issue, if possible -->
-1.
-2.
-3.
-4.
-
-### Logs
-- btmon log: <!-- Bluetooth traffic dump: (usually needed)
-                  0. Power off connected device
-                  1. Run: btmon -w btmon.log
-                  2. Connect device, reproduce the issue
-                  3. Ctrl-C btmon
-                  4. Attach btmon.log to the issue -->
-- bluetoothd log: <!-- Run: journalctl -u bluetooth --boot 0 > bluetoothd.log; if relevant for issue -->
-
-<!-- Any other logs etc. relevant for the issue -->
-
-### Versions
-- BlueZ version:  <!-- Run: bluetoothctl --version -->
-- Kernel version:  <!-- Run: uname -r -->
-- Problematic device: <!-- Device model etc information, if relevant -->
-
-<!-- Any other relevant information on platform / hardware here -->
diff --git a/.github/ISSUE_TEMPLATE/issue.yml b/.github/ISSUE_TEMPLATE/issue.yml
new file mode 100644
index 000000000000..0f069858eeaf
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/issue.yml
@@ -0,0 +1,108 @@
+name: New issue
+description: Report a bug or other problem
+labels: []
+body:
+  - type: textarea
+    id: description
+    attributes:
+      label: Description
+      description: |
+        A clear and concise description of the bug, what you expected
+        to happen, and any other relevant details.
+      placeholder: Describe the issue...
+    validations:
+      required: true
+
+  - type: textarea
+    id: reproduce
+    attributes:
+      label: To reproduce
+      description: Steps to reproduce the issue, if possible.
+      placeholder: |
+        1.
+        2.
+        3.
+        4.
+    validations:
+      required: false
+
+  - type: textarea
+    id: trace-file
+    attributes:
+      label: btmon trace
+      description: |
+        Drag and drop your btsnoop trace file here. If a trace is
+        attached and the privacy acknowledgment below is checked,
+        it will be automatically analyzed by
+        [btsnoop-analyzer](https://github.com/Vudentz/btsnoop-analyzer).
+
+        To capture a trace: power off the device, run `btmon -w btmon.log`,
+        reproduce the issue, then Ctrl-C btmon.
+      placeholder: Drag and drop your btmon.log file here...
+    validations:
+      required: false
+
+  - type: dropdown
+    id: focus-area
+    attributes:
+      label: Analysis focus
+      description: >
+        If you attached a btsnoop trace, what aspect should the
+        automated analysis focus on? Select "General" to auto-detect.
+      options:
+        - General (full analysis)
+        - Connection issues
+        - Controller enumeration
+        - Pairing / Security
+        - GATT discovery
+        - Audio
+        - Audio / LE Audio
+        - Audio / A2DP
+        - Audio / HFP
+        - L2CAP channel issues
+        - Advertising / Scanning
+        - Disconnection analysis
+        - Channel Sounding
+    validations:
+      required: false
+
+  - type: checkboxes
+    id: privacy
+    attributes:
+      label: Privacy
+      description: |
+        btsnoop traces contain Bluetooth MAC addresses and may contain
+        device names. By default, MAC addresses are anonymized before
+        being sent to an LLM for analysis.
+      options:
+        - label: >
+            **Skip anonymization** — Send the raw decoded trace to the
+            LLM without scrubbing MAC addresses or device names.
+          required: false
+        - label: >
+            I understand this trace will be processed by a third-party
+            LLM API and I have the right to share this data.
+          required: false
+
+  - type: textarea
+    id: logs
+    attributes:
+      label: Other logs
+      description: |
+        Attach bluetoothd or other relevant logs.
+        Run: `journalctl -u bluetooth --boot 0 > bluetoothd.log`
+      render: text
+    validations:
+      required: false
+
+  - type: textarea
+    id: versions
+    attributes:
+      label: Versions
+      description: BlueZ version, kernel version, and device information.
+      value: |
+        - BlueZ version:
+        - Kernel version:
+        - Problematic device:
+    validations:
+      required: false
diff --git a/.github/workflows/btsnoop-analyzer.yml b/.github/workflows/btsnoop-analyzer.yml
new file mode 100644
index 000000000000..3bbd963004cb
--- /dev/null
+++ b/.github/workflows/btsnoop-analyzer.yml
@@ -0,0 +1,202 @@
+name: btsnoop-analyzer
+
+on:
+  issues:
+    types: [opened, reopened]
+
+permissions:
+  issues: write
+  contents: read
+
+jobs:
+  analyze:
+    # Only run when the user acknowledged the privacy statement
+    if: contains(github.event.issue.body, 'I understand this trace will be processed')
+    runs-on: ubuntu-latest
+    timeout-minutes: 15
+
+    steps:
+      - name: Parse issue body
+        id: parse
+        uses: actions/github-script@v7
+        with:
+          script: |
+            const body = context.payload.issue.body || '';
+
+            // Extract trace file URL from issue body
+            const urlPatterns = [
+              /https:\/\/github\.com\/[^\s)]+\.(?:log|snoop|btsnoop|cfa)/gi,
+              /https:\/\/github\.com\/user-attachments\/(?:files|assets)\/[^\s)]+/gi,
+            ];
+
+            let traceUrl = '';
+            for (const pattern of urlPatterns) {
+              const match = body.match(pattern);
+              if (match) {
+                traceUrl = match[0];
+                break;
+              }
+            }
+
+            if (!traceUrl) {
+              console.log('No trace file URL found — skipping analysis');
+              core.setOutput('found', 'false');
+              return;
+            }
+
+            // Extract description
+            const descMatch = body.match(
+              /### Description\s*\n([\s\S]*?)(?=\n###|\n\*\*|$)/i
+            );
+            const description = descMatch?.[1]?.trim() || 'No description provided';
+
+            // Extract focus area
+            const focusMatch = body.match(
+              /### Analysis focus\s*\n\s*(\S[^\n]*)/i
+            );
+            const focus = focusMatch?.[1]?.trim() || 'General (full analysis)';
+
+            // Check anonymization preference
+            const skipAnon = body.includes('[X] **Skip anonymization**') ||
+                             body.includes('[x] **Skip anonymization**');
+
+            core.setOutput('found', 'true');
+            core.setOutput('trace_url', traceUrl);
+            core.setOutput('description', description);
+            core.setOutput('focus', focus);
+            core.setOutput('anonymize', skipAnon ? 'false' : 'true');
+
+            console.log(`Trace URL: ${traceUrl}`);
+            console.log(`Focus: ${focus}`);
+            console.log(`Anonymize: ${!skipAnon}`);
+
+      - name: Post "analyzing" comment
+        if: steps.parse.outputs.found == 'true'
+        uses: actions/github-script@v7
+        with:
+          script: |
+            await github.rest.issues.createComment({
+              owner: context.repo.owner,
+              repo: context.repo.repo,
+              issue_number: context.issue.number,
+              body: '**btsnoop Analyzer** is processing your trace. This typically takes 1-3 minutes.\n\n_Decoding with btmon, then sending to LLM for analysis..._'
+            });
+
+      - name: Run btsnoop-analyzer
+        if: steps.parse.outputs.found == 'true'
+        id: analysis
+        uses: Vudentz/btsnoop-analyzer@master
+        with:
+          trace-url: ${{ steps.parse.outputs.trace_url }}
+          description: ${{ steps.parse.outputs.description }}
+          focus: ${{ steps.parse.outputs.focus }}
+          anonymize: ${{ steps.parse.outputs.anonymize }}
+          provider: ${{ vars.LLM_PROVIDER || 'github' }}
+          model: ${{ vars.LLM_MODEL || '' }}
+        env:
+          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
+          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+          GH_MODELS_TOKEN: ${{ secrets.GH_MODELS_TOKEN }}
+
+      - name: Post detection comment
+        if: steps.parse.outputs.found == 'true' && success()
+        uses: actions/github-script@v7
+        with:
+          script: |
+            const fs = require('fs');
+            const detect = fs.readFileSync('${{ steps.analysis.outputs.detect }}', 'utf8');
+            await github.rest.issues.createComment({
+              owner: context.repo.owner,
+              repo: context.repo.repo,
+              issue_number: context.issue.number,
+              body: detect
+            });
+
+      - name: Post filter comment
+        if: steps.parse.outputs.found == 'true' && success()
+        uses: actions/github-script@v7
+        with:
+          script: |
+            const fs = require('fs');
+            const filter = fs.readFileSync('${{ steps.analysis.outputs.filter }}', 'utf8');
+            await github.rest.issues.createComment({
+              owner: context.repo.owner,
+              repo: context.repo.repo,
+              issue_number: context.issue.number,
+              body: filter
+            });
+
+      - name: Post annotation comment
+        if: steps.parse.outputs.found == 'true' && success()
+        uses: actions/github-script@v7
+        with:
+          script: |
+            const fs = require('fs');
+            const annotate = fs.readFileSync('${{ steps.analysis.outputs.annotate }}', 'utf8');
+            await github.rest.issues.createComment({
+              owner: context.repo.owner,
+              repo: context.repo.repo,
+              issue_number: context.issue.number,
+              body: annotate
+            });
+
+      - name: Post diagnostics comment
+        if: steps.parse.outputs.found == 'true' && success()
+        uses: actions/github-script@v7
+        with:
+          script: |
+            const fs = require('fs');
+            const diagnose = fs.readFileSync('${{ steps.analysis.outputs.diagnose }}', 'utf8');
+            await github.rest.issues.createComment({
+              owner: context.repo.owner,
+              repo: context.repo.repo,
+              issue_number: context.issue.number,
+              body: diagnose
+            });
+
+      - name: Post analysis comment
+        if: steps.parse.outputs.found == 'true' && success()
+        uses: actions/github-script@v7
+        with:
+          script: |
+            const fs = require('fs');
+            const analysis = fs.readFileSync('${{ steps.analysis.outputs.analyze }}', 'utf8');
+
+            const footer = `\n\n---\n<sub>Analyzed by [btsnoop-analyzer](https://github.com/Vudentz/btsnoop-analyzer) using btmon from [BlueZ](https://github.com/bluez/bluez). MAC addresses ${
+              '${{ steps.parse.outputs.anonymize }}' === 'true'
+                ? 'were anonymized'
+                : 'were **not** anonymized (user opted out)'
+            } before LLM processing.</sub>`;
+
+            await github.rest.issues.createComment({
+              owner: context.repo.owner,
+              repo: context.repo.repo,
+              issue_number: context.issue.number,
+              body: analysis + footer
+            });
+
+      - name: Post error comment
+        if: steps.parse.outputs.found == 'true' && failure()
+        uses: actions/github-script@v7
+        with:
+          script: |
+            await github.rest.issues.createComment({
+              owner: context.repo.owner,
+              repo: context.repo.repo,
+              issue_number: context.issue.number,
+              body: `## Analysis Failed
+
+            The automated analysis encountered an error. This could be due to:
+            - Unsupported trace file format
+            - Trace file too large to process
+            - LLM API rate limiting or downtime
+
+            A maintainer will review your trace manually.
+
+            <details>
+            <summary>Debug info</summary>
+
+            Workflow run: https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}
+            </details>`
+            });
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [PATCH BlueZ v1 2/3] github: Make result posting robust against action failures
  2026-04-15 18:22 [PATCH BlueZ v1 1/3] github: Add YAML issue template and btsnoop-analyzer workflow Luiz Augusto von Dentz
@ 2026-04-15 18:22 ` Luiz Augusto von Dentz
  2026-04-15 18:22 ` [PATCH BlueZ v1 3/3] github: Add btsnoop-analyzer slash command workflow Luiz Augusto von Dentz
  2026-04-16 17:30 ` [PATCH BlueZ v1 1/3] github: Add YAML issue template and btsnoop-analyzer workflow patchwork-bot+bluetooth
  2 siblings, 0 replies; 4+ messages in thread
From: Luiz Augusto von Dentz @ 2026-04-15 18:22 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

Use always() conditions with file-existence checks so steps 1-4
results are posted even if the btsnoop-analyzer action fails
partway through. The error comment now only fires on actual action
failure (e.g. btmon build error), not LLM errors which are handled
gracefully by the analyzer.
---
 .github/workflows/btsnoop-analyzer.yml | 34 +++++++++++++++++---------
 1 file changed, 22 insertions(+), 12 deletions(-)

diff --git a/.github/workflows/btsnoop-analyzer.yml b/.github/workflows/btsnoop-analyzer.yml
index 3bbd963004cb..9ecda193ceff 100644
--- a/.github/workflows/btsnoop-analyzer.yml
+++ b/.github/workflows/btsnoop-analyzer.yml
@@ -100,12 +100,14 @@ jobs:
           GH_MODELS_TOKEN: ${{ secrets.GH_MODELS_TOKEN }}
 
       - name: Post detection comment
-        if: steps.parse.outputs.found == 'true' && success()
+        if: always() && steps.parse.outputs.found == 'true' && steps.analysis.outcome != 'skipped'
         uses: actions/github-script@v7
         with:
           script: |
             const fs = require('fs');
-            const detect = fs.readFileSync('${{ steps.analysis.outputs.detect }}', 'utf8');
+            const path = '${{ steps.analysis.outputs.detect }}';
+            if (!fs.existsSync(path)) return;
+            const detect = fs.readFileSync(path, 'utf8');
             await github.rest.issues.createComment({
               owner: context.repo.owner,
               repo: context.repo.repo,
@@ -114,12 +116,14 @@ jobs:
             });
 
       - name: Post filter comment
-        if: steps.parse.outputs.found == 'true' && success()
+        if: always() && steps.parse.outputs.found == 'true' && steps.analysis.outcome != 'skipped'
         uses: actions/github-script@v7
         with:
           script: |
             const fs = require('fs');
-            const filter = fs.readFileSync('${{ steps.analysis.outputs.filter }}', 'utf8');
+            const path = '${{ steps.analysis.outputs.filter }}';
+            if (!fs.existsSync(path)) return;
+            const filter = fs.readFileSync(path, 'utf8');
             await github.rest.issues.createComment({
               owner: context.repo.owner,
               repo: context.repo.repo,
@@ -128,12 +132,14 @@ jobs:
             });
 
       - name: Post annotation comment
-        if: steps.parse.outputs.found == 'true' && success()
+        if: always() && steps.parse.outputs.found == 'true' && steps.analysis.outcome != 'skipped'
         uses: actions/github-script@v7
         with:
           script: |
             const fs = require('fs');
-            const annotate = fs.readFileSync('${{ steps.analysis.outputs.annotate }}', 'utf8');
+            const path = '${{ steps.analysis.outputs.annotate }}';
+            if (!fs.existsSync(path)) return;
+            const annotate = fs.readFileSync(path, 'utf8');
             await github.rest.issues.createComment({
               owner: context.repo.owner,
               repo: context.repo.repo,
@@ -142,12 +148,14 @@ jobs:
             });
 
       - name: Post diagnostics comment
-        if: steps.parse.outputs.found == 'true' && success()
+        if: always() && steps.parse.outputs.found == 'true' && steps.analysis.outcome != 'skipped'
         uses: actions/github-script@v7
         with:
           script: |
             const fs = require('fs');
-            const diagnose = fs.readFileSync('${{ steps.analysis.outputs.diagnose }}', 'utf8');
+            const path = '${{ steps.analysis.outputs.diagnose }}';
+            if (!fs.existsSync(path)) return;
+            const diagnose = fs.readFileSync(path, 'utf8');
             await github.rest.issues.createComment({
               owner: context.repo.owner,
               repo: context.repo.repo,
@@ -156,12 +164,14 @@ jobs:
             });
 
       - name: Post analysis comment
-        if: steps.parse.outputs.found == 'true' && success()
+        if: always() && steps.parse.outputs.found == 'true' && steps.analysis.outcome != 'skipped'
         uses: actions/github-script@v7
         with:
           script: |
             const fs = require('fs');
-            const analysis = fs.readFileSync('${{ steps.analysis.outputs.analyze }}', 'utf8');
+            const path = '${{ steps.analysis.outputs.analyze }}';
+            if (!fs.existsSync(path)) return;
+            const analysis = fs.readFileSync(path, 'utf8');
 
             const footer = `\n\n---\n<sub>Analyzed by [btsnoop-analyzer](https://github.com/Vudentz/btsnoop-analyzer) using btmon from [BlueZ](https://github.com/bluez/bluez). MAC addresses ${
               '${{ steps.parse.outputs.anonymize }}' === 'true'
@@ -177,7 +187,7 @@ jobs:
             });
 
       - name: Post error comment
-        if: steps.parse.outputs.found == 'true' && failure()
+        if: always() && steps.parse.outputs.found == 'true' && steps.analysis.outcome == 'failure'
         uses: actions/github-script@v7
         with:
           script: |
@@ -190,7 +200,7 @@ jobs:
             The automated analysis encountered an error. This could be due to:
             - Unsupported trace file format
             - Trace file too large to process
-            - LLM API rate limiting or downtime
+            - btmon build failure
 
             A maintainer will review your trace manually.
 
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* [PATCH BlueZ v1 3/3] github: Add btsnoop-analyzer slash command workflow
  2026-04-15 18:22 [PATCH BlueZ v1 1/3] github: Add YAML issue template and btsnoop-analyzer workflow Luiz Augusto von Dentz
  2026-04-15 18:22 ` [PATCH BlueZ v1 2/3] github: Make result posting robust against action failures Luiz Augusto von Dentz
@ 2026-04-15 18:22 ` Luiz Augusto von Dentz
  2026-04-16 17:30 ` [PATCH BlueZ v1 1/3] github: Add YAML issue template and btsnoop-analyzer workflow patchwork-bot+bluetooth
  2 siblings, 0 replies; 4+ messages in thread
From: Luiz Augusto von Dentz @ 2026-04-15 18:22 UTC (permalink / raw)
  To: linux-bluetooth

From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>

Add caller workflow for the btsnoop-analyzer reusable workflow,
enabling trace analysis from any issue comment.
---
 .github/workflows/btsnoop-analyze.yml | 13 +++++++++++++
 1 file changed, 13 insertions(+)
 create mode 100644 .github/workflows/btsnoop-analyze.yml

diff --git a/.github/workflows/btsnoop-analyze.yml b/.github/workflows/btsnoop-analyze.yml
new file mode 100644
index 000000000000..484288c0d9b6
--- /dev/null
+++ b/.github/workflows/btsnoop-analyze.yml
@@ -0,0 +1,13 @@
+name: Analyze on mention
+
+on:
+  issue_comment:
+    types: [created]
+
+jobs:
+  analyze:
+    if: >-
+      github.event.issue.pull_request == null &&
+      contains(github.event.comment.body, '/analyze')
+    uses: Vudentz/btsnoop-analyzer/.github/workflows/analyze-on-mention.yml@master
+    secrets: inherit
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 4+ messages in thread

* Re: [PATCH BlueZ v1 1/3] github: Add YAML issue template and btsnoop-analyzer workflow
  2026-04-15 18:22 [PATCH BlueZ v1 1/3] github: Add YAML issue template and btsnoop-analyzer workflow Luiz Augusto von Dentz
  2026-04-15 18:22 ` [PATCH BlueZ v1 2/3] github: Make result posting robust against action failures Luiz Augusto von Dentz
  2026-04-15 18:22 ` [PATCH BlueZ v1 3/3] github: Add btsnoop-analyzer slash command workflow Luiz Augusto von Dentz
@ 2026-04-16 17:30 ` patchwork-bot+bluetooth
  2 siblings, 0 replies; 4+ messages in thread
From: patchwork-bot+bluetooth @ 2026-04-16 17:30 UTC (permalink / raw)
  To: Luiz Augusto von Dentz; +Cc: linux-bluetooth

Hello:

This series was applied to bluetooth/bluez.git (master)
by Luiz Augusto von Dentz <luiz.von.dentz@intel.com>:

On Wed, 15 Apr 2026 14:22:07 -0400 you wrote:
> From: Luiz Augusto von Dentz <luiz.von.dentz@intel.com>
> 
> Replace the markdown issue template with a structured YAML form that
> includes fields for description, reproduction steps, btmon trace upload,
> analysis focus area selection, and privacy consent checkboxes.
> 
> Add a btsnoop-analyzer workflow that automatically analyzes btsnoop
> traces attached to new issues using Vudentz/btsnoop-analyzer action,
> posting results as issue comments.
> 
> [...]

Here is the summary with links:
  - [BlueZ,v1,1/3] github: Add YAML issue template and btsnoop-analyzer workflow
    https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=b73ba12b0bd6
  - [BlueZ,v1,2/3] github: Make result posting robust against action failures
    https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=ec6940c1a284
  - [BlueZ,v1,3/3] github: Add btsnoop-analyzer slash command workflow
    https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=ce07e906e499

You are awesome, thank you!
-- 
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html



^ permalink raw reply	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-04-16 17:30 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-15 18:22 [PATCH BlueZ v1 1/3] github: Add YAML issue template and btsnoop-analyzer workflow Luiz Augusto von Dentz
2026-04-15 18:22 ` [PATCH BlueZ v1 2/3] github: Make result posting robust against action failures Luiz Augusto von Dentz
2026-04-15 18:22 ` [PATCH BlueZ v1 3/3] github: Add btsnoop-analyzer slash command workflow Luiz Augusto von Dentz
2026-04-16 17:30 ` [PATCH BlueZ v1 1/3] github: Add YAML issue template and btsnoop-analyzer workflow patchwork-bot+bluetooth

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox