Compare commits

...
13 Commits
Author SHA1 Message Date
dependabot[bot] f3b74998f1 ci(deps): bump docker/metadata-action from 5 to 6
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 5 to 6.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](https://github.com/docker/metadata-action/compare/v5...v6)

---
updated-dependencies:
- dependency-name: docker/metadata-action
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-10 12:04:13 +01:00
dependabot[bot] ce15e706b8 ci(deps): bump docker/login-action from 3 to 4
Bumps [docker/login-action](https://github.com/docker/login-action) from 3 to 4.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-10 12:04:00 +01:00
dependabot[bot] bc61081acc ci(deps): bump docker/setup-buildx-action in the setup-actions group
Bumps the setup-actions group with 1 update: [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action).


Updates `docker/setup-buildx-action` from 3 to 4
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/v3...v4)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: setup-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-10 12:03:47 +01:00
dependabot[bot] 16f7327b7a deps(deps): bump the golang group with 2 updates
Bumps the golang group with 2 updates: [golang.org/x/sync](https://github.com/golang/sync) and [golang.org/x/sys](https://github.com/golang/sys).


Updates `golang.org/x/sync` from 0.19.0 to 0.20.0
- [Commits](https://github.com/golang/sync/compare/v0.19.0...v0.20.0)

Updates `golang.org/x/sys` from 0.41.0 to 0.42.0
- [Commits](https://github.com/golang/sys/compare/v0.41.0...v0.42.0)

---
updated-dependencies:
- dependency-name: golang.org/x/sync
  dependency-version: 0.20.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: golang
- dependency-name: golang.org/x/sys
  dependency-version: 0.42.0
  dependency-type: indirect
  update-type: version-update:semver-minor
  dependency-group: golang
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-09 20:46:04 +01:00
Tobias Gesellchenandlnx01 2e9f931797 Potential fix for code scanning alert no. 8: Workflow does not contain permissions
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2026-03-07 15:02:57 +01:00
Tobias Gesellchenandlnx01 41378f720b Potential fix for code scanning alert no. 7: Workflow does not contain permissions
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2026-03-07 15:02:15 +01:00
Tobias Gesellchenandlnx01 c87a28f3ba Potential fix for code scanning alert no. 4: Workflow does not contain permissions
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2026-03-07 15:01:03 +01:00
Tobias Gesellchenandlnx01 df18749220 Potential fix for code scanning alert no. 1: Workflow does not contain permissions
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2026-03-07 14:55:55 +01:00
Tobias Gesellchenandlnx01 b6702cd4b5 Potential fix for code scanning alert no. 2: Workflow does not contain permissions
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
2026-03-07 14:55:55 +01:00
Tobias GesellchenandJunie fc5de2bbc7 refactor: replace deprecated httputil.ReverseProxy.Director with Rewrite
- Update pkg/service/handlers/handlers_proxy.go and mirror_middleware.go to
  use the modern httputil.ReverseProxy.Rewrite hook (available since Go 1.20).
- Fix SA1019 staticcheck warnings triggered by Go 1.26 deprecation notice.
- Refactor proxy initialization to avoid NewSingleHostReverseProxy to prevent
  conflicts between Director and Rewrite hooks.
- Standardize request modification using ProxyRequest.SetURL and ProxyRequest.Out.

Co-authored-by: Junie <junie@jetbrains.com>
2026-03-07 12:58:01 +01:00
Tobias GesellchenandJunie cf82feca06 security: upgrade Go to 1.26.1 and update dependencies
- Update Go version to 1.26.1 in go.mod and examples to address:
  - GO-2026-4602 (os: FileInfo escape)
  - GO-2026-4601 (net/url: IPv6 host literal parsing)
  - GO-2026-4600 (crypto/x509: panic in name constraint checking)
  - GO-2026-4599 (crypto/x509: incorrect email constraint enforcement)
- Upgrade golang.org/x/* and other dependencies to latest stable versions.
- Synchronize go.sum via go mod tidy.

Co-authored-by: Junie <junie@jetbrains.com>
2026-03-07 12:58:01 +01:00
Tobias Gesellchen b8bbc52803 Refine migration guide (#100) 2026-03-07 12:56:25 +01:00
Tobias Gesellchen a36c2e4629 Prevent browser freeze for large payloads (#101) 2026-03-07 12:56:06 +01:00
13 changed files with 157 additions and 149 deletions
+6 -3
View File
@@ -1,5 +1,8 @@
name: CI
permissions:
contents: read
on:
push:
branches: [main]
@@ -253,11 +256,11 @@ jobs:
uses: actions/checkout@v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v4
- name: Log in to GitHub Container Registry
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
@@ -265,7 +268,7 @@ jobs:
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
uses: docker/metadata-action@v6
with:
images: ghcr.io/${{ github.repository }}
tags: |
+3 -3
View File
@@ -492,10 +492,10 @@ jobs:
uses: actions/checkout@v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
uses: docker/setup-buildx-action@v4
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
@@ -503,7 +503,7 @@ jobs:
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
uses: docker/metadata-action@v6
with:
images: ghcr.io/${{ github.repository }}
tags: |
+8
View File
@@ -14,6 +14,8 @@ jobs:
vulnerability-scan:
name: Vulnerability Scan
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout code
@@ -53,6 +55,8 @@ jobs:
static-analysis:
name: Static Security Analysis
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout code
@@ -119,6 +123,8 @@ jobs:
dependency-review:
name: Dependency Review
runs-on: ubuntu-latest
permissions:
contents: read
if: github.event_name == 'pull_request'
steps:
@@ -137,6 +143,8 @@ jobs:
runs-on: ubuntu-latest
needs: [vulnerability-scan, static-analysis, codeql-analysis]
if: always()
permissions:
contents: read
steps:
- name: Security scan summary
+1 -1
View File
@@ -1,5 +1,5 @@
# Build stage
FROM --platform=$BUILDPLATFORM golang:1.26.0-alpine AS builder
FROM --platform=$BUILDPLATFORM golang:1.26.1-alpine AS builder
# Declare automatic platform ARGs to make them available in build stage
# See https://docs.docker.com/reference/dockerfile#automatic-platform-args-in-the-global-scope
+60 -78
View File
@@ -9,7 +9,7 @@
## Overview
This guide will walk you through migrating your Bose SoundTouch speakers from Bose's cloud services to your own local SoundTouch service. By the end of this process, your speakers will be completely independent of Bose's servers while retaining all their functionality.
This guide will walk you through migrating your Bose SoundTouch speakers from Bose's cloud services to AfterTouch, your own local SoundTouch service. By the end of this process, your speakers will be completely independent of Bose's servers while retaining all their functionality.
> **💡 Why Migrate?** Bose announced the shutdown of their SoundTouch cloud services in May 2026. This migration ensures your speakers continue working indefinitely with enhanced local control and monitoring.
@@ -104,6 +104,20 @@ After creation, you'll see your **Account Dashboard**:
![Account Dashboard](../images/account-dashboard.png)
*Example: Fresh account dashboard ready for device migration*
### 2.3 Initial Settings
Once your account is created, configure the global settings:
1. **Settings**:
- Check **Target Domain**: Ensure it's reachable from the speaker (e.g., `soundtouch.fritz.box`).
- **DNS Discovery**: Enable DNS discovery on port `:53`. This is crucial for the DNS hook migration method.
2. **Devices**:
- Go to the **"Device Discovery"** tab.
- Click **"Scan Network"** or manually add a speaker via IP address.
- Your devices should appear with **SSH Status**: `Enabled`.
![Device Discovery](../images/device-discovery.png)
*Example: Discovered devices with remote access enabled*
## Step 3: Prepare Your Devices
> **⚠️ Important**: This step temporarily enables SSH access on your speakers. SSH will be automatically disabled after migration unless you choose to keep it enabled.
@@ -114,8 +128,8 @@ For each SoundTouch device:
1. **Prepare a USB drive**:
- Format as FAT32
- Create a file named `remote_services` (no extension)
- Content of file: `enable`
- Create an empty file named `remote_services` (no extension)
- (Optional) Firmware update/reset, see [Bose SoundTouch USB Update](https://downloads.bose.com/ced/soundtouch/soundtouch_usb/index.html)
2. **Insert USB drive** into your SoundTouch speaker
3. **Power cycle** the device (unplug for 10 seconds, then reconnect)
@@ -123,17 +137,6 @@ For each SoundTouch device:
![USB Preparation](../images/usb-remote-services.png)
*Example: USB drive setup for enabling remote services*
### 3.2 Verify Remote Access
In your SoundTouch Service dashboard:
1. Go to **"Device Discovery"** tab
2. Click **"Scan Network"**
3. Your devices should appear with **SSH Status**: `Enabled`
![Device Discovery](../images/device-discovery.png)
*Example: Discovered devices with remote access enabled*
## Step 4: Discover and Register Devices
### 4.1 Automatic Discovery
@@ -174,70 +177,51 @@ After registration, you'll see:
## Step 5: Migrate Individual Devices
> **🔄 Migration Strategy**: We recommend "gradual migration" where devices continue using Bose services while building local data, then switch to local-only when ready.
### 5.1 Start Device Migration
### 5.1 Step 3: Data Sync
1. **Dashboard****"Devices"** → Select your device
2. **Click "Migrate Device"**
3. **Configure migration**:
- Migration Method: `Gradual` (recommended)
- Preserve Data: ✅ `Yes`
- Backup Current State: ✅ `Yes`
- Test Period: `7 days`
2. Click **"Data Sync"**
3. This fetches configuration (presets, recents, sources) from the speaker to the SoundTouch service.
![Migration Setup](../images/migration-setup.png)
*Example: Migration configuration dialog*
### 5.2 Step 4: Migration
### 5.2 Migration Process
Once data is synced, proceed to the migration tab for the device:
The migration happens in phases:
1. **Backup XML**: Create an off-device backup of the current configuration.
2. **Enable Persistent Remote Service**: This ensures SSH remains available after reboots.
- *Note*: If you see `'rw: command not found'`, you can safely ignore it.
3. **CA Certificate Configuration**:
- **Test with explicit CA**: Verify the speaker can communicate using the local CA.
- **Trust CA now**: Inject the local Root CA into the speaker's trust store.
- **Test with shared trust store**: Verify general HTTPS communication.
4. **Migration Method**:
- Select **"Redirect via DNS hook"**.
- **Test DNS Redirection**: Ensure the speaker correctly resolves the service domain.
5. **Confirm Migration**: Apply the final changes to the speaker.
**Phase 1: Data Collection (Days 1-3)**
- Device continues using Bose services
- Local service mirrors all requests/responses
- Builds local database of presets, recents, sources
- Status: `Migrating - Data Collection`
#### Example Migration Output:
```text
Successfully created off-device backup of current configuration.
Pre-flight: Write access verified.
Resolved soundtouch.fritz.box to 192.168.1.100
Uploaded /mnt/nv/soundtouch-service/aftertouch.resolv.conf
/mnt/nv/rc.local already contains Aftertouch hook logic
(rw || mount -o remount,rw /): sh: rw: command not found
**Phase 2: Testing (Days 4-6)**
- Device uses local services for some requests
- Falls back to Bose if issues occur
- Validates local data completeness
- Status: `Migrating - Testing`
cp /etc/udhcpc.d/50default /etc/udhcpc.d/50default.original:
Applied patch to /etc/udhcpc.d/50default
Verified patch on /etc/udhcpc.d/50default
cp /opt/Bose/udhcpc.script /opt/Bose/udhcpc.script.original:
Applied patch to /opt/Bose/udhcpc.script
Verified patch on /opt/Bose/udhcpc.script
CA certificate already trusted, skipping injection
```
**Phase 3: Full Local (Day 7+)**
- Device uses only local services
- Bose services disabled for this device
- Full independence achieved
- Status: `Active - Local Only`
## Step 7: Complete Account Migration
![Migration Progress](../images/migration-progress.png)
*Example: Migration progress tracking*
### 7.1 Migrate All Devices
### 5.3 Monitor Migration Health
During migration, monitor:
**Device Health Dashboard**:
- **Connectivity**: Should remain `Online` throughout
- **Response Time**: Should be similar or better than before
- **Error Rate**: Should remain low (<1%)
- **Data Completeness**: Shows percentage of data successfully migrated
![Migration Health](../images/migration-health.png)
*Example: Migration health monitoring*
**Data Verification**:
- **Presets**: Verify all presets work correctly
- **Recent History**: Check recent play history is preserved
- **Source Configuration**: Ensure all configured sources (Spotify, etc.) work
- **Multiroom**: Test zone functionality if applicable
## Step 6: Complete Account Migration
### 6.1 Migrate All Devices
Repeat Step 5 for each of your SoundTouch devices. You can migrate multiple devices simultaneously, but we recommend doing 1-2 at a time to monitor progress.
Repeat the migration process for each of your SoundTouch devices. You can migrate multiple devices simultaneously, but we recommend doing 1-2 at a time to monitor progress.
**Migration Dashboard** shows overall progress:
- **Devices Migrated**: `2 of 4 completed`
@@ -248,7 +232,7 @@ Repeat Step 5 for each of your SoundTouch devices. You can migrate multiple devi
![Account Migration Status](../images/account-migration.png)
*Example: Account-wide migration progress*
### 6.2 Verify Complete Migration
### 7.2 Verify Complete Migration
When all devices are migrated:
@@ -260,18 +244,16 @@ When all devices are migrated:
![Migration Complete](../images/migration-complete.png)
*Example: Completed migration dashboard*
## Step 7: Post-Migration Setup
## Step 8: Post-Migration Tasks
### 7.1 Disable Remote Services (Optional)
1. **Remove USB stick** from the speaker.
2. **Reboot** the device to apply all changes.
For enhanced security, disable SSH on migrated devices:
### 8.1 Disable Remote Services (Optional)
1. **Dashboard****"Device Management"**
2. **Select device****"Security Settings"**
3. **Click "Disable Remote Services"**
4. **Confirm** to disable SSH access
For enhanced security, you can disable SSH on migrated devices. However, keeping it enabled allows for easier future maintenance or reverts.
### 7.2 Configure Backups
### 8.2 Configure Backups
Set up automatic backups of your device configurations:
@@ -284,7 +266,7 @@ Set up automatic backups of your device configurations:
![Backup Configuration](../images/backup-setup.png)
*Example: Backup configuration settings*
### 7.3 Set Up Monitoring Alerts (Optional)
### 8.3 Set Up Monitoring Alerts (Optional)
Configure notifications for important events:
+2 -2
View File
@@ -1,8 +1,8 @@
module navigation-station-demo
go 1.25.7
go 1.26.1
require github.com/gesellix/bose-soundtouch v0.0.0
require github.com/gesellix/bose-soundtouch v0.43.0
require github.com/gorilla/websocket v1.5.3 // indirect
+2 -2
View File
@@ -1,8 +1,8 @@
module preset-management-example
go 1.25.7
go 1.26.1
require github.com/gesellix/bose-soundtouch v0.0.0
require github.com/gesellix/bose-soundtouch v0.43.0
require github.com/gorilla/websocket v1.5.3 // indirect
+4 -4
View File
@@ -1,6 +1,6 @@
module github.com/gesellix/bose-soundtouch
go 1.25.7
go 1.26.1
require (
github.com/go-chi/chi/v5 v5.2.5
@@ -14,10 +14,10 @@ require (
require (
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 // indirect
golang.org/x/mod v0.33.0 // indirect
golang.org/x/net v0.51.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/sync v0.20.0 // indirect
golang.org/x/sys v0.42.0 // indirect
golang.org/x/tools v0.42.0 // indirect
)
+6 -6
View File
@@ -15,8 +15,8 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU=
github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 h1:FnBeRrxr7OU4VvAzt5X7s6266i6cSVkkFPS0TuXWbIg=
github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
@@ -53,8 +53,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -67,8 +67,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+14 -16
View File
@@ -55,23 +55,21 @@ func (s *Server) ServeProxy(target *url.URL) http.HandlerFunc {
r.Body = io.NopCloser(bytes.NewBuffer(reqBody))
}
rp := httputil.NewSingleHostReverseProxy(target)
rp.Transport = &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
rp := &httputil.ReverseProxy{
Rewrite: func(pr *httputil.ProxyRequest) {
pr.SetURL(target)
pr.Out.Host = target.Host
// If target has a path, we should probably append or replace.
// For Bose upstream, it's usually just the domain.
if target.Path != "" && target.Path != "/" {
pr.Out.URL.Path = target.Path
}
// Update director to set the correct host and path
originalDirector := rp.Director
rp.Director = func(req *http.Request) {
originalDirector(req)
req.Host = target.Host
// If target has a path, we should probably append or replace.
// For Bose upstream, it's usually just the domain.
if target.Path != "" && target.Path != "/" {
req.URL.Path = target.Path
}
lp.LogRequest(req)
lp.LogRequest(pr.Out)
},
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
rp.ModifyResponse = func(res *http.Response) error {
+9 -11
View File
@@ -257,17 +257,15 @@ func (s *Server) performMirror(r *http.Request) *mirrorResponseRecorder {
}
// Create a proxy that doesn't write to the original ResponseWriter
proxy := httputil.NewSingleHostReverseProxy(target)
proxy.Transport = &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
// Record the mirrored request
originalDirector := proxy.Director
proxy.Director = func(req *http.Request) {
originalDirector(req)
req.Host = target.Host
req.Header.Set("X-Mirror-Request", "true")
proxy := &httputil.ReverseProxy{
Rewrite: func(pr *httputil.ProxyRequest) {
pr.SetURL(target)
pr.Out.Host = target.Host
pr.Out.Header.Set("X-Mirror-Request", "true")
},
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
// Capture response for parity check and recording
+5
View File
@@ -1389,6 +1389,11 @@
></ul>
</div>
<div id="diff-size-warning" style="display: none; margin-bottom: 15px; padding: 10px; background: #fff3cd; border: 1px solid #ffeeba; border-radius: 4px; color: #856404;">
⚠️ <strong>Large Payload:</strong> Rich diff highlighting is disabled to prevent a browser freeze. Showing raw comparison instead.
<button id="force-rich-diff-btn" style="margin-left: 10px; font-size: 0.8em; cursor: pointer;">Show Rich Diff Anyway</button>
</div>
<div class="diff-container" style="margin-top: 15px">
<div class="diff-pane">
<span
+37 -23
View File
@@ -1008,7 +1008,7 @@ async function clearParityMismatches() {
}
}
function viewParityMismatch(m) {
function viewParityMismatch(m, forceRichDiff = false) {
document.getElementById("diff-path-display").innerText = m.method + " " + m.path;
const reasonsList = document.getElementById("diff-reasons-list");
reasonsList.innerHTML = "";
@@ -1027,32 +1027,46 @@ function viewParityMismatch(m) {
const localBody = formatBody(m.local.body, localCT);
const upstreamBody = formatBody(m.upstream.body, upstreamCT);
if (typeof Diff !== 'undefined') {
const diff = Diff.diffChars(localBody, upstreamBody);
const localEl = document.getElementById("diff-local-body");
const upstreamEl = document.getElementById("diff-upstream-body");
const diffSizeThreshold = 50000; // 50KB
const isLarge = localBody.length > diffSizeThreshold || upstreamBody.length > diffSizeThreshold;
const warningEl = document.getElementById("diff-size-warning");
localEl.innerHTML = "";
upstreamEl.innerHTML = "";
if (isLarge && !forceRichDiff) {
warningEl.style.display = "block";
const forceBtn = document.getElementById("force-rich-diff-btn");
forceBtn.onclick = () => viewParityMismatch(m, true);
diff.forEach((part) => {
const span = document.createElement('span');
if (part.added) {
span.className = 'diff-added';
span.innerText = part.value;
upstreamEl.appendChild(span);
} else if (part.removed) {
span.className = 'diff-removed';
span.innerText = part.value;
localEl.appendChild(span);
} else {
localEl.appendChild(document.createTextNode(part.value));
upstreamEl.appendChild(document.createTextNode(part.value));
}
});
} else {
document.getElementById("diff-local-body").innerText = localBody;
document.getElementById("diff-upstream-body").innerText = upstreamBody;
} else {
warningEl.style.display = "none";
if (typeof Diff !== 'undefined') {
const diff = Diff.diffChars(localBody, upstreamBody);
const localEl = document.getElementById("diff-local-body");
const upstreamEl = document.getElementById("diff-upstream-body");
localEl.innerHTML = "";
upstreamEl.innerHTML = "";
diff.forEach((part) => {
const span = document.createElement('span');
if (part.added) {
span.className = 'diff-added';
span.innerText = part.value;
upstreamEl.appendChild(span);
} else if (part.removed) {
span.className = 'diff-removed';
span.innerText = part.value;
localEl.appendChild(span);
} else {
localEl.appendChild(document.createTextNode(part.value));
upstreamEl.appendChild(document.createTextNode(part.value));
}
});
} else {
document.getElementById("diff-local-body").innerText = localBody;
document.getElementById("diff-upstream-body").innerText = upstreamBody;
}
}
document.getElementById("parity-diff-view").style.display = "block";