mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-31 14:57:17 +00:00
Compare commits
@@ -0,0 +1,87 @@
|
||||
Draft a "News & Updates" blog post for AfterTouch covering recent git activity, then open a draft PR for review.
|
||||
|
||||
## Step 1 — Determine lookback window
|
||||
|
||||
Run:
|
||||
```
|
||||
git log --format="%ad" --date=short -- docs/content/blog/ | grep -v '_index' | head -1
|
||||
```
|
||||
|
||||
If a date is returned, use it as SINCE.
|
||||
If the output is empty (no posts yet), compute SINCE = 30 days before today:
|
||||
- macOS: `date -v-30d +%Y-%m-%d`
|
||||
- Linux: `date -d '30 days ago' +%Y-%m-%d`
|
||||
|
||||
## Step 2 — Collect commits since SINCE
|
||||
|
||||
Run:
|
||||
```
|
||||
git log --format="%ad %h %s" --date=short --since="$SINCE" --no-merges
|
||||
```
|
||||
|
||||
Exclude these (they are noise):
|
||||
- Subjects matching: `^(ci|chore|deps|bump|Bump|test|lint|style|code style|debug)`
|
||||
- Dependabot bumps (subject contains "bump" and includes a package name pattern)
|
||||
- Routine doc link/URL fixes
|
||||
|
||||
Group the remaining commits into categories:
|
||||
- **NEW FEATURES** — subjects starting with `feat(` or `feat:`
|
||||
- **BUG FIXES** — subjects starting with `fix(` or `fix:`
|
||||
- **SECURITY** — subjects starting with `sec` or containing "security", "inject", "path expression"
|
||||
- **DOCS** — user-visible doc changes only (new guides, major restructures)
|
||||
- **MAINTENANCE** — everything else that passed the filter
|
||||
|
||||
Omit empty categories entirely.
|
||||
|
||||
## Step 3 — Current version
|
||||
|
||||
Run: `git tag --sort=-version:refname | head -1`
|
||||
|
||||
## Step 4 — Determine the period label
|
||||
|
||||
Use the first and last commit dates from Step 2 to produce a human-readable label,
|
||||
e.g. "May 2026" or "April – May 2026".
|
||||
|
||||
## Step 5 — Write the blog post
|
||||
|
||||
Create the file at: `docs/content/blog/YYYY-MM-slug.md`
|
||||
- YYYY-MM = today's year-month
|
||||
- slug = short kebab-case summary of the biggest theme
|
||||
|
||||
Use this exact frontmatter shape:
|
||||
```yaml
|
||||
---
|
||||
title: "AfterTouch PERIOD: <one-line theme>"
|
||||
date: YYYY-MM-DD
|
||||
description: "<one sentence, ≤200 chars, suitable as a standalone teaser>"
|
||||
tags:
|
||||
- <up to 4 tags from: security, tls, discovery, docs, cli, web, spotify, amazon, health, migration, fixes, ci>
|
||||
sidebar:
|
||||
exclude: true
|
||||
---
|
||||
```
|
||||
|
||||
Body structure:
|
||||
1. Opening paragraph (3–5 sentences) explaining what happened and why it matters to someone running AfterTouch.
|
||||
2. One `##` section per non-empty category. Use bullet points written for an operator audience — no raw git subjects, no internal Go package paths.
|
||||
3. End with: `**Current release:** vX.Y.Z`
|
||||
|
||||
Target length: 300–600 words. Never include real IPs, MAC addresses, account IDs, or device names.
|
||||
|
||||
## Step 6 — Create a branch and open a draft PR
|
||||
|
||||
```bash
|
||||
git checkout -b blog/YYYY-MM-update
|
||||
git add docs/content/blog/YYYY-MM-slug.md
|
||||
git commit -m "docs(blog): add PERIOD update post"
|
||||
git push -u origin blog/YYYY-MM-update
|
||||
gh pr create --draft \
|
||||
--title "Blog: PERIOD update post" \
|
||||
--body "Automated draft from /blog-update skill. Review content before merging — deployment is automatic on merge to main."
|
||||
```
|
||||
|
||||
If the `documentation` label exists on the repo, add `--label documentation`.
|
||||
|
||||
## Step 7 — Done
|
||||
|
||||
Report the PR URL. Do not merge, approve, or request review.
|
||||
+12
-57
@@ -1,74 +1,29 @@
|
||||
# CodeQL configuration for enhanced security analysis
|
||||
# See: https://docs.github.com/en/code-security/codeql-cli/using-the-codeql-cli/creating-codeql-query-suites
|
||||
# CodeQL configuration
|
||||
# https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning
|
||||
|
||||
name: "Go Security Analysis"
|
||||
|
||||
disable-default-queries: false
|
||||
|
||||
queries:
|
||||
# Include default security queries
|
||||
- uses: security-extended
|
||||
- uses: security-and-quality
|
||||
|
||||
# Additional Go-specific security queries
|
||||
- name: go-security-extra
|
||||
uses:
|
||||
- go/bad-redirect-check
|
||||
- go/clear-text-logging
|
||||
- go/incorrect-integer-conversion
|
||||
- go/log-injection
|
||||
- go/missing-regexp-anchor
|
||||
- go/path-injection
|
||||
- go/request-forgery
|
||||
- go/sensitive-package-import
|
||||
- go/sql-injection
|
||||
- go/uncontrolled-allocation-size
|
||||
- go/unsafe-quoting
|
||||
- go/useless-regexp-character-escape
|
||||
- go/zip-slip
|
||||
|
||||
# Configure paths to exclude from analysis
|
||||
paths-ignore:
|
||||
- "**/*.pb.go" # Generated protobuf files
|
||||
- "**/*_gen.go" # Generated code
|
||||
- "**/vendor/**" # Vendor dependencies
|
||||
- "**/build/**" # Build artifacts
|
||||
- "**/scripts/**" # Build scripts
|
||||
- "**/*_test.go" # Test files (optional - remove if you want to analyze tests)
|
||||
|
||||
# Configure paths to include (if not specified, all Go files are included)
|
||||
# Paths to include
|
||||
paths:
|
||||
- "cmd/**/*.go"
|
||||
- "pkg/**/*.go"
|
||||
- "*.go"
|
||||
|
||||
# Query filters to reduce noise
|
||||
# Paths to exclude from analysis
|
||||
paths-ignore:
|
||||
- "**/*.pb.go" # Generated protobuf files
|
||||
- "**/*_gen.go" # Generated code
|
||||
- "**/vendor/**" # Vendor dependencies
|
||||
- "**/build/**" # Build artifacts
|
||||
- "**/scripts/**" # Build scripts
|
||||
- "**/*_test.go" # Test files
|
||||
|
||||
query-filters:
|
||||
- exclude:
|
||||
id: go/unused-variable
|
||||
reason: "Can be noisy in development"
|
||||
- exclude:
|
||||
id: go/hardcoded-credentials
|
||||
reason: "Will be handled by separate secret scanning"
|
||||
|
||||
# Configuration for specific query packs
|
||||
packs:
|
||||
# Use the official CodeQL Go queries
|
||||
- codeql/go-queries
|
||||
|
||||
# Additional community query packs for enhanced security
|
||||
- codeql/go-queries@~0.0.0 # Latest version
|
||||
|
||||
# Custom configuration for specific queries
|
||||
query-config:
|
||||
go/path-injection:
|
||||
# Configure severity levels
|
||||
severity: "error"
|
||||
go/sql-injection:
|
||||
severity: "error"
|
||||
go/request-forgery:
|
||||
severity: "warning"
|
||||
go/log-injection:
|
||||
severity: "warning"
|
||||
go/clear-text-logging:
|
||||
severity: "note"
|
||||
|
||||
@@ -38,6 +38,69 @@ updates:
|
||||
patterns:
|
||||
- "golang.org/*"
|
||||
|
||||
# Hugo module dependency updates (docs site)
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/docs"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
day: "monday"
|
||||
time: "09:00"
|
||||
timezone: "UTC"
|
||||
open-pull-requests-limit: 3
|
||||
reviewers:
|
||||
- "gesellix"
|
||||
assignees:
|
||||
- "gesellix"
|
||||
commit-message:
|
||||
prefix: "deps"
|
||||
include: "scope"
|
||||
labels:
|
||||
- "dependencies"
|
||||
- "go"
|
||||
- "docs"
|
||||
rebase-strategy: "auto"
|
||||
|
||||
# Example module dependency updates
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/examples/navigation-station-demo"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
day: "monday"
|
||||
time: "09:00"
|
||||
timezone: "UTC"
|
||||
open-pull-requests-limit: 3
|
||||
reviewers:
|
||||
- "gesellix"
|
||||
assignees:
|
||||
- "gesellix"
|
||||
commit-message:
|
||||
prefix: "deps"
|
||||
include: "scope"
|
||||
labels:
|
||||
- "dependencies"
|
||||
- "go"
|
||||
rebase-strategy: "auto"
|
||||
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/examples/preset-management"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
day: "monday"
|
||||
time: "09:00"
|
||||
timezone: "UTC"
|
||||
open-pull-requests-limit: 3
|
||||
reviewers:
|
||||
- "gesellix"
|
||||
assignees:
|
||||
- "gesellix"
|
||||
commit-message:
|
||||
prefix: "deps"
|
||||
include: "scope"
|
||||
labels:
|
||||
- "dependencies"
|
||||
- "go"
|
||||
rebase-strategy: "auto"
|
||||
|
||||
# GitHub Actions workflow dependency updates
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
"pattern": "^https://pkg.go.dev.*badge"
|
||||
},
|
||||
{
|
||||
"pattern": "^\\.\\./images/(dashboard-home|account-creation|account-dashboard|usb-remote-services|device-discovery|device-registration|account-migration|migration-setup|migration-progress|migration-health|migration-complete|backup-setup)\\.png$"
|
||||
"pattern": "^/images/"
|
||||
},
|
||||
{
|
||||
"pattern": "https://www.contributor-covenant.org/version/2/0/code_of_conduct.html"
|
||||
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
run: sudo apt-get install -y libpcap-dev
|
||||
|
||||
- name: Run golangci-lint
|
||||
uses: golangci/golangci-lint-action@1e7e51e771db61008b38414a730f564565cf7c20 # v9.2.0
|
||||
uses: golangci/golangci-lint-action@82606bf257cbaff209d206a39f5134f0cfbfd2ee # v9.2.1
|
||||
with:
|
||||
version: latest
|
||||
args: --timeout=5m
|
||||
@@ -215,8 +215,8 @@ jobs:
|
||||
)
|
||||
|
||||
for img in "${IMAGES[@]}"; do
|
||||
if [ ! -f "docs/images/$img" ]; then
|
||||
echo "::warning file=docs/guides/MIGRATION-GUIDE.md::Pending image '$img' is missing from docs/images/"
|
||||
if [ ! -f "docs/static/images/$img" ]; then
|
||||
echo "::warning file=docs/content/docs/guides/MIGRATION-GUIDE.md::Pending image '$img' is missing from docs/static/images/"
|
||||
fi
|
||||
done
|
||||
|
||||
@@ -226,7 +226,7 @@ jobs:
|
||||
echo "Validating API documentation consistency..."
|
||||
|
||||
# Check API cookbook
|
||||
if [ -f "docs/reference/API-COOKBOOK.md" ]; then
|
||||
if [ -f "docs/content/docs/reference/API-COOKBOOK.md" ]; then
|
||||
echo "✓ API Cookbook exists"
|
||||
else
|
||||
echo "✗ API Cookbook missing"
|
||||
@@ -234,7 +234,7 @@ jobs:
|
||||
fi
|
||||
|
||||
# Check getting started guide
|
||||
if [ -f "docs/guides/GETTING-STARTED.md" ]; then
|
||||
if [ -f "docs/content/docs/guides/GETTING-STARTED.md" ]; then
|
||||
echo "✓ Getting Started guide exists"
|
||||
else
|
||||
echo "✗ Getting Started guide missing"
|
||||
@@ -308,6 +308,10 @@ jobs:
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
||||
|
||||
- name: Set build date
|
||||
id: build_date
|
||||
run: echo "date=$(date -u +%Y-%m-%d)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Determine push eligibility
|
||||
id: push-check
|
||||
run: |
|
||||
@@ -332,7 +336,7 @@ jobs:
|
||||
|
||||
- name: Extract metadata (tags, labels) for soundtouch-service
|
||||
id: meta-service
|
||||
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
|
||||
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
|
||||
with:
|
||||
images: ghcr.io/${{ github.repository }}
|
||||
tags: |
|
||||
@@ -350,12 +354,15 @@ jobs:
|
||||
push: ${{ steps.push-check.outputs.should-push == 'true' }}
|
||||
tags: ${{ steps.meta-service.outputs.tags }}
|
||||
labels: ${{ steps.meta-service.outputs.labels }}
|
||||
build-args: |
|
||||
COMMIT=${{ github.sha }}
|
||||
DATE=${{ steps.build_date.outputs.date }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
- name: Extract metadata (tags, labels) for soundtouch-web
|
||||
id: meta-web
|
||||
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
|
||||
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
|
||||
with:
|
||||
images: ghcr.io/${{ github.repository }}-web
|
||||
tags: |
|
||||
@@ -373,6 +380,9 @@ jobs:
|
||||
push: ${{ steps.push-check.outputs.should-push == 'true' }}
|
||||
tags: ${{ steps.meta-web.outputs.tags }}
|
||||
labels: ${{ steps.meta-web.outputs.labels }}
|
||||
build-args: |
|
||||
COMMIT=${{ github.sha }}
|
||||
DATE=${{ steps.build_date.outputs.date }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
name: "CodeQL Advanced"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
schedule:
|
||||
- cron: '36 6 * * 1'
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze (${{ matrix.language }})
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
security-events: write
|
||||
packages: read
|
||||
actions: read
|
||||
contents: read
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- language: actions
|
||||
build-mode: none
|
||||
- language: go
|
||||
build-mode: manual
|
||||
- language: javascript-typescript
|
||||
build-mode: none
|
||||
- language: python
|
||||
build-mode: none
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Install libpcap (required for Go build)
|
||||
if: matrix.language == 'go'
|
||||
run: sudo apt-get install -y libpcap-dev
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
|
||||
with:
|
||||
languages: ${{ matrix.language }}
|
||||
build-mode: ${{ matrix.build-mode }}
|
||||
config-file: ${{ matrix.language == 'go' && './.github/codeql-config.yml' || '' }}
|
||||
|
||||
- name: Build Go (required for manual build-mode)
|
||||
if: matrix.language == 'go'
|
||||
run: go build ./...
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
|
||||
with:
|
||||
category: "/language:${{ matrix.language }}"
|
||||
@@ -22,12 +22,18 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
- name: Setup Pages
|
||||
id: pages
|
||||
uses: actions/configure-pages@45bfe0192ca1faeb007ade9deae92b16b8254a0d # v6.0.0
|
||||
- name: Build with Jekyll
|
||||
uses: actions/jekyll-build-pages@44a6e6beabd48582f863aeeb6cb2151cc1716697 # v1.0.13
|
||||
- name: Setup Hugo
|
||||
uses: peaceiris/actions-hugo@2752ce1d29631191ea3f27c23495fa06139a5b78 # v3.2.1
|
||||
with:
|
||||
source: 'docs/'
|
||||
destination: '_site'
|
||||
hugo-version: 'latest'
|
||||
extended: true
|
||||
- name: Build with Hugo
|
||||
run: hugo --source docs/ --minify --destination ../_site --baseURL "${{ steps.pages.outputs.base_url }}"
|
||||
env:
|
||||
HUGO_ENVIRONMENT: production
|
||||
HUGO_PARAMS_GITHASH: ${{ github.sha }}
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@fc324d3547104276b827a68afc52ff2a11cc49c9 # v5.0.0
|
||||
with:
|
||||
|
||||
@@ -523,6 +523,10 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Set build date
|
||||
id: build_date
|
||||
run: echo "date=$(date -u +%Y-%m-%d)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
||||
|
||||
@@ -535,7 +539,7 @@ jobs:
|
||||
|
||||
- name: Extract metadata (tags, labels) for soundtouch-service
|
||||
id: meta-service
|
||||
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
|
||||
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
|
||||
with:
|
||||
images: ghcr.io/${{ github.repository }}
|
||||
tags: |
|
||||
@@ -552,12 +556,16 @@ jobs:
|
||||
push: true
|
||||
tags: ${{ steps.meta-service.outputs.tags }}
|
||||
labels: ${{ steps.meta-service.outputs.labels }}
|
||||
build-args: |
|
||||
VERSION=v${{ needs.validate.outputs.version }}
|
||||
COMMIT=${{ github.sha }}
|
||||
DATE=${{ steps.build_date.outputs.date }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
- name: Extract metadata (tags, labels) for soundtouch-web
|
||||
id: meta-web
|
||||
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
|
||||
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
|
||||
with:
|
||||
images: ghcr.io/${{ github.repository }}-web
|
||||
tags: |
|
||||
@@ -574,6 +582,10 @@ jobs:
|
||||
push: true
|
||||
tags: ${{ steps.meta-web.outputs.tags }}
|
||||
labels: ${{ steps.meta-web.outputs.labels }}
|
||||
build-args: |
|
||||
VERSION=v${{ needs.validate.outputs.version }}
|
||||
COMMIT=${{ github.sha }}
|
||||
DATE=${{ steps.build_date.outputs.date }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
|
||||
@@ -29,10 +29,8 @@ jobs:
|
||||
- name: Install libpcap
|
||||
run: sudo apt-get install -y libpcap-dev
|
||||
|
||||
- name: Install security scanning tools
|
||||
run: |
|
||||
go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||
go install github.com/sonatypecommunity/nancy@latest
|
||||
- name: Install govulncheck
|
||||
run: go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||
|
||||
- name: Run govulncheck (Official Go vulnerability scanner)
|
||||
run: |
|
||||
@@ -40,21 +38,6 @@ jobs:
|
||||
govulncheck ./...
|
||||
echo "::endgroup::"
|
||||
|
||||
- name: Run Nancy vulnerability scanner
|
||||
run: |
|
||||
echo "::group::Running Nancy dependency scanner"
|
||||
go list -json -deps ./... | nancy sleuth
|
||||
echo "::endgroup::"
|
||||
|
||||
- name: Upload vulnerability scan results
|
||||
if: failure()
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
with:
|
||||
name: vulnerability-scan-results
|
||||
path: |
|
||||
vulnerability-report.json
|
||||
nancy-report.json
|
||||
|
||||
static-analysis:
|
||||
name: Static Security Analysis
|
||||
runs-on: ubuntu-latest
|
||||
@@ -95,40 +78,11 @@ jobs:
|
||||
|
||||
- name: Upload Semgrep SARIF results
|
||||
if: always()
|
||||
uses: github/codeql-action/upload-sarif@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
|
||||
uses: github/codeql-action/upload-sarif@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v4.36.0
|
||||
with:
|
||||
sarif_file: semgrep.sarif
|
||||
continue-on-error: true
|
||||
|
||||
codeql-analysis:
|
||||
name: CodeQL Analysis
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
|
||||
- name: Install libpcap
|
||||
run: sudo apt-get install -y libpcap-dev
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
|
||||
with:
|
||||
languages: go
|
||||
config-file: ./.github/codeql-config.yml
|
||||
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v4.35.5
|
||||
with:
|
||||
category: "/language:go"
|
||||
|
||||
dependency-review:
|
||||
name: Dependency Review
|
||||
runs-on: ubuntu-latest
|
||||
@@ -150,7 +104,7 @@ jobs:
|
||||
security-summary:
|
||||
name: Security Summary
|
||||
runs-on: ubuntu-latest
|
||||
needs: [vulnerability-scan, static-analysis, codeql-analysis]
|
||||
needs: [vulnerability-scan, static-analysis]
|
||||
if: always()
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -173,17 +127,11 @@ jobs:
|
||||
echo "❌ **Static Analysis**: FAILED" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
if [[ "${{ needs.codeql-analysis.result }}" == "success" ]]; then
|
||||
echo "✅ **CodeQL Analysis**: PASSED" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "❌ **CodeQL Analysis**: FAILED" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo "For detailed results, check the individual job logs above." >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Fail on security issues
|
||||
if: needs.vulnerability-scan.result == 'failure' || needs.static-analysis.result == 'failure' || needs.codeql-analysis.result == 'failure'
|
||||
if: needs.vulnerability-scan.result == 'failure' || needs.static-analysis.result == 'failure'
|
||||
run: |
|
||||
echo "Security scan detected issues. Please review the results above."
|
||||
exit 1
|
||||
|
||||
+12
-1
@@ -48,7 +48,8 @@ node_modules/
|
||||
# IDE and editor files
|
||||
.vscode/
|
||||
.idea/
|
||||
.claude/
|
||||
.claude/*
|
||||
!.claude/commands/
|
||||
.junie/
|
||||
*.swp
|
||||
*.swo
|
||||
@@ -115,6 +116,10 @@ stockholm_zip/*.zip
|
||||
NEXT.md
|
||||
DONE.md
|
||||
|
||||
# Code-scanning working notes — snapshot + remediation plan; not committed
|
||||
# until the sweep is complete and the notes are stable.
|
||||
CODE-SCANNING-NOTES.md
|
||||
|
||||
# Plan/tracking note for the Health-tab debug-utility programme.
|
||||
# Living document; commit history of the checks themselves is the
|
||||
# source of truth for what shipped.
|
||||
@@ -122,3 +127,9 @@ SERVICE-HEALTH.md
|
||||
|
||||
# Diagnostic encryption keys — private key stays local with the maintainer
|
||||
keys/private/
|
||||
|
||||
# Hugo (docs site)
|
||||
# Hugo build artifacts (docs site)
|
||||
docs/.hugo_build.lock
|
||||
docs/public/
|
||||
docs/resources/
|
||||
|
||||
@@ -182,6 +182,12 @@ unless the user has already authorised that specific action in this
|
||||
session. Prefer reversible alternatives (`git stash` over
|
||||
`git reset --hard`).
|
||||
|
||||
**Force-flags also require explicit approval.** `git add -f` (force-add
|
||||
a gitignored file), `git push --force`, `git push --force-with-lease`,
|
||||
and any other flag that overrides a git safety mechanism must be
|
||||
proposed and confirmed before running, for the same reason: they
|
||||
bypass protections that exist intentionally.
|
||||
|
||||
## What never goes into this repo
|
||||
|
||||
This repository is public. The following must never be committed:
|
||||
|
||||
+3
-3
@@ -88,7 +88,7 @@ When filing a bug report, include:
|
||||
Feature requests are welcome! Please:
|
||||
|
||||
1. **Check if the feature already exists** in documentation
|
||||
2. **Verify it's supported by the SoundTouch API** (see [official API docs](docs/reference/API-ENDPOINTS.md))
|
||||
2. **Verify it's supported by the SoundTouch API** (see [official API docs](docs/content/docs/reference/API-ENDPOINTS.md))
|
||||
3. **Explain the use case** and how it benefits users
|
||||
|
||||
### 🔧 Contributing Code
|
||||
@@ -489,8 +489,8 @@ Sponsorship is entirely optional. Code, docs, and bug reports remain the most us
|
||||
|
||||
- [Go Documentation](https://golang.org/doc/)
|
||||
- [Effective Go](https://golang.org/doc/effective_go.html)
|
||||
- [Bose SoundTouch API Documentation](docs/reference/API-ENDPOINTS.md)
|
||||
- [Project Architecture](docs/PROJECT-PATTERNS.md)
|
||||
- [Bose SoundTouch API Documentation](docs/content/docs/reference/API-ENDPOINTS.md)
|
||||
- [Project Architecture](docs/content/docs/appendix/PROJECT-PATTERNS.md)
|
||||
- [Development Status](docs/archive/STATUS.md)
|
||||
|
||||
---
|
||||
|
||||
+18
-4
@@ -8,6 +8,12 @@ ARG TARGETARCH
|
||||
ARG TARGETOS
|
||||
ARG TARGETVARIANT
|
||||
|
||||
# Version info injected at build time; defaults keep local builds working.
|
||||
# The release workflow passes VERSION, COMMIT, and DATE via --build-arg.
|
||||
ARG VERSION=dev
|
||||
ARG COMMIT=unknown
|
||||
ARG DATE=unknown
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy go mod and sum files
|
||||
@@ -19,16 +25,24 @@ COPY . .
|
||||
|
||||
# Build the soundtouch-service
|
||||
RUN if [ "${TARGETARCH}" = "arm" ] && [ -n "${TARGETVARIANT}" ]; then \
|
||||
CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} GOARM=${TARGETVARIANT#v} go build -o /soundtouch-service ./cmd/soundtouch-service; \
|
||||
CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} GOARM=${TARGETVARIANT#v} \
|
||||
go build -trimpath -ldflags="-s -w -X main.version=${VERSION} -X main.commit=${COMMIT} -X main.date=${DATE}" \
|
||||
-o /soundtouch-service ./cmd/soundtouch-service; \
|
||||
else \
|
||||
CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o /soundtouch-service ./cmd/soundtouch-service; \
|
||||
CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \
|
||||
go build -trimpath -ldflags="-s -w -X main.version=${VERSION} -X main.commit=${COMMIT} -X main.date=${DATE}" \
|
||||
-o /soundtouch-service ./cmd/soundtouch-service; \
|
||||
fi
|
||||
|
||||
# Build the soundtouch-web
|
||||
RUN if [ "${TARGETARCH}" = "arm" ] && [ -n "${TARGETVARIANT}" ]; then \
|
||||
CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} GOARM=${TARGETVARIANT#v} go build -o /soundtouch-web ./cmd/soundtouch-web; \
|
||||
CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} GOARM=${TARGETVARIANT#v} \
|
||||
go build -trimpath -ldflags="-s -w -X main.version=${VERSION} -X main.commit=${COMMIT} -X main.date=${DATE}" \
|
||||
-o /soundtouch-web ./cmd/soundtouch-web; \
|
||||
else \
|
||||
CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o /soundtouch-web ./cmd/soundtouch-web; \
|
||||
CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} \
|
||||
go build -trimpath -ldflags="-s -w -X main.version=${VERSION} -X main.commit=${COMMIT} -X main.date=${DATE}" \
|
||||
-o /soundtouch-web ./cmd/soundtouch-web; \
|
||||
fi
|
||||
|
||||
# soundtouch-service image
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
.PHONY: all build build-cli test test-coverage test-http-client test-http-client-rotate check fmt vet lint clean dev help screenshots build-stockholm-image prepare-stockholm update-static-deps
|
||||
.PHONY: all build build-cli test test-coverage test-http-client test-http-client-rotate check fmt vet lint clean dev help screenshots build-stockholm-image prepare-stockholm update-static-deps dev-docs dev-docs-tidy hugo
|
||||
|
||||
# Load .env if present (simple KEY=VALUE format, no shell quoting)
|
||||
-include .env
|
||||
@@ -454,6 +454,22 @@ screenshots:
|
||||
@echo "Capturing documentation screenshots..."
|
||||
@bash scripts/screenshots/run.sh
|
||||
|
||||
# Documentation site (Hugo + Hextra via Docker)
|
||||
# First run: make dev-docs-tidy (downloads Hextra, writes docs/go.sum)
|
||||
# Then: make dev-docs (http://localhost:1313, live reload)
|
||||
dev-docs:
|
||||
HUGO_PARAMS_GITHASH=$(shell git rev-parse HEAD) docker compose -f docker-compose.docs.yml up
|
||||
|
||||
dev-docs-tidy:
|
||||
docker compose -f docker-compose.docs.yml run --rm hugo mod tidy --source docs/
|
||||
|
||||
# Run any hugo CLI command inside the docs container:
|
||||
# make hugo ARGS="version"
|
||||
# make hugo ARGS="new content/docs/guides/my-guide.md"
|
||||
ARGS ?=
|
||||
hugo:
|
||||
docker compose -f docker-compose.docs.yml run --rm hugo --source docs/ $(ARGS)
|
||||
|
||||
help:
|
||||
@echo "Available targets:"
|
||||
@echo " build - Build the CLI tool, service, and examples"
|
||||
@@ -478,6 +494,9 @@ help:
|
||||
@echo " dev-service-proxy - Build and run service with proxy (PROXY_URL=url required)"
|
||||
@echo " dev-service-stockholm - Build and run service with Stockholm frontend (requires prior 'make prepare-stockholm')"
|
||||
@echo " screenshots - Capture documentation screenshots (headless Chrome via chromedp)"
|
||||
@echo " dev-docs - Serve documentation site locally via Docker (http://localhost:1313)"
|
||||
@echo " dev-docs-tidy - Run hugo mod tidy (first run, or after hugo.toml module changes)"
|
||||
@echo " hugo ARGS=... - Run any hugo CLI command via Docker (e.g. make hugo ARGS=version)"
|
||||
@echo " dev-discover - Build and run device discovery"
|
||||
@echo " dev-info - Build and get device info (HOST=ip required)"
|
||||
@echo " dev-mdns - Build and run mDNS discovery example"
|
||||
|
||||
@@ -9,11 +9,13 @@
|
||||
> by, or otherwise connected to Bose Corporation.** See
|
||||
> [Disclaimer](#disclaimer) for the full statement.
|
||||
|
||||
## Context: Cloud Shutdown
|
||||
## The Bose Cloud Has Shut Down
|
||||
|
||||
Bose is shutting down SoundTouch cloud services on **May 6, 2026**. After that, music service browsing, preset sync, and the official SoundTouch app stop working. This toolkit lets you keep your speakers fully functional.
|
||||
Bose shut down SoundTouch cloud services on **May 6, 2026**. Presets, music service browsing, and stereo pairing no longer work through Bose's infrastructure. AfterTouch restores all of these — no Bose infrastructure required.
|
||||
|
||||
See the [Survival Guide](https://gesellix.github.io/Bose-SoundTouch/guides/SURVIVAL-GUIDE.html) for the full picture.
|
||||
See the [Survival Guide](https://gesellix.github.io/Bose-SoundTouch/docs/guides/SURVIVAL-GUIDE/) for the full picture.
|
||||
|
||||
[](https://gesellix.github.io/Bose-SoundTouch/)
|
||||
|
||||
---
|
||||
|
||||
@@ -23,15 +25,13 @@ See the [Survival Guide](https://gesellix.github.io/Bose-SoundTouch/guides/SURVI
|
||||
|
||||
A local server that replaces the Bose cloud ("AfterTouch"). Once your speaker is redirected to it, you have full control without any Bose cloud dependency. The built-in web UI at `http://localhost:8000` handles all setup — no config files needed to get started.
|
||||
|
||||
If you don't want to run a server for this - no problem. The service is small enough to run on the SoundTouch itself. See the [On-Device Installer](./scripts/on-device-install/README.md) for instructions.
|
||||
Not sure which approach fits your situation? See the [Deployment Overview](./docs/content/docs/guides/DEPLOYMENT-OVERVIEW.md) — it compares running AfterTouch on a Raspberry Pi or other always-on host against running it directly on the SoundTouch speaker, with links to step-by-step walkthroughs for each path.
|
||||
|
||||
**Two scenarios:**
|
||||
**Getting started:**
|
||||
|
||||
**Before shutdown — migrate your existing setup**
|
||||
While the Bose cloud is still running, use `soundtouch-backup` to save your account data. The local service web UI then helps with the migration so your speaker keeps its presets and credentials.
|
||||
**Already migrated before May 6** — your presets and credentials are preserved. AfterTouch picks up where the Bose cloud left off.
|
||||
|
||||
**After shutdown or factory reset — start fresh**
|
||||
Create a local account, configure your speakers, and start using them immediately. No Bose infrastructure required.
|
||||
**Starting fresh (or after a factory reset)** — create a local account, configure your speakers, and start using them immediately.
|
||||
|
||||
**Redirecting your speaker**
|
||||
|
||||
@@ -50,7 +50,7 @@ The web UI walks you through each method. DNS redirect requires HTTPS — the se
|
||||
|
||||
Some setup steps require SSH access to the speaker. Enable it once per device: create a file named `remote_services` on a FAT-formatted USB drive (the drive may need its bootable flag set — see [SoundCork issue #172](https://github.com/deborahgu/soundcork/issues/172)), and insert it while the speaker is powered on. After reboot, root SSH is available with no password.
|
||||
|
||||
See [Device Initial Setup](https://gesellix.github.io/Bose-SoundTouch/guides/DEVICE-INITIAL-SETUP.html) and [Migration Guide](https://gesellix.github.io/Bose-SoundTouch/guides/MIGRATION-GUIDE.html) for step-by-step instructions.
|
||||
See [Device Initial Setup](https://gesellix.github.io/Bose-SoundTouch/docs/guides/DEVICE-INITIAL-SETUP/) and [Migration Guide](https://gesellix.github.io/Bose-SoundTouch/docs/guides/MIGRATION-GUIDE/) for step-by-step instructions.
|
||||
|
||||
---
|
||||
|
||||
@@ -66,7 +66,7 @@ See the [soundtouch-backup README](cmd/soundtouch-backup/README.md) for usage.
|
||||
|
||||
Command-line control of any SoundTouch device: play/pause/volume, presets, source selection, multiroom zones, device discovery, and more. Works entirely over the local network — no cloud dependency. Well-suited for scripting and home automation.
|
||||
|
||||
See the [CLI Reference](https://gesellix.github.io/Bose-SoundTouch/guides/CLI-REFERENCE.html) for full usage.
|
||||
See the [CLI Reference](https://gesellix.github.io/Bose-SoundTouch/docs/guides/CLI-REFERENCE/) for full usage.
|
||||
|
||||
---
|
||||
|
||||
@@ -86,21 +86,21 @@ See the [soundtouch-web README](cmd/soundtouch-web/README.md) for usage.
|
||||
go get github.com/gesellix/bose-soundtouch
|
||||
```
|
||||
|
||||
See the [API Reference](https://gesellix.github.io/Bose-SoundTouch/reference/API-ENDPOINTS.html) and [pkg.go.dev](https://pkg.go.dev/github.com/gesellix/bose-soundtouch) for documentation.
|
||||
See the [API Reference](https://gesellix.github.io/Bose-SoundTouch/docs/reference/API-ENDPOINTS/) and [pkg.go.dev](https://pkg.go.dev/github.com/gesellix/bose-soundtouch) for documentation.
|
||||
|
||||
---
|
||||
|
||||
## Documentation
|
||||
|
||||
- [Getting Started](https://gesellix.github.io/Bose-SoundTouch/guides/GETTING-STARTED.html)
|
||||
- [Survival Guide](https://gesellix.github.io/Bose-SoundTouch/guides/SURVIVAL-GUIDE.html)
|
||||
- [Migration Guide](https://gesellix.github.io/Bose-SoundTouch/guides/MIGRATION-GUIDE.html)
|
||||
- [Device Initial Setup](https://gesellix.github.io/Bose-SoundTouch/guides/DEVICE-INITIAL-SETUP.html)
|
||||
- [Migration & Safety Guide](https://gesellix.github.io/Bose-SoundTouch/guides/MIGRATION-SAFETY.html)
|
||||
- [CLI Reference](https://gesellix.github.io/Bose-SoundTouch/guides/CLI-REFERENCE.html)
|
||||
- [SoundTouch Service Guide](https://gesellix.github.io/Bose-SoundTouch/guides/SOUNDTOUCH-SERVICE.html)
|
||||
- [HTTPS & CA Setup](https://gesellix.github.io/Bose-SoundTouch/guides/HTTPS-SETUP.html)
|
||||
- [API Reference](https://gesellix.github.io/Bose-SoundTouch/reference/API-ENDPOINTS.html)
|
||||
- [Getting Started](https://gesellix.github.io/Bose-SoundTouch/docs/guides/GETTING-STARTED/)
|
||||
- [Survival Guide](https://gesellix.github.io/Bose-SoundTouch/docs/guides/SURVIVAL-GUIDE/)
|
||||
- [Migration Guide](https://gesellix.github.io/Bose-SoundTouch/docs/guides/MIGRATION-GUIDE/)
|
||||
- [Device Initial Setup](https://gesellix.github.io/Bose-SoundTouch/docs/guides/DEVICE-INITIAL-SETUP/)
|
||||
- [Migration & Safety Guide](https://gesellix.github.io/Bose-SoundTouch/docs/guides/MIGRATION-SAFETY/)
|
||||
- [CLI Reference](https://gesellix.github.io/Bose-SoundTouch/docs/guides/CLI-REFERENCE/)
|
||||
- [SoundTouch Service Guide](https://gesellix.github.io/Bose-SoundTouch/docs/guides/SOUNDTOUCH-SERVICE/)
|
||||
- [HTTPS & CA Setup](https://gesellix.github.io/Bose-SoundTouch/docs/guides/HTTPS-SETUP/)
|
||||
- [API Reference](https://gesellix.github.io/Bose-SoundTouch/docs/reference/API-ENDPOINTS/)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package main
|
||||
|
||||
import "strings"
|
||||
|
||||
// sanitizeLog strips newline characters from s to prevent log-injection
|
||||
// (CodeQL go/log-injection). Values from speakers, HTTP requests, and
|
||||
// external APIs may contain attacker-controlled newlines.
|
||||
func sanitizeLog(s string) string {
|
||||
s = strings.ReplaceAll(s, "\n", `\n`)
|
||||
s = strings.ReplaceAll(s, "\r", `\r`)
|
||||
|
||||
return s
|
||||
}
|
||||
@@ -43,10 +43,10 @@ func main() {
|
||||
log.Fatalf("start fake speaker: %v", err)
|
||||
}
|
||||
|
||||
log.Printf("fake speaker HTTP listening on http://%s", s.HTTPAddr())
|
||||
log.Printf("fake speaker HTTP listening on http://%s", sanitizeLog(s.HTTPAddr()))
|
||||
|
||||
if addr := s.TelnetAddr(); addr != "" {
|
||||
log.Printf("fake speaker telnet listening on tcp://%s", addr)
|
||||
log.Printf("fake speaker telnet listening on tcp://%s", sanitizeLog(addr))
|
||||
}
|
||||
|
||||
if *register != "" {
|
||||
@@ -58,7 +58,7 @@ func main() {
|
||||
if err := registerWithService(*register, target); err != nil {
|
||||
log.Printf("self-register failed: %v (continuing anyway)", err)
|
||||
} else {
|
||||
log.Printf("registered %s with service at %s", target, *register)
|
||||
log.Printf("registered %s with service at %s", sanitizeLog(target), sanitizeLog(*register))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package main
|
||||
|
||||
import "strings"
|
||||
|
||||
// sanitizeLog strips newline characters from s to prevent log-injection
|
||||
// (CodeQL go/log-injection). Values from speakers, HTTP requests, and
|
||||
// external APIs may contain attacker-controlled newlines.
|
||||
func sanitizeLog(s string) string {
|
||||
s = strings.ReplaceAll(s, "\n", `\n`)
|
||||
s = strings.ReplaceAll(s, "\r", `\r`)
|
||||
|
||||
return s
|
||||
}
|
||||
@@ -116,7 +116,7 @@ func main() {
|
||||
defer close(entries)
|
||||
|
||||
if *verbose {
|
||||
log.Printf("mDNS: Starting scan for service '%s' with timeout %v", *service, *timeout)
|
||||
log.Printf("mDNS: Starting scan for service '%s' with timeout %v", sanitizeLog(*service), *timeout)
|
||||
}
|
||||
|
||||
// Query for services
|
||||
@@ -196,7 +196,7 @@ func parseServiceEntry(entry *mdns.ServiceEntry, verbose bool) *ServiceInfo {
|
||||
|
||||
if verbose {
|
||||
log.Printf("mDNS: Received service entry: Name='%s', Host='%s', Port=%d, AddrV4=%v, AddrV6=%v",
|
||||
entry.Name, entry.Host, entry.Port, entry.AddrV4, entry.AddrV6)
|
||||
sanitizeLog(entry.Name), sanitizeLog(entry.Host), entry.Port, entry.AddrV4, entry.AddrV6)
|
||||
}
|
||||
|
||||
service := &ServiceInfo{
|
||||
|
||||
@@ -207,6 +207,6 @@ Running `cloud` and `local` separately produces two archives. To combine them, u
|
||||
|
||||
## See also
|
||||
|
||||
- [Cloud Shutdown Survival Guide](../../docs/guides/SURVIVAL-GUIDE.md) — full migration context
|
||||
- [Cloud Shutdown Survival Guide](../../docs/content/docs/guides/SURVIVAL-GUIDE.md) — full migration context
|
||||
- [`soundtouch-cli`](../soundtouch-cli/) — live device control
|
||||
- [`soundtouch-service`](../soundtouch-service/) — local cloud replacement
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// cloudCommand assembles the `soundtouch-cli cloud …` command group.
|
||||
// All subcommands talk to the AfterTouch service (not the speaker directly)
|
||||
// and require --service-url.
|
||||
func cloudCommand() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "cloud",
|
||||
Usage: "Manage AfterTouch service data (sources, accounts, devices)",
|
||||
Subcommands: []*cli.Command{
|
||||
cloudSourceCmd(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func cloudSourceCmd() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "source",
|
||||
Usage: "Manage sources stored in AfterTouch",
|
||||
Subcommands: []*cli.Command{
|
||||
cloudSourceRemoveCmd(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func cloudSourceRemoveCmd() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "remove",
|
||||
Usage: "Remove a source from AfterTouch's datastore for a specific device",
|
||||
Flags: append(CloudCommonFlags,
|
||||
&cli.StringFlag{
|
||||
Name: "account",
|
||||
Aliases: []string{"a"},
|
||||
Usage: "Account ID",
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "device",
|
||||
Aliases: []string{"d"},
|
||||
Usage: "Device ID",
|
||||
Required: true,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "id",
|
||||
Usage: "Source ID to remove (e.g. 10002)",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "type",
|
||||
Aliases: []string{"t"},
|
||||
Usage: "Source type to remove (e.g. INTERNET_RADIO). Resolved to a canonical ID; fails if multiple sources share the type.",
|
||||
},
|
||||
),
|
||||
Action: cloudSourceRemove,
|
||||
}
|
||||
}
|
||||
|
||||
// canonicalSourceID maps well-known SourceKeyType values to their canonical IDs.
|
||||
// Used to resolve --type to an ID without requiring a round-trip GET.
|
||||
// TODO We need to ensure that ids here are consistent with the ones used in the AfterTouch service.
|
||||
var canonicalSourceID = map[string]string{
|
||||
"AUX": "10001",
|
||||
"INTERNET_RADIO": "10002",
|
||||
"LOCAL_INTERNET_RADIO": "10003",
|
||||
"TUNEIN": "10004",
|
||||
"RADIO_BROWSER": "10005",
|
||||
}
|
||||
|
||||
func cloudSourceRemove(c *cli.Context) error {
|
||||
serviceURL := strings.TrimRight(c.String("service-url"), "/")
|
||||
account := c.String("account")
|
||||
device := c.String("device")
|
||||
sourceID := c.String("id")
|
||||
sourceType := strings.ToUpper(c.String("type"))
|
||||
|
||||
if sourceID == "" && sourceType == "" {
|
||||
return fmt.Errorf("one of --id or --type is required")
|
||||
}
|
||||
|
||||
if sourceID != "" && sourceType != "" {
|
||||
return fmt.Errorf("only one of --id or --type may be given")
|
||||
}
|
||||
|
||||
if sourceType != "" {
|
||||
id, ok := canonicalSourceID[sourceType]
|
||||
if !ok {
|
||||
return fmt.Errorf("unknown source type %q; use --id for non-canonical sources", sourceType)
|
||||
}
|
||||
|
||||
sourceID = id
|
||||
}
|
||||
|
||||
url := fmt.Sprintf("%s/setup/sources/%s/%s/%s", serviceURL, account, device, sourceID)
|
||||
|
||||
req, err := http.NewRequest(http.MethodDelete, url, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("build request: %w", err)
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("request failed: %w", err)
|
||||
}
|
||||
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
if resp.StatusCode == http.StatusNoContent {
|
||||
PrintSuccess(fmt.Sprintf("Removed source %s from device %s (account %s)", sourceID, device, account))
|
||||
|
||||
if sourceType != "" {
|
||||
fmt.Printf(" Type: %s\n", sourceType)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
body, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<10))
|
||||
|
||||
return fmt.Errorf("service returned %d: %s", resp.StatusCode, strings.TrimSpace(string(body)))
|
||||
}
|
||||
@@ -568,7 +568,7 @@ type VerboseLogger struct{}
|
||||
|
||||
func (v *VerboseLogger) Printf(format string, args ...interface{}) {
|
||||
timestamp := time.Now().Format("15:04:05")
|
||||
fmt.Printf("[%s] [WebSocket] %s\n", timestamp, fmt.Sprintf(format, args...))
|
||||
fmt.Printf("[%s] [WebSocket] %s\n", timestamp, sanitizeLog(fmt.Sprintf(format, args...)))
|
||||
}
|
||||
|
||||
type SilentLogger struct{}
|
||||
|
||||
@@ -3,6 +3,8 @@ package main
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
@@ -685,3 +687,51 @@ func boolToStatus(b bool) string {
|
||||
|
||||
return "❌ No"
|
||||
}
|
||||
|
||||
// notifySourcesUpdated POSTs a sourcesUpdated notification directly to the
|
||||
// speaker's :8090/notification endpoint. The speaker re-fetches its source
|
||||
// list from AfterTouch immediately. Requires network access to the speaker.
|
||||
func notifySourcesUpdated(c *cli.Context) error {
|
||||
if err := RequireHost(c); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
clientConfig := GetClientConfig(c)
|
||||
|
||||
client, err := CreateSoundTouchClient(clientConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
info, err := client.GetDeviceInfo()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get device info from %s: %w", clientConfig.Host, err)
|
||||
}
|
||||
|
||||
body := fmt.Sprintf(`<updates deviceID="%s"><sourcesUpdated/></updates>`, info.DeviceID)
|
||||
notifyURL := fmt.Sprintf("http://%s:8090/notification", clientConfig.Host)
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, notifyURL, strings.NewReader(body))
|
||||
if err != nil {
|
||||
return fmt.Errorf("build request: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/xml")
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("post to speaker: %w", err)
|
||||
}
|
||||
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
if resp.StatusCode >= 300 {
|
||||
respBody, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<10))
|
||||
|
||||
return fmt.Errorf("speaker returned %d: %s", resp.StatusCode, strings.TrimSpace(string(respBody)))
|
||||
}
|
||||
|
||||
PrintSuccess(fmt.Sprintf("Sent sourcesUpdated to %s (%s)", info.DeviceID, clientConfig.Host))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -19,6 +19,16 @@ import (
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// CloudCommonFlags defines flags for commands that talk to the AfterTouch service.
|
||||
var CloudCommonFlags = []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "service-url",
|
||||
Usage: "AfterTouch service URL",
|
||||
Required: true,
|
||||
EnvVars: []string{"AFTERTOUCH_URL"},
|
||||
},
|
||||
}
|
||||
|
||||
// CommonFlags defines flags that are shared across multiple commands
|
||||
var CommonFlags = []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
@@ -322,7 +332,7 @@ func PrintSuccess(message string) {
|
||||
|
||||
// PrintError prints a standard error message
|
||||
func PrintError(message string) {
|
||||
fmt.Printf("✗ %s\n", message)
|
||||
fmt.Printf("✗ %s\n", sanitizeLog(message))
|
||||
}
|
||||
|
||||
// PrintWarning prints a standard warning message
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package main
|
||||
|
||||
import "strings"
|
||||
|
||||
// sanitizeLog strips newline characters from s to prevent log-injection
|
||||
// (CodeQL go/log-injection). Values from speakers, HTTP requests, and
|
||||
// external APIs may contain attacker-controlled newlines.
|
||||
func sanitizeLog(s string) string {
|
||||
s = strings.ReplaceAll(s, "\n", `\n`)
|
||||
s = strings.ReplaceAll(s, "\r", `\r`)
|
||||
|
||||
return s
|
||||
}
|
||||
@@ -1146,6 +1146,12 @@ func main() {
|
||||
Action: introspectAllServices,
|
||||
Before: RequireHost,
|
||||
},
|
||||
{
|
||||
Name: "notify-updated",
|
||||
Usage: "Tell the speaker to re-fetch its source list from AfterTouch",
|
||||
Action: notifySourcesUpdated,
|
||||
Before: RequireHost,
|
||||
},
|
||||
},
|
||||
},
|
||||
// Bass commands
|
||||
@@ -2238,6 +2244,10 @@ func main() {
|
||||
// Defined in cmd_setup.go to keep the top-level command list readable.
|
||||
app.Commands = append(app.Commands, setupCommand())
|
||||
|
||||
// AfterTouch service management (sources, accounts, devices).
|
||||
// Defined in cmd_cloud.go.
|
||||
app.Commands = append(app.Commands, cloudCommand())
|
||||
|
||||
// Sort commands alphabetically (including subcommands and flags recursively)
|
||||
sortCommands(app.Commands)
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package main
|
||||
|
||||
import "strings"
|
||||
|
||||
// sanitizeLog strips newline characters from s to prevent log-injection
|
||||
// (CodeQL go/log-injection). Values from speakers, HTTP requests, and
|
||||
// external APIs may contain attacker-controlled newlines.
|
||||
func sanitizeLog(s string) string {
|
||||
s = strings.ReplaceAll(s, "\n", `\n`)
|
||||
s = strings.ReplaceAll(s, "\r", `\r`)
|
||||
|
||||
return s
|
||||
}
|
||||
@@ -71,13 +71,13 @@ func initializeDefaultSources(ds *datastore.DataStore) {
|
||||
for i := range allDevices {
|
||||
dev := &allDevices[i]
|
||||
if sources, errGet := ds.GetConfiguredSources(dev.AccountID, dev.DeviceID); errGet == nil {
|
||||
log.Printf("Initializing default Sources.xml for existing device %s", dev.DeviceID)
|
||||
log.Printf("Initializing default Sources.xml for existing device %s", sanitizeLog(dev.DeviceID))
|
||||
|
||||
// Find default sources and merge them if missing or outdated tokens.
|
||||
// claimed tracks which stored sources have already been matched by a default,
|
||||
// so two defaults with the same SourceKeyType but different SourceProviderIDs
|
||||
// (e.g. INTERNET_RADIO/2 and INTERNET_RADIO/39) are treated as distinct entries.
|
||||
defaults := ds.GetDefaultSources()
|
||||
defaults := ds.GetInitialSources()
|
||||
modified := false
|
||||
claimed := make(map[int]bool)
|
||||
|
||||
@@ -103,13 +103,13 @@ func initializeDefaultSources(ds *datastore.DataStore) {
|
||||
claimed[foundIdx] = true
|
||||
|
||||
if sources[foundIdx].Secret == "" && def.Secret != "" {
|
||||
log.Printf("Initializing missing token for source %s on device %s", def.SourceKeyType, dev.DeviceID)
|
||||
log.Printf("Initializing missing token for source %s on device %s", sanitizeLog(def.SourceKeyType), sanitizeLog(dev.DeviceID))
|
||||
sources[foundIdx].Secret = def.Secret
|
||||
sources[foundIdx].SecretType = def.SecretType
|
||||
modified = true
|
||||
}
|
||||
} else {
|
||||
log.Printf("Adding missing default source %s (providerID=%s) to device %s", def.SourceKeyType, def.SourceProviderID, dev.DeviceID)
|
||||
log.Printf("Adding missing default source %s (providerID=%s) to device %s", sanitizeLog(def.SourceKeyType), sanitizeLog(def.SourceProviderID), sanitizeLog(dev.DeviceID))
|
||||
sources = append(sources, def)
|
||||
modified = true
|
||||
}
|
||||
@@ -117,7 +117,7 @@ func initializeDefaultSources(ds *datastore.DataStore) {
|
||||
|
||||
if modified {
|
||||
if errSave := ds.SaveConfiguredSources(dev.AccountID, dev.DeviceID, sources); errSave != nil {
|
||||
log.Printf("Failed to save updated sources for %s: %v", dev.DeviceID, errSave)
|
||||
log.Printf("Failed to save updated sources for %s: %v", sanitizeLog(dev.DeviceID), errSave)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -147,7 +147,7 @@ func initMusicServices(config serviceConfig, server *handlers.Server) {
|
||||
clientIDPrefix = clientIDPrefix[:8]
|
||||
}
|
||||
|
||||
log.Printf("Spotify service initialized (client ID: %s...)", clientIDPrefix)
|
||||
log.Printf("Spotify service initialized (client ID: %s...)", sanitizeLog(clientIDPrefix))
|
||||
}
|
||||
|
||||
if config.amazonClientID != "" {
|
||||
@@ -172,7 +172,7 @@ func initMusicServices(config serviceConfig, server *handlers.Server) {
|
||||
clientIDPrefix = clientIDPrefix[:8]
|
||||
}
|
||||
|
||||
log.Printf("Amazon Music service initialized (client ID: %s...)", clientIDPrefix)
|
||||
log.Printf("Amazon Music service initialized (client ID: %s...)", sanitizeLog(clientIDPrefix))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,7 +188,7 @@ func logBufferCapacityFromEnv(defaultCap int) int {
|
||||
|
||||
v, err := strconv.Atoi(raw)
|
||||
if err != nil {
|
||||
log.Printf("[Logs] Invalid SOUNDTOUCH_LOG_BUFFER_LINES=%q, using default %d", raw, defaultCap)
|
||||
log.Printf("[Logs] Invalid SOUNDTOUCH_LOG_BUFFER_LINES=%q, using default %d", sanitizeLog(raw), defaultCap)
|
||||
return defaultCap
|
||||
}
|
||||
|
||||
@@ -415,7 +415,7 @@ func main() {
|
||||
persisted := applyPersistedSettings(ds, &config)
|
||||
|
||||
if persisted.ServerURL == "" {
|
||||
log.Printf("Creating default settings.json in %s", config.dataDir)
|
||||
log.Printf("Creating default settings.json in %s", sanitizeLog(config.dataDir))
|
||||
persisted = createDefaultSettings(ds, config)
|
||||
}
|
||||
|
||||
@@ -468,7 +468,7 @@ func main() {
|
||||
server.SetShortcuts(persisted.Shortcuts)
|
||||
|
||||
for path, status := range persisted.Shortcuts {
|
||||
log.Printf("Warning: configured shortcut: %s -> %d", path, status)
|
||||
log.Printf("Warning: configured shortcut: %s -> %d", sanitizeLog(path), status)
|
||||
}
|
||||
|
||||
recorder := proxy.NewRecorder(config.dataDir)
|
||||
@@ -477,11 +477,11 @@ func main() {
|
||||
|
||||
patterns, err := proxy.LoadPatterns(patternsPath)
|
||||
if err != nil {
|
||||
log.Printf("Warning: Failed to load patterns from %s: %v", patternsPath, err)
|
||||
log.Printf("Warning: Failed to load patterns from %s: %v", sanitizeLog(patternsPath), err)
|
||||
}
|
||||
|
||||
if len(patterns) == 0 {
|
||||
log.Printf("Creating default patterns at %s", patternsPath)
|
||||
log.Printf("Creating default patterns at %s", sanitizeLog(patternsPath))
|
||||
|
||||
patterns = proxy.DefaultPatterns()
|
||||
|
||||
@@ -512,17 +512,26 @@ func main() {
|
||||
} else {
|
||||
stockholmHandler = sh
|
||||
|
||||
log.Printf("Stockholm frontend enabled from %s", config.stockholmDir)
|
||||
log.Printf("Stockholm frontend enabled from %s", sanitizeLog(config.stockholmDir))
|
||||
}
|
||||
}
|
||||
|
||||
r := setupRouter(server, stockholmHandler)
|
||||
|
||||
log.Printf("Go service starting on %s", config.serverURL)
|
||||
// Bind the listener before logging so we print the true
|
||||
// effective port (handles :0 and catches "address already
|
||||
// in use" before the TLS goroutine launches).
|
||||
ln, err := net.Listen("tcp", config.addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to listen on %s: %w", config.addr, err)
|
||||
}
|
||||
|
||||
log.Printf("Go service listening on %s (configured: %s, server URL: %s)",
|
||||
ln.Addr().String(), sanitizeLog(config.addr), sanitizeLog(config.serverURL))
|
||||
|
||||
// TLS cert generation can be slow on constrained hardware; run it in the
|
||||
// background so the HTTP server is available immediately.
|
||||
log.Printf("HTTPS setup running in background; %s will be available shortly", config.httpsServerURL)
|
||||
log.Printf("HTTPS setup running in background; %s will be available shortly", sanitizeLog(config.httpsServerURL))
|
||||
|
||||
go func() {
|
||||
tlsConfig, err := cm.GetServerTLSConfig(config.domains)
|
||||
@@ -536,7 +545,7 @@ func main() {
|
||||
runHTTPSPreflight(config.httpsServerURL, config.serverURL, config.dnsEnabled, server.ResolveServerURLIPForPreflight)
|
||||
}()
|
||||
|
||||
return http.ListenAndServe(config.addr, r)
|
||||
return http.Serve(ln, r)
|
||||
},
|
||||
Commands: []*cli.Command{
|
||||
{
|
||||
@@ -652,7 +661,7 @@ func loadConfig(c *cli.Context) serviceConfig {
|
||||
|
||||
discoveryInterval, err := time.ParseDuration(discoveryIntervalStr)
|
||||
if err != nil {
|
||||
log.Printf("Warning: Failed to parse discovery interval %s, using default 5m: %v", discoveryIntervalStr, err)
|
||||
log.Printf("Warning: Failed to parse discovery interval %s, using default 5m: %v", sanitizeLog(discoveryIntervalStr), err)
|
||||
|
||||
discoveryInterval = 5 * time.Minute
|
||||
}
|
||||
@@ -1244,6 +1253,7 @@ func setupRouter(server *handlers.Server, stockholmHandler *stockholm.Handler) *
|
||||
r.Get("/dns-discoveries", server.HandleGetDNSDiscoveries)
|
||||
r.Get("/dns-discoveries/download", server.HandleDownloadDNSDiscoveries)
|
||||
r.Delete("/dns-discoveries", server.HandleClearDNSDiscoveries)
|
||||
r.Delete("/sources/{account}/{device}/{sourceID}", server.HandleDeleteSource)
|
||||
|
||||
r.Get("/devices/{deviceId}/events", server.HandleGetDeviceEvents)
|
||||
r.Get("/device-summary/{deviceId}", server.HandleDeviceSummary)
|
||||
@@ -1293,7 +1303,7 @@ func startHTTPSServer(httpsAddr string, r http.Handler, tlsConfig *tls.Config, h
|
||||
return &tlsConfig.Certificates[0], nil
|
||||
}
|
||||
|
||||
log.Printf("[TLS] ❌ No certificate available for %s", clientHello.ServerName)
|
||||
log.Printf("[TLS] ❌ No certificate available for %s", sanitizeLog(clientHello.ServerName))
|
||||
|
||||
return nil, fmt.Errorf("no certificate available for %s", clientHello.ServerName)
|
||||
}
|
||||
@@ -1305,8 +1315,6 @@ func startHTTPSServer(httpsAddr string, r http.Handler, tlsConfig *tls.Config, h
|
||||
ErrorLog: log.Default(), // Ensure error logging is enabled
|
||||
}
|
||||
|
||||
log.Printf("Go service starting HTTPS on %s", httpsServerURL)
|
||||
|
||||
go func() {
|
||||
listener, err := net.Listen("tcp", httpsAddr)
|
||||
if err != nil {
|
||||
@@ -1314,6 +1322,9 @@ func startHTTPSServer(httpsAddr string, r http.Handler, tlsConfig *tls.Config, h
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("Go service listening HTTPS on %s (server URL: %s)",
|
||||
listener.Addr().String(), sanitizeLog(httpsServerURL))
|
||||
|
||||
tlsListener := tls.NewListener(listener, tlsConfig)
|
||||
|
||||
// Wrap listener to log connection attempts
|
||||
@@ -1360,9 +1371,9 @@ func runHTTPSPreflight(httpsServerURL, serverURL string, dnsEnabled bool, resolv
|
||||
case res.Skipped:
|
||||
// Listener already on :443 — nothing to say.
|
||||
case res.NotApplicable:
|
||||
log.Printf("HTTPS pre-flight: :443 check skipped — %s", res.Reason)
|
||||
log.Printf("HTTPS pre-flight: :443 check skipped — %s", sanitizeLog(res.Reason))
|
||||
default:
|
||||
log.Printf("HTTPS pre-flight: :443 reachable at localhost and %s ✓", res.LANHost)
|
||||
log.Printf("HTTPS pre-flight: :443 reachable at localhost and %s ✓", sanitizeLog(res.LANHost))
|
||||
}
|
||||
|
||||
return
|
||||
@@ -1437,7 +1448,7 @@ func (c *loggingTLSConn) Read(b []byte) (n int, err error) {
|
||||
if strings.Contains(err.Error(), "tls:") ||
|
||||
strings.Contains(err.Error(), "handshake") ||
|
||||
strings.Contains(err.Error(), "certificate") {
|
||||
log.Printf("[TLS] ❌ Handshake failed from %s: %v", c.addr, err)
|
||||
log.Printf("[TLS] ❌ Handshake failed from %s: %v", sanitizeLog(c.addr.String()), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ DELETE /setup/devices/{deviceId} handlers.(
|
||||
DELETE /setup/dns-discoveries handlers.(*Server).HandleClearDNSDiscoveries-fm
|
||||
DELETE /setup/interactions/sessions handlers.(*Server).HandleCleanupSessions-fm
|
||||
DELETE /setup/interactions/sessions/{session} handlers.(*Server).HandleDeleteSession-fm
|
||||
DELETE /setup/sources/{account}/{device}/{sourceID} handlers.(*Server).HandleDeleteSource-fm
|
||||
DELETE /streaming/account/{account}/device/{device} handlers.(*Server).HandleMargeRemoveDevice-fm
|
||||
DELETE /streaming/account/{account}/device/{device}/preset/{presetNumber} handlers.(*Server).HandleMargeRemovePreset-fm
|
||||
DELETE /streaming/account/{account}/group/{groupId} handlers.(*Server).HandleMargeDeleteGroup-fm
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package main
|
||||
|
||||
import "strings"
|
||||
|
||||
// sanitizeLog strips newline characters from s to prevent log-injection
|
||||
// (CodeQL go/log-injection). Values from speakers, HTTP requests, and
|
||||
// external APIs may contain attacker-controlled newlines.
|
||||
func sanitizeLog(s string) string {
|
||||
s = strings.ReplaceAll(s, "\n", `\n`)
|
||||
s = strings.ReplaceAll(s, "\r", `\r`)
|
||||
|
||||
return s
|
||||
}
|
||||
@@ -86,7 +86,7 @@ func main() {
|
||||
}
|
||||
|
||||
if rawBind != "" && bindAddr != rawBind {
|
||||
log.Printf("Resolved --bind %q to %s", rawBind, bindAddr)
|
||||
log.Printf("Resolved --bind %q to %s", sanitizeLog(rawBind), sanitizeLog(bindAddr))
|
||||
}
|
||||
|
||||
rawIface := c.String("interface")
|
||||
@@ -94,7 +94,7 @@ func main() {
|
||||
|
||||
ifaceName := defaultDiscoveryInterface(rawIface, rawBind, bindAddr)
|
||||
if rawIface == "" && ifaceName != "" {
|
||||
log.Printf("Defaulting --interface to %q from --bind", ifaceName)
|
||||
log.Printf("Defaulting --interface to %q from --bind", sanitizeLog(ifaceName))
|
||||
}
|
||||
|
||||
addr := ":" + port
|
||||
@@ -131,7 +131,7 @@ func main() {
|
||||
r := chi.NewRouter()
|
||||
webApp.Mount(r, discoveryService)
|
||||
|
||||
log.Printf("AfterTouch Web UI starting on http://%s", addr)
|
||||
log.Printf("AfterTouch Web UI starting on http://%s", sanitizeLog(addr))
|
||||
|
||||
return http.ListenAndServe(addr, r)
|
||||
},
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
package main
|
||||
|
||||
import "strings"
|
||||
|
||||
// sanitizeLog strips newline characters from s to prevent log-injection
|
||||
// (CodeQL go/log-injection). Values from speakers, HTTP requests, and
|
||||
// external APIs may contain attacker-controlled newlines.
|
||||
func sanitizeLog(s string) string {
|
||||
s = strings.ReplaceAll(s, "\n", `\n`)
|
||||
s = strings.ReplaceAll(s, "\r", `\r`)
|
||||
|
||||
return s
|
||||
}
|
||||
@@ -573,7 +573,7 @@ type VerboseLogger struct{}
|
||||
|
||||
func (v *VerboseLogger) Printf(format string, args ...interface{}) {
|
||||
timestamp := time.Now().Format("15:04:05")
|
||||
fmt.Printf("[%s] [WebSocket] %s\n", timestamp, fmt.Sprintf(format, args...))
|
||||
fmt.Printf("[%s] [WebSocket] %s\n", timestamp, sanitizeLog(fmt.Sprintf(format, args...)))
|
||||
}
|
||||
|
||||
// SilentLogger provides no-op WebSocket logging
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
# Local Hugo/Hextra documentation server.
|
||||
#
|
||||
# Usage:
|
||||
# make dev-docs # start the live-reload server (http://localhost:1313)
|
||||
# make dev-docs-tidy # run hugo mod tidy (required on first run, or after
|
||||
# # changing hugo.toml module imports)
|
||||
# make hugo ARGS="..." # run any other hugo CLI command, e.g.
|
||||
# # make hugo ARGS="version"
|
||||
# # make hugo ARGS="new content/blog/my-post.md"
|
||||
#
|
||||
# The hugomods/hugo:exts image bundles Hugo extended + Go so Hugo modules
|
||||
# (Hextra) work without any extra tooling on the host.
|
||||
|
||||
services:
|
||||
hugo:
|
||||
image: hugomods/hugo:exts
|
||||
# --source docs/ because docs/ is the Hugo root inside the repo.
|
||||
# --baseURL / overrides the production subpath (/Bose-SoundTouch/) so
|
||||
# absolute links work at http://localhost:1313/ during local development.
|
||||
# The full repo is mounted so enableGitInfo can read git history.
|
||||
command: server --source docs/ --baseURL / --bind 0.0.0.0 --buildDrafts --navigateToChanged
|
||||
ports:
|
||||
- "1313:1313"
|
||||
volumes:
|
||||
- .:/src
|
||||
# Persist the Hugo module cache across runs so 'hugo mod tidy' only
|
||||
# downloads Hextra once.
|
||||
- hugo-mod-cache:/root/.cache/hugo_cache
|
||||
working_dir: /src
|
||||
environment:
|
||||
- HUGO_PARAMS_GITHASH
|
||||
|
||||
volumes:
|
||||
hugo-mod-cache:
|
||||
-113
@@ -1,113 +0,0 @@
|
||||
# Table of Contents
|
||||
|
||||
* [Introduction](README.md)
|
||||
|
||||
## User Guides
|
||||
* [Device-Local Install Journeys](DEVICE-LOCAL-INSTALL.md)
|
||||
* [Cloud Shutdown Survival Guide](guides/SURVIVAL-GUIDE.md)
|
||||
* [Self-Hosting AfterTouch](guides/SELF-HOSTING.md)
|
||||
* [Connecting Music Services](guides/MUSIC-SERVICES.md)
|
||||
* [Migration & Safety Guide](guides/MIGRATION-SAFETY.md)
|
||||
* [CLI Reference](guides/CLI-REFERENCE.md)
|
||||
* [Backup Tool](../cmd/soundtouch-backup/README.md)
|
||||
* [Getting Started](guides/GETTING-STARTED.md)
|
||||
* [SoundTouch Service](guides/SOUNDTOUCH-SERVICE.md)
|
||||
* [Initial Device Setup](guides/DEVICE-INITIAL-SETUP.md)
|
||||
* [Capture Device Pairing Traffic](guides/CAPTURE-DEVICE-PAIRING.md)
|
||||
* [Capture Migration Traffic](guides/CAPTURE-MIGRATION-TRAFFIC.md)
|
||||
* [Device Setup Flow](DEVICE-SETUP.md)
|
||||
* [MAC Address Mapping](guides/MAC-ADDRESS-MAPPING.md)
|
||||
* [HTTPS Setup](guides/HTTPS-SETUP.md)
|
||||
* [Deployment](guides/DEPLOYMENT.md)
|
||||
* [Raspberry Pi Guide](guides/RASPBERRY-PI.md)
|
||||
* [Troubleshooting](guides/TROUBLESHOOTING.md)
|
||||
* [IoT Implementation Guide](guides/IOT-IMPLEMENTATION-GUIDE.md)
|
||||
* [Migration Guide](guides/MIGRATION-GUIDE.md)
|
||||
* [MQTT Integration Design](guides/MQTT-INTEGRATION-DESIGN.md)
|
||||
* [Useful Links](#useful-links)
|
||||
|
||||
### Useful Links
|
||||
* [Cloud Shutdown Survival Guide](guides/SURVIVAL-GUIDE.md)
|
||||
* [Raspberry Pi Installer](../scripts/raspberry-pi/README.md)
|
||||
* [Updating the Service](../scripts/raspberry-pi/README.md#updating-to-a-new-version)
|
||||
* [CLI Reference](guides/CLI-REFERENCE.md)
|
||||
|
||||
## Technical Reference
|
||||
* [API Cookbook](reference/API-COOKBOOK.md)
|
||||
* [API Endpoints](reference/API-ENDPOINTS.md)
|
||||
* [Spotify Account Addition](reference/spotify-account-addition.md)
|
||||
* [Cloud API Emulation](reference/CLOUD-API.md)
|
||||
* [System Endpoints](reference/SYSTEM-ENDPOINTS.md)
|
||||
* [Speaker Endpoint](reference/SPEAKER-ENDPOINT.md)
|
||||
* [WebSocket Events](reference/WEBSOCKET-EVENTS.md)
|
||||
* [Device Pairing Flow](reference/DEVICE-PAIRING-FLOW.md)
|
||||
* [Discovery](reference/DISCOVERY.md)
|
||||
* [Zone Management](reference/ZONE-MANAGEMENT.md)
|
||||
* [Preset Management](reference/PRESET-MANAGEMENT.md)
|
||||
* [Source Selection](reference/SOURCE-SELECTION.md)
|
||||
* [Volume Controls](reference/VOLUME-CONTROLS.md)
|
||||
* [RadioBrowser](reference/radio-browser.md)
|
||||
* [Bass Controls](reference/BASS-CONTROLS.md)
|
||||
* [Key Controls](reference/KEY-CONTROLS.md)
|
||||
* [Feature Mapping](reference/FEATURE-MAPPING.md)
|
||||
|
||||
## Concepts
|
||||
* [Request Recording](REQUEST_RECORDING_CONCEPT.md)
|
||||
* [Spotify Overview](concepts/spotify-overview.md)
|
||||
* [Spotify Priming Strategy](concepts/spotify-priming-strategy.md)
|
||||
* [Spotify OAuth](concepts/spotify-oauth.md)
|
||||
* [Amazon Music OAuth](concepts/amazon-music-oauth.md)
|
||||
* [Encrypted Export](concepts/ENCRYPTED-EXPORT.md)
|
||||
* [Diagnostic Export (Maintainer Setup)](DIAGNOSTIC-EXPORT.md)
|
||||
* [soundtouch-web Roadmap](soundtouch-web-roadmap.md)
|
||||
|
||||
## Analysis & Research
|
||||
* [API Coverage Analysis](analysis/API-COVERAGE.md)
|
||||
* [Supported URLs](analysis/SUPPORTED-URLS.md)
|
||||
* [Upstream URLs](analysis/UPSTREAM-URLS.md)
|
||||
* [Anonymization Summary](analysis/ANONYMIZATION-SUMMARY.md)
|
||||
* [Device Redirect Methods](analysis/DEVICE-REDIRECT-METHODS.md)
|
||||
* [Telnet (Port 17000) Migration Method](analysis/TELNET-MIGRATION-METHOD.md)
|
||||
* [Telnet Command Reference](analysis/TELNET-COMMAND-REFERENCE.md)
|
||||
* [Setup WebSocket Experiment](analysis/SETUP-WEBSOCKET-EXPERIMENT.md)
|
||||
* [Factory Reset Protocol](analysis/FACTORY-RESET-PROTOCOL.md)
|
||||
* [Wiki API Comparison](analysis/WIKI-COMPARISON.md)
|
||||
* [IoT Config Summary](analysis/IOT-CONFIG-SUMMARY.md)
|
||||
* [IoT Configuration Analysis](analysis/IOT-CONFIGURATION-ANALYSIS.md)
|
||||
* [Bose Lab Runbook](analysis/BOSE-LAB-RUNBOOK.md)
|
||||
* [Missing Routes Spotify](analysis/MISSING-ROUTES-SPOTIFY.md)
|
||||
* [Bose App ADB Emulator](analysis/BOSE-APP-ADB-Emulator.md)
|
||||
* [Community Tools](analysis/bose-soundtouch-community-tools.md)
|
||||
|
||||
## Parity Analysis
|
||||
* [Parity Improvements](PARITY-IMPROVEMENTS.md)
|
||||
* [Parity SoundCork](PARITY-SOUNDCORK.md)
|
||||
* [Parity OpenCloudTouch](PARITY-OPENCLOUDTOUCH.md)
|
||||
|
||||
## Appendix (Other Documents)
|
||||
* [External Services Abstraction](EXTERNAL-SERVICES-ABSTRACTION.md)
|
||||
* [API Navigation Reference](API-NAVIGATION-REFERENCE.md)
|
||||
* [Claude Instructions](CLAUDE.md)
|
||||
* [Content Selection Implementation](CONTENT-SELECTION-IMPLEMENTATION.md)
|
||||
* [Device Customization Setup](DEVICE-CUSTOMIZATION-SETUP.md)
|
||||
* [Device Logging](DEVICE-LOGGING.md)
|
||||
* [Feature History](FEATURE_HISTORY.md)
|
||||
* [Host/Port Parsing](HOST-PORT-PARSING.md)
|
||||
* [Manual Network Discovery](MANUAL-NETWORK-DISCOVERY.md)
|
||||
* [Navigation Guide](NAVIGATION-GUIDE.md)
|
||||
* [Official API Verification](OFFICIAL-API-VERIFICATION.md)
|
||||
* [Preset Quickstart](PRESET-QUICKSTART.md)
|
||||
* [Project Patterns](PROJECT-PATTERNS.md)
|
||||
* [Service Availability Implementation](SERVICE-AVAILABILITY-IMPLEMENTATION.md)
|
||||
* [SoundTouch Service Announcement](SOUNDTOUCH-SERVICE-ANNOUNCEMENT.md)
|
||||
* [Undocumented Community Features](UNDOCUMENTED-COMMUNITY-FEATURES.md)
|
||||
* [Unimplemented Endpoints](UNIMPLEMENTED-ENDPOINTS.md)
|
||||
* [Preset Store](preset-store.md)
|
||||
* [SCMUDC Enrichment Implementation](SCMUDC-ENRICHMENT-IMPLEMENTATION.md)
|
||||
* [Device Lifecycle and Power On Enhancement](device-lifecycle-and-power-on-enhancement.md)
|
||||
* [Device Lifecycle Summary](device-lifecycle-summary.md)
|
||||
* [Power On Implementation Guide](power-on-implementation-guide.md)
|
||||
* [SCMUDC Events Analysis](scmudc-events-analysis.md)
|
||||
* [Parity Improvements](PARITY-IMPROVEMENTS.md)
|
||||
* [Parity SoundCork](PARITY-SOUNDCORK.md)
|
||||
* [Stockholm Port Guide](stockholm-port-guide.md)
|
||||
@@ -1,11 +0,0 @@
|
||||
title: Bose SoundTouch Toolkit
|
||||
description: Documentation for controlling and preserving Bose SoundTouch devices
|
||||
remote_theme: pages-themes/minimal@v0.2.0
|
||||
plugins:
|
||||
- jekyll-remote-theme
|
||||
- jekyll-relative-links
|
||||
relative_links:
|
||||
enabled: true
|
||||
collections: true
|
||||
include:
|
||||
- SUMMARY.md
|
||||
@@ -784,4 +784,4 @@ docker compose up # Mock devices + web app
|
||||
- [UPnP Device Architecture](http://upnp.org/specs/arch/UPnP-arch-DeviceArchitecture-v1.0.pdf)
|
||||
- [Go Embed Directive](https://pkg.go.dev/embed)
|
||||
- [Gorilla WebSocket](https://github.com/gorilla/websocket)
|
||||
- [PROJECT-PATTERNS.md](../PROJECT-PATTERNS.md) - Detailed pattern documentation
|
||||
- [PROJECT-PATTERNS.md](../content/docs/appendix/PROJECT-PATTERNS.md) - Detailed pattern documentation
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
/* Font paths use ../../fonts/ (two levels up) so the URL resolves correctly
|
||||
regardless of where the CSS is served from:
|
||||
- dev: /css/custom.css → ../../fonts/ → /fonts/
|
||||
- production: /css/compiled/main.css → ../../fonts/ → /fonts/
|
||||
- GH Pages: /Bose-SoundTouch/css/compiled/main.css
|
||||
→ ../../fonts/ → /Bose-SoundTouch/fonts/ */
|
||||
|
||||
@font-face {
|
||||
font-family: 'Noto Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url('../../fonts/noto-sans-v42-latin-regular.woff2') format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Noto Sans';
|
||||
font-style: italic;
|
||||
font-weight: 400;
|
||||
font-display: swap;
|
||||
src: url('../../fonts/noto-sans-v42-latin-italic.woff2') format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Noto Sans';
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url('../../fonts/noto-sans-v42-latin-700.woff2') format('woff2');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Noto Sans';
|
||||
font-style: italic;
|
||||
font-weight: 700;
|
||||
font-display: swap;
|
||||
src: url('../../fonts/noto-sans-v42-latin-700italic.woff2') format('woff2');
|
||||
}
|
||||
|
||||
:root {
|
||||
--hx-default-font-family: "Noto Sans", ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
}
|
||||
|
||||
.content {
|
||||
font-family: var(--hx-default-font-family);
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
---
|
||||
title: AfterTouch
|
||||
layout: hextra-home
|
||||
---
|
||||
|
||||
{{< hextra/hero-badge >}}
|
||||
<div class="hx-w-2 hx-h-2 hx-rounded-full hx-bg-primary-400"></div>
|
||||
<span>Free, open source</span>
|
||||
{{< icon name="arrow-circle-right" attributes="height=14" >}}
|
||||
{{< /hextra/hero-badge >}}
|
||||
|
||||
<div class="hx-mt-6 hx-mb-6">
|
||||
{{< hextra/hero-headline >}}
|
||||
Keep Your Bose SoundTouch Speakers Alive
|
||||
{{< /hextra/hero-headline >}}
|
||||
</div>
|
||||
|
||||
<div class="hx-mb-12">
|
||||
{{< hextra/hero-subtitle >}}
|
||||
Bose shut down SoundTouch cloud services on May 6, 2026. <br class="sm:hx-block hx-hidden" />AfterTouch replaces the cloud — presets, music browsing, stereo pairing, all restored.
|
||||
{{< /hextra/hero-subtitle >}}
|
||||
</div>
|
||||
|
||||
<div class="hx-mb-6">
|
||||
{{< hextra/hero-button text="Get Started" link="docs/guides/MIGRATION-GUIDE" >}}
|
||||
{{< hextra/hero-button text="Survival Guide" link="docs/guides/SURVIVAL-GUIDE" style="outline" >}}
|
||||
</div>
|
||||
|
||||
<div class="hx-mt-6">
|
||||
{{< hextra/feature-grid >}}
|
||||
{{< hextra/feature-card
|
||||
title="Presets Restored"
|
||||
subtitle="Preset buttons, long-press assignment, and recently-played sync — fully working."
|
||||
icon="star"
|
||||
>}}
|
||||
{{< hextra/feature-card
|
||||
title="Music Browsing"
|
||||
subtitle="TuneIn, Internet Radio, RadioBrowser, and Spotify via soundtouch-web and soundtouch-cli."
|
||||
icon="speakerphone"
|
||||
>}}
|
||||
{{< hextra/feature-card
|
||||
title="Stereo Pairing"
|
||||
subtitle="SoundTouch 10 stereo pairing via soundtouch-cli, no Bose cloud required."
|
||||
icon="adjustments"
|
||||
>}}
|
||||
{{< hextra/feature-card
|
||||
title="Three Deployment Options"
|
||||
subtitle="Run on a Raspberry Pi, a VPS, or directly on the speaker itself."
|
||||
icon="server"
|
||||
link="docs/guides/DEPLOYMENT-OVERVIEW"
|
||||
>}}
|
||||
{{< hextra/feature-card
|
||||
title="CLI Control"
|
||||
subtitle="soundtouch-cli for scripting, home automation, and direct device control."
|
||||
icon="terminal"
|
||||
link="docs/guides/CLI-REFERENCE"
|
||||
>}}
|
||||
{{< hextra/feature-card
|
||||
title="Open Source"
|
||||
subtitle="MIT licensed. Not affiliated with Bose Corporation."
|
||||
icon="shield-check"
|
||||
link="https://github.com/gesellix/Bose-SoundTouch"
|
||||
>}}
|
||||
{{< /hextra/feature-grid >}}
|
||||
</div>
|
||||
@@ -0,0 +1,120 @@
|
||||
---
|
||||
title: "Welcome to AfterTouch: Your SoundTouch Speakers, Still Alive"
|
||||
date: 2026-05-24
|
||||
description: "Bose shut down SoundTouch cloud services in May 2026. AfterTouch replaces everything your speakers relied on — migration, radio, Spotify, presets, and more."
|
||||
tags:
|
||||
- migration
|
||||
- web
|
||||
- spotify
|
||||
- cli
|
||||
sidebar:
|
||||
exclude: true
|
||||
---
|
||||
|
||||
On May 6, 2026, Bose shut down the SoundTouch cloud services that millions of speakers
|
||||
depended on for account sync, presets, internet radio, and streaming. Speakers kept
|
||||
working locally, but remote features stopped and first-time setup became impossible.
|
||||
|
||||
AfterTouch was built to change that. It is a self-hosted replacement for the Bose
|
||||
cloud infrastructure — a drop-in local service that your speakers talk to instead of
|
||||
`streaming.bose.com`. This post covers what works today and how to get started.
|
||||
|
||||
## What works right now
|
||||
|
||||
### Migration and first-time setup
|
||||
|
||||
If your speaker was registered with Bose before the shutdown, AfterTouch can **migrate
|
||||
your existing account and presets** in a single step — no reconfiguration on the
|
||||
speaker side. If you are setting up a factory-reset or brand-new speaker, AfterTouch
|
||||
handles that path too, guiding you through Wi-Fi pairing and account creation locally.
|
||||
See the [Migration Guide](../docs/guides/MIGRATION-GUIDE.md) for step-by-step instructions.
|
||||
|
||||
### Internet radio — TuneIn and RadioBrowser
|
||||
|
||||
Both **TuneIn** and **RadioBrowser** are fully supported for browsing and playback.
|
||||
Navigate categories and search for stations exactly as you did with the original Bose
|
||||
app. TuneIn delivers the same station catalogue; RadioBrowser provides an open,
|
||||
community-maintained alternative.
|
||||
|
||||
### Spotify
|
||||
|
||||
**Spotify** works via both OAuth (account linking) and Spotify Connect (the ZeroConf
|
||||
"connect to device" flow from the Spotify app). Once linked, playback and device
|
||||
selection behave the same as before.
|
||||
|
||||
### Presets
|
||||
|
||||
Your six preset buttons work. AfterTouch stores preset bindings locally and serves them
|
||||
back to the speaker on request. You can also **save new presets** — via the API,
|
||||
via `soundtouch-cli`, or through the soundtouch-web UI.
|
||||
|
||||
### ST-10 stereo pairing
|
||||
|
||||
**SoundTouch 10 stereo pairs** (and other ST pairing configurations) are supported
|
||||
end-to-end: creation, management, and playback routing all go through AfterTouch.
|
||||
|
||||
### soundtouch-web — browser UI
|
||||
|
||||
**soundtouch-web** is an early-stage but functional browser UI bundled with AfterTouch.
|
||||
It gives you:
|
||||
|
||||
- TuneIn and RadioBrowser browsing and playback
|
||||
- Speaker management and device discovery
|
||||
- Recent tracks panel
|
||||
- Multi-room zone management
|
||||
|
||||
It runs as part of the AfterTouch service — no separate install needed.
|
||||
|
||||

|
||||
|
||||
### Automation with soundtouch-cli
|
||||
|
||||
The **`soundtouch-cli`** command-line tool covers every speaker control: play, pause,
|
||||
volume, source selection, preset recall, group management, migration, and more.
|
||||
It is well-suited for home-automation scripts, cron jobs, and shell one-liners.
|
||||
|
||||
## Three ways to install
|
||||
|
||||
AfterTouch runs on any machine your speakers can reach:
|
||||
|
||||
1. **On the speaker itself** — install directly on supported SoundTouch hardware via
|
||||
the on-device installer. The speaker hosts its own replacement cloud, with no
|
||||
additional hardware required.
|
||||
|
||||
2. **On a local network host** — run AfterTouch on any machine on your LAN. A
|
||||
**Raspberry Pi Zero 2W** handles the load without breaking a sweat, making this
|
||||
path remarkably low-cost and low-power.
|
||||
|
||||
3. **On a cloud or VPS host** — deploy to a remote server for access outside your
|
||||
home network. AfterTouch handles TLS certificate generation and DNS configuration
|
||||
for this scenario.
|
||||
|
||||
All three paths are documented in the [Deployment Overview](../docs/guides/DEPLOYMENT-OVERVIEW.md).
|
||||
|
||||
## Current release
|
||||
|
||||
**v0.93.1** — released May 24, 2026
|
||||
|
||||
## Community
|
||||
|
||||
AfterTouch would not be where it is without the people who opened issues, tested
|
||||
pre-release builds, reported edge cases, and contributed code. A significant share of
|
||||
the fixes and features shipped in the lead-up to the cloud shutdown were driven by
|
||||
real-world feedback from the community — from migration quirks to stereo-pair
|
||||
specifics to Spotify Connect timing issues. Thank you to everyone who helped.
|
||||
|
||||
If you run into something or have an idea, the
|
||||
[GitHub issue tracker](https://github.com/gesellix/Bose-SoundTouch/issues) and
|
||||
[Discussions](https://github.com/gesellix/Bose-SoundTouch/discussions) are the
|
||||
right places to start.
|
||||
|
||||
## What's next
|
||||
|
||||
The soundtouch-web UI will gain richer preset management — browsing, editing, and
|
||||
reordering presets directly from the browser. Longer term, merging
|
||||
`soundtouch-service` and `soundtouch-web` into a single binary is on the table,
|
||||
which would simplify deployment to a single process with no extra flags.
|
||||
|
||||
This blog will be updated monthly — or whenever something significant ships.
|
||||
Subscribe to the [GitHub releases](https://github.com/gesellix/Bose-SoundTouch/releases)
|
||||
for individual version notes.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
title: News & Updates
|
||||
---
|
||||
|
||||
Project updates, release notes, and development notes for AfterTouch — the local replacement for the Bose SoundTouch cloud.
|
||||
@@ -1,3 +1,9 @@
|
||||
---
|
||||
title: Introduction
|
||||
sidebar:
|
||||
open: true
|
||||
---
|
||||
|
||||
# Bose SoundTouch Toolkit Documentation
|
||||
|
||||
Welcome to the documentation for the Bose SoundTouch Toolkit. This comprehensive toolkit helps you keep your Bose SoundTouch speakers functional even after the Bose Cloud shutdown in May 2026, with enhanced local management and monitoring capabilities.
|
||||
@@ -10,7 +16,7 @@ Welcome to the documentation for the Bose SoundTouch Toolkit. This comprehensive
|
||||
|
||||
### For Existing Users
|
||||
- **[Cloud Shutdown Survival Guide](guides/SURVIVAL-GUIDE.md)** - Prepare for the May 2026 shutdown
|
||||
- **[Backup Tool](../cmd/soundtouch-backup/README.md)** - Back up your cloud account and speaker data before shutdown
|
||||
- **[Backup Tool](https://github.com/gesellix/Bose-SoundTouch/blob/main/cmd/soundtouch-backup/README.md)** - Back up your cloud account and speaker data before shutdown
|
||||
- **[SoundTouch Service Guide](guides/SOUNDTOUCH-SERVICE.md)** - Advanced service configuration
|
||||
|
||||
## 📋 Essential Documentation
|
||||
@@ -41,7 +47,7 @@ The documentation is organized into three main categories:
|
||||
### Advanced Features
|
||||
- [MAC Address Mapping](guides/MAC-ADDRESS-MAPPING.md) - Device identification
|
||||
- [CLI Reference](guides/CLI-REFERENCE.md) - Command-line tools
|
||||
- [Backup Tool](../cmd/soundtouch-backup/README.md) - Cloud account and speaker data backup
|
||||
- [Backup Tool](https://github.com/gesellix/Bose-SoundTouch/blob/main/cmd/soundtouch-backup/README.md) - Cloud account and speaker data backup
|
||||
- [IoT Implementation Guide](guides/IOT-IMPLEMENTATION-GUIDE.md) - IoT integrations
|
||||
- [MQTT Integration Design](guides/MQTT-INTEGRATION-DESIGN.md) - MQTT setup
|
||||
|
||||
@@ -61,20 +67,18 @@ The documentation is organized into three main categories:
|
||||
- [IoT Config Summary](analysis/IOT-CONFIG-SUMMARY.md) - Configuration summaries
|
||||
|
||||
### Device Lifecycle & Network Independence
|
||||
- **[Device Lifecycle and /power_on Enhancement](device-lifecycle-and-power-on-enhancement.md)** - Complete analysis of device registration and network independence improvements
|
||||
- [/power_on Implementation Guide](power-on-implementation-guide.md) - Technical implementation details for enhanced device management
|
||||
- **[Device Lifecycle and /power_on Enhancement](appendix/device-lifecycle-and-power-on-enhancement.md)** - Complete analysis of device registration and network independence improvements
|
||||
- [/power_on Implementation Guide](appendix/power-on-implementation-guide.md) - Technical implementation details for enhanced device management
|
||||
|
||||
## 🏗 Concept Documentation
|
||||
|
||||
Current concept docs are listed under the **Concepts** section of [SUMMARY.md](SUMMARY.md#concepts). Highlights:
|
||||
|
||||
- [Spotify Overview](concepts/spotify-overview.md) — mental model, Spotify Connect vs OAuth-intercept, DNS rewrite gotcha
|
||||
- [Spotify OAuth](concepts/spotify-oauth.md) — flows and management endpoints
|
||||
- [Amazon Music OAuth](concepts/amazon-music-oauth.md) — companion to Spotify OAuth; same protocol shape, different scopes
|
||||
- [Encrypted Export](concepts/ENCRYPTED-EXPORT.md) — `.age`-encrypted diagnostic bundles
|
||||
- [Request Recording](REQUEST_RECORDING_CONCEPT.md) — how the proxy captures live device traffic for parity testing
|
||||
- [Request Recording](appendix/REQUEST_RECORDING_CONCEPT.md) — how the proxy captures live device traffic for parity testing
|
||||
|
||||
Older planning artefacts ("Enhanced State Management System", "Upstream Service Simulation") live under [docs/archive/](archive/) — kept for the record, no longer current.
|
||||
Older planning artefacts ("Enhanced State Management System", "Upstream Service Simulation") live under [docs/archive/](../../archive/) — kept for the record, no longer current.
|
||||
|
||||
## 💡 Quick Reference
|
||||
|
||||
@@ -90,4 +94,4 @@ Older planning artefacts ("Enhanced State Management System", "Upstream Service
|
||||
- **Documentation**: Check troubleshooting guides first
|
||||
- **Community**: Share experiences and help others
|
||||
|
||||
For a complete list of all documents, see the [Summary](SUMMARY.md).
|
||||
For a complete list of all documents, browse the sections in the sidebar.
|
||||
+3
-2
@@ -1,5 +1,6 @@
|
||||
# Placeholder values for examples
|
||||
|
||||
---
|
||||
title: "Placeholder values for examples"
|
||||
---
|
||||
This repo is public. Documentation, READMEs, example configs, and test
|
||||
fixtures must never carry real LAN IPs, real device MACs, real Bose
|
||||
account IDs, or personal device names from any maintainer or
|
||||
@@ -1,5 +1,6 @@
|
||||
# Bose SoundTouch API Coverage Analysis
|
||||
|
||||
---
|
||||
title: "Bose SoundTouch API Coverage Analysis"
|
||||
---
|
||||
**Last Updated:** February 2026
|
||||
**API Version:** Official Bose SoundTouch Web API v1.0
|
||||
**Implementation Status:** 100% Official Coverage + Extended Features
|
||||
+3
-2
@@ -1,5 +1,6 @@
|
||||
# Bose SoundTouch Traffic Interception Runbook
|
||||
|
||||
---
|
||||
title: "Bose SoundTouch Traffic Interception Runbook"
|
||||
---
|
||||
Intercept HTTPS/WebSocket traffic from the Bose SoundTouch Android app using an Android emulator, mitmproxy, and Frida. Tested on Apple Silicon (ARM64) Mac.
|
||||
|
||||
## Automated Setup
|
||||
@@ -1,5 +1,6 @@
|
||||
# Bose SoundTouch – Traffic Analysis Runbook
|
||||
|
||||
---
|
||||
title: "Bose SoundTouch – Traffic Analysis Runbook"
|
||||
---
|
||||
> **Goal:** Set up a Raspberry Pi as a transparent access point to fully observe the traffic of the Bose SoundTouch app – specifically the pairing flow with the Bose Cloud. This serves as a basis for later reverse engineering / simulation of the cloud endpoints.
|
||||
|
||||
---
|
||||
+3
-2
@@ -1,5 +1,6 @@
|
||||
# Device Redirect Methods & Custom Service Setup
|
||||
|
||||
---
|
||||
title: "Device Redirect Methods & Custom Service Setup"
|
||||
---
|
||||
To enable offline operation or use custom services like **SoundCork** or **ÜberBöse API**, SoundTouch devices must be redirected from Bose's official cloud endpoints to a local or custom server. This document outlines the three known methods to achieve this, gathered from community reverse-engineering efforts in the **SoundCork** and **ÜberBöse API** projects.
|
||||
|
||||
> A fourth, **SSH-free** path — driving the device's diagnostic shell on TCP port 17000 — is being added as a peer to the XML and DNS methods. See **[TELNET-MIGRATION-METHOD.md](TELNET-MIGRATION-METHOD.md)** for the use cases, community findings, and feasibility analysis. The `/etc/hosts` method documented below is now deprecated and will not be exposed in the web UI.
|
||||
+3
-2
@@ -1,5 +1,6 @@
|
||||
# What a SoundTouch speaker does during factory reset
|
||||
|
||||
---
|
||||
title: "What a SoundTouch speaker does during factory reset"
|
||||
---
|
||||
Observed live on ST10 firmware `27.0.6.46330.5043500` (build `epdbuild.trunk.hepdswbld04.2022-08-04`) on 2026-05-12, by running `soundtouch-cli setup factory-reset` and tailing the speaker's `logread` over SSH. The trace is preserved at `_/logs/factory-reset.txt` for reference.
|
||||
|
||||
## Sequence
|
||||
+3
-2
@@ -1,5 +1,6 @@
|
||||
# IoT Configuration Quick Reference
|
||||
|
||||
---
|
||||
title: "IoT Configuration Quick Reference"
|
||||
---
|
||||
## Key Files and Locations
|
||||
|
||||
| File/Location | Purpose | Notes |
|
||||
+3
-2
@@ -1,5 +1,6 @@
|
||||
# IoT Configuration Analysis
|
||||
|
||||
---
|
||||
title: "IoT Configuration Analysis"
|
||||
---
|
||||
## Overview
|
||||
|
||||
This document provides a detailed analysis of the AWS IoT configuration system used by Bose SoundTouch devices, based on firmware backup analysis from ST10 and ST20 models.
|
||||
+3
-2
@@ -1,5 +1,6 @@
|
||||
# Spotify Account Addition Implementation Status
|
||||
|
||||
---
|
||||
title: "Spotify Account Addition Implementation Status"
|
||||
---
|
||||
To fully replace Bose cloud services for the Spotify account addition flow in the "Stockholm" SoundTouch application, the following routes have been implemented in the `soundtouch-service`:
|
||||
|
||||
## 1. OAuth Token Exchange (Bose Cloud)
|
||||
+3
-2
@@ -1,5 +1,6 @@
|
||||
# Experiment: Does bare `setMargeAccount` work outside the SETUP bracket?
|
||||
|
||||
---
|
||||
title: "Experiment: Does bare `setMargeAccount` work outside the SETUP bracket?"
|
||||
---
|
||||
## Why we are doing this
|
||||
|
||||
Our captured pairing flow (`docs/reference/DEVICE-PAIRING-FLOW.md`) shows the official Bose app always sends `setMargeAccount` *inside* a `SETUP_START` → `SETUP_ENTER` → `SETUP_LEAVE` state-machine bracket over WebSocket. The question this experiment answers:
|
||||
@@ -1,5 +1,6 @@
|
||||
# SoundTouch supportedURLs Endpoint Analysis
|
||||
|
||||
---
|
||||
title: "SoundTouch supportedURLs Endpoint Analysis"
|
||||
---
|
||||
This document provides a comprehensive analysis of the `/supportedURLs` endpoint response from real Bose SoundTouch devices and compares it with our current implementation.
|
||||
|
||||
## Discovery Summary
|
||||
+3
-2
@@ -1,5 +1,6 @@
|
||||
# Bose SoundTouch Telnet (Port 17000) Command Reference
|
||||
|
||||
---
|
||||
title: "Bose SoundTouch Telnet (Port 17000) Command Reference"
|
||||
---
|
||||
A consolidated reference for the diagnostic shell that listens on TCP port
|
||||
17000 across the SoundTouch line. Compiled from multiple community sources
|
||||
to give a single map of what's been observed in the wild — useful both for
|
||||
+4
-3
@@ -1,5 +1,6 @@
|
||||
# Telnet (Port 17000) Migration Method — Analysis
|
||||
|
||||
---
|
||||
title: "Telnet (Port 17000) Migration Method — Analysis"
|
||||
---
|
||||
This document captures the use cases, community findings, and feasibility analysis
|
||||
for adding a **Telnet/port 17000** migration path to `soundtouch-service` as a
|
||||
peer of the existing XML and DNS-based methods. The `/etc/hosts` method stays
|
||||
@@ -8,7 +9,7 @@ deprecated and is intentionally kept off the visible UI options.
|
||||
> **Sources** — community discussion synthesised from
|
||||
> [gesellix/Bose-SoundTouch#221](https://github.com/gesellix/Bose-SoundTouch/issues/221),
|
||||
> [gesellix/Bose-SoundTouch#236](https://github.com/gesellix/Bose-SoundTouch/issues/236),
|
||||
> [scheilch/opencloudtouch#167](https://github.com/scheilch/opencloudtouch/issues/167),
|
||||
> [scheilch/opencloudtouch#167](https://github.com/opencloudtouch/opencloudtouch/issues/167),
|
||||
> [deborahgu/soundcork#228](https://github.com/deborahgu/soundcork/issues/228),
|
||||
> [deborahgu/soundcork#141](https://github.com/deborahgu/soundcork/issues/141),
|
||||
> the post-EOS walkthrough PDF in `docs/`,
|
||||
@@ -1,5 +1,6 @@
|
||||
# Upstream URLs & Domains Analysis
|
||||
|
||||
---
|
||||
title: "Upstream URLs & Domains Analysis"
|
||||
---
|
||||
This document provides a comprehensive overview of the upstream Bose cloud services and domains that SoundTouch devices communicate with. These details were gathered from firmware analysis of ST10/ST20 devices, binary string extraction, and community research from the **SoundCork** project (Issue #128).
|
||||
|
||||
## Core Service Domains
|
||||
@@ -1,5 +1,6 @@
|
||||
# SoundTouch API Comparison: Community Wiki vs Current Implementation
|
||||
|
||||
---
|
||||
title: "SoundTouch API Comparison: Community Wiki vs Current Implementation"
|
||||
---
|
||||
**Date:** January 2026
|
||||
**Source:** [SoundTouch Plus Wiki](https://github.com/thlucas1/homeassistantcomponent_soundtouchplus/wiki/SoundTouch-WebServices-API)
|
||||
**Our Implementation:** Bose-SoundTouch Go Library v1.0
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Analysis & Research"
|
||||
weight: 4
|
||||
---
|
||||
+4
-3
@@ -1,5 +1,6 @@
|
||||
# Bose SoundTouch — Community Tools for Post-EOL Preservation
|
||||
|
||||
---
|
||||
title: "Bose SoundTouch — Community Tools for Post-EOL Preservation"
|
||||
---
|
||||
> **Context:** Bose announced the shutdown of SoundTouch cloud services, extended to **May 6, 2026**. On that date the official SoundTouch app will update to a local-only version. Bose has released the [SoundTouch Web API documentation](https://assets.bosecreative.com/m/496577402d128874/original/SoundTouch-Web-API.pdf) as open-source to enable community-driven development. This document surveys the active community projects, their feature coverage, and open development opportunities.
|
||||
|
||||
---
|
||||
@@ -233,7 +234,7 @@ A design document exists (`docs/guides/MQTT-INTEGRATION-DESIGN.md`) but no code
|
||||
|
||||
## soundcork ↔ AfterTouch
|
||||
|
||||
soundcork and AfterTouch share the most functional overlap of any two projects in the ecosystem. For the implementation-level parity analysis and remaining tasks see [docs/PARITY-SOUNDCORK.md](../PARITY-SOUNDCORK.md).
|
||||
soundcork and AfterTouch share the most functional overlap of any two projects in the ecosystem. For the implementation-level parity analysis and remaining tasks see [docs/PARITY-SOUNDCORK.md](../appendix/PARITY-SOUNDCORK.md).
|
||||
|
||||
### Architectural differences (not gaps)
|
||||
|
||||
+5
-2
@@ -1,5 +1,8 @@
|
||||
# Navigation API Reference
|
||||
|
||||
---
|
||||
title: "Navigation API Reference"
|
||||
sidebar:
|
||||
exclude: true
|
||||
---
|
||||
## Overview
|
||||
|
||||
This document provides a complete API reference for the Bose SoundTouch navigation and station management functionality. For usage examples and workflows, see [NAVIGATION-GUIDE.md](NAVIGATION-GUIDE.md).
|
||||
@@ -1,13 +1,15 @@
|
||||
# CLAUDE.md - Development Guidelines for Bose SoundTouch Project
|
||||
|
||||
---
|
||||
title: "CLAUDE.md - Development Guidelines for Bose SoundTouch Project"
|
||||
sidebar:
|
||||
exclude: true
|
||||
---
|
||||
## Documentation Overview
|
||||
|
||||
This document contains important development guidelines for working on the Bose SoundTouch project. Please also read the following documentation:
|
||||
|
||||
- **[PLAN.md](archive/PLAN.md)** - Project planning and roadmap
|
||||
- **[PLAN.md](../../../archive/PLAN.md)** - Project planning and roadmap
|
||||
- **[PROJECT-PATTERNS.md](PROJECT-PATTERNS.md)** - Project structure and design patterns
|
||||
- **[API-ENDPOINTS.md](reference/API-ENDPOINTS.md)** - API endpoints overview
|
||||
- **[SoundTouch Web API.pdf](2025.12.18%20SoundTouch%20Web%20API.pdf)** - Official API documentation
|
||||
- **[API-ENDPOINTS.md](../reference/API-ENDPOINTS.md)** - API endpoints overview
|
||||
|
||||
## Development Guidelines
|
||||
|
||||
+8
-5
@@ -1,5 +1,8 @@
|
||||
# Content Selection Implementation Summary
|
||||
|
||||
---
|
||||
title: "Content Selection Implementation Summary"
|
||||
sidebar:
|
||||
exclude: true
|
||||
---
|
||||
This document summarizes the implementation of advanced content selection features for the Bose SoundTouch Go client, including full support for the LOCAL_INTERNET_RADIO streamUrl format and LOCAL_MUSIC/STORED_MUSIC content selection.
|
||||
|
||||
## ✅ Implementation Status: COMPLETE
|
||||
@@ -212,9 +215,9 @@ soundtouch-cli --host 192.0.2.100 source internet-radio \
|
||||
- [SoundTouch WebServices API Wiki](https://github.com/thlucas1/homeassistantcomponent_soundtouchplus/wiki/SoundTouch-WebServices-API)
|
||||
- [LOCAL_INTERNET_RADIO - streamUrl format](https://github.com/thlucas1/homeassistantcomponent_soundtouchplus/wiki/SoundTouch-WebServices-API#select-local_internet_radio---streamurl-format)
|
||||
- [LOCAL_MUSIC](https://github.com/thlucas1/homeassistantcomponent_soundtouchplus/wiki/SoundTouch-WebServices-API#select-local_music)
|
||||
- [Content Selection Example](../examples/content-selection/README.md)
|
||||
- [CLI Reference](guides/CLI-REFERENCE.md)
|
||||
- [Content Selection Example (Direct)](../examples/content-selection/)
|
||||
- [Content Selection Example](https://github.com/gesellix/Bose-SoundTouch/tree/main/examples/content-selection/README.md)
|
||||
- [CLI Reference](../guides/CLI-REFERENCE.md)
|
||||
- [Content Selection Example (Direct)](https://github.com/gesellix/Bose-SoundTouch/tree/main/examples/content-selection)
|
||||
|
||||
## ✅ Verification
|
||||
|
||||
+5
-2
@@ -1,5 +1,8 @@
|
||||
# Device Customization Setup Guide
|
||||
|
||||
---
|
||||
title: "Device Customization Setup Guide"
|
||||
sidebar:
|
||||
exclude: true
|
||||
---
|
||||
This guide documents the manual steps required to configure your Bose SoundTouch device for customization using the SoundCork approach.
|
||||
|
||||
Based on: https://github.com/deborahgu/soundcork
|
||||
@@ -1,5 +1,8 @@
|
||||
# Device Logging & Troubleshooting
|
||||
|
||||
---
|
||||
title: "Device Logging & Troubleshooting"
|
||||
sidebar:
|
||||
exclude: true
|
||||
---
|
||||
Accessing logs from SoundTouch devices is critical for debugging custom service integrations and understanding internal device behavior. This document outlines the methods for collecting logs, as discovered by the **SoundCork** and **ÜberBöse API** communities.
|
||||
|
||||
## Log Types
|
||||
@@ -80,7 +83,7 @@ If you have a managed switch or a router capable of port mirroring, you can use
|
||||
### "IsItBose" Validation Failures
|
||||
If the device fails to connect to your custom service despite correct configuration, it may be failing the internal `IsItBose` regex check.
|
||||
- **Evidence**: Look for SSL handshake failures or "Unauthorized" errors in your service logs.
|
||||
- **Solution**: See the [Binary Patching section in DEVICE-REDIRECT-METHODS.md](analysis/DEVICE-REDIRECT-METHODS.md#method-3-binary-patching).
|
||||
- **Solution**: See the [Binary Patching section in DEVICE-REDIRECT-METHODS.md](../analysis/DEVICE-REDIRECT-METHODS.md#method-3-binary-patching).
|
||||
|
||||
### Disappearing Sources (TuneIn/Local Radio)
|
||||
If `TUNEIN` or `LOCAL_INTERNET_RADIO` sources disappear after a reboot in an offline environment.
|
||||
@@ -1,5 +1,8 @@
|
||||
# Bose SoundTouch Device Setup Flow
|
||||
|
||||
---
|
||||
title: "Bose SoundTouch Device Setup Flow"
|
||||
sidebar:
|
||||
exclude: true
|
||||
---
|
||||
This document details the multi-step process required to fully set up a Bose SoundTouch device, as derived from the Stockholm firmware (`setup/js/`) analysis.
|
||||
|
||||
A complete setup flow involves a sequence of local (WebSocket) and cloud (HTTP) actions that move the device from a factory-reset state to a fully registered, functional system.
|
||||
@@ -1,5 +1,8 @@
|
||||
# Encrypted Diagnostic Export
|
||||
|
||||
---
|
||||
title: "Encrypted Diagnostic Export"
|
||||
sidebar:
|
||||
exclude: true
|
||||
---
|
||||
AfterTouch can produce an encrypted diagnostic report that users can download and
|
||||
send to the project maintainer without exposing sensitive data to third parties.
|
||||
The report is encrypted with an SSH public key using
|
||||
+5
-2
@@ -1,5 +1,8 @@
|
||||
# Technical Proposal: External Service Provider Abstraction
|
||||
|
||||
---
|
||||
title: "Technical Proposal: External Service Provider Abstraction"
|
||||
sidebar:
|
||||
exclude: true
|
||||
---
|
||||
This document outlines a strategy to refactor the SoundTouch Service's content handling into a modular provider-based system.
|
||||
|
||||
## 1. Problem Statement
|
||||
@@ -1,5 +1,8 @@
|
||||
# Feature Development History
|
||||
|
||||
---
|
||||
title: "Feature Development History"
|
||||
sidebar:
|
||||
exclude: true
|
||||
---
|
||||
This document tracks the detailed evolution of features and capabilities in the Bose SoundTouch API client library.
|
||||
|
||||
## Development Timeline
|
||||
@@ -1,5 +1,8 @@
|
||||
# Host:Port Parsing Feature
|
||||
|
||||
---
|
||||
title: "Host:Port Parsing Feature"
|
||||
sidebar:
|
||||
exclude: true
|
||||
---
|
||||
This document describes the automatic host:port parsing functionality added to the SoundTouch CLI, which allows users to specify both host and port in a single `-host` flag.
|
||||
|
||||
## Overview
|
||||
+5
-2
@@ -1,5 +1,8 @@
|
||||
# Manual Network Discovery on macOS
|
||||
|
||||
---
|
||||
title: "Manual Network Discovery on macOS"
|
||||
sidebar:
|
||||
exclude: true
|
||||
---
|
||||
This document provides comprehensive guidance for manually discovering network services and devices using built-in macOS tools and command-line utilities. This is particularly useful for troubleshooting network discovery issues or understanding what services are available on your local network.
|
||||
|
||||
## Overview
|
||||
@@ -1,5 +1,8 @@
|
||||
# Navigation and Station Management Guide
|
||||
|
||||
---
|
||||
title: "Navigation and Station Management Guide"
|
||||
sidebar:
|
||||
exclude: true
|
||||
---
|
||||
## Overview
|
||||
|
||||
The Bose SoundTouch Go client provides comprehensive navigation and station management functionality that allows you to:
|
||||
@@ -895,4 +898,4 @@ For additional help:
|
||||
|
||||
---
|
||||
|
||||
*This guide covers the complete navigation and station management functionality. For preset management, see [PRESET-MANAGEMENT.md](reference/PRESET-MANAGEMENT.md).*
|
||||
*This guide covers the complete navigation and station management functionality. For preset management, see [PRESET-MANAGEMENT.md](../reference/PRESET-MANAGEMENT.md).*
|
||||
+5
-2
@@ -1,5 +1,8 @@
|
||||
# Official SoundTouch Web API Verification
|
||||
|
||||
---
|
||||
title: "Official SoundTouch Web API Verification"
|
||||
sidebar:
|
||||
exclude: true
|
||||
---
|
||||
**Source**: Official Bose SoundTouch Web API v1.0 Documentation (January 7, 2026)
|
||||
**Verification Date**: January 9, 2026
|
||||
**Project Status**: Complete API coverage verification
|
||||
@@ -1,3 +1,9 @@
|
||||
---
|
||||
title: "Parity Improvements"
|
||||
sidebar:
|
||||
exclude: true
|
||||
---
|
||||
|
||||
### Overview of Recent Improvements and Next Steps
|
||||
|
||||
This document summarizes the improvements made to the **Marge service** to improve parity with the upstream Bose SoundTouch service, along with open issues and proposed next steps.
|
||||
@@ -1,5 +1,8 @@
|
||||
# Parity Analysis: Bose-SoundTouch (Go) vs. OpenCloudTouch (Python)
|
||||
|
||||
---
|
||||
title: "Parity Analysis: Bose-SoundTouch (Go) vs. OpenCloudTouch (Python)"
|
||||
sidebar:
|
||||
exclude: true
|
||||
---
|
||||
This document provides a comparative analysis of the current Go implementation and the `scheilch/opencloudtouch` project, identifying functional gaps and potential improvements.
|
||||
|
||||
## 1. Core Architecture and Language
|
||||
@@ -1,5 +1,8 @@
|
||||
# Parity Analysis: Bose-SoundTouch (Go) vs. SoundCork (Python)
|
||||
|
||||
---
|
||||
title: "Parity Analysis: Bose-SoundTouch (Go) vs. SoundCork (Python)"
|
||||
sidebar:
|
||||
exclude: true
|
||||
---
|
||||
This document provides a comparative analysis of the current Go implementation and the `deborahgu/soundcork` project, identifying functional gaps and potential improvements.
|
||||
|
||||
## 1. Core Architecture and Language
|
||||
@@ -54,4 +57,4 @@ Group support and ZeroConf Spotify priming are now feature-complete in AfterTouc
|
||||
- **BMX service extensibility**: the `bmx_services.json` registry makes it trivial to add or mock new streaming providers without code changes (step C above).
|
||||
- **Group pairing logic**: master/slave relationship management for SoundTouch 10 stereo pairs goes beyond the CRUD AfterTouch implements.
|
||||
|
||||
For the broader ecosystem context (feature matrix across all community projects, AfterTouch open tasks, and cross-project observations) see [docs/analysis/bose-soundtouch-community-tools.md](analysis/bose-soundtouch-community-tools.md).
|
||||
For the broader ecosystem context (feature matrix across all community projects, AfterTouch open tasks, and cross-project observations) see [docs/analysis/bose-soundtouch-community-tools.md](../analysis/bose-soundtouch-community-tools.md).
|
||||
@@ -1,10 +1,33 @@
|
||||
# Preset Management Quick Start Guide
|
||||
|
||||
---
|
||||
title: "Preset Management Quick Start Guide"
|
||||
sidebar:
|
||||
exclude: true
|
||||
---
|
||||
**Save your favorite music, radio stations, and playlists as 1-6 presets for instant access.**
|
||||
|
||||
## Overview
|
||||
|
||||
SoundTouch devices support 6 preset slots that can store your favorite content for instant access. This guide shows you how to manage presets using both the CLI and Go library.
|
||||
SoundTouch devices support 6 preset slots that can store your favorite content for instant access. This guide shows you how to manage presets using the soundtouch-web UI, the CLI, or the Go library.
|
||||
|
||||
## Via soundtouch-web (browser UI)
|
||||
|
||||
**soundtouch-web** (default port **8080**) is the easiest way to manage presets without the command line. Two save paths are available whenever content is playing:
|
||||
|
||||
### ★ Star button — save from Now Playing
|
||||
|
||||
1. Navigate to your speaker's detail page.
|
||||
2. Play any station or track (via Radio Browser, TuneIn, or the speaker's own sources).
|
||||
3. A semi-transparent **★** appears in the top-right corner of the **Now Playing** card.
|
||||
4. Click the star — a slot picker **1 · 2 · 3 · 4 · 5 · 6** opens.
|
||||
5. Click the target slot number. The star turns gold once the current content is saved to at least one slot.
|
||||
|
||||
### + button — save directly to a preset tile
|
||||
|
||||
While something is playing, hover over any of the six **Preset** tiles. A small **+** button appears in the tile's corner; clicking it saves the current content to that slot immediately (no picker needed).
|
||||
|
||||
Use the **+** when you already know which slot you want; use the **★** when you want to pick the slot after you've decided to save.
|
||||
|
||||
---
|
||||
|
||||
## Quick CLI Usage
|
||||
|
||||
@@ -332,11 +355,11 @@ soundtouch-cli --host 192.0.2.100 info
|
||||
|
||||
## Next Steps
|
||||
|
||||
- 📖 [Complete CLI Reference](guides/CLI-REFERENCE.md)
|
||||
- 🔧 [Full Implementation Guide](reference/PRESET-MANAGEMENT.md)
|
||||
- 📡 [WebSocket Events Documentation](reference/WEBSOCKET-EVENTS.md)
|
||||
- 💻 [Preset Management Example](../examples/preset-management/)
|
||||
- 📚 [API Endpoints Overview](reference/API-ENDPOINTS.md)
|
||||
- 📖 [Complete CLI Reference](../guides/CLI-REFERENCE.md)
|
||||
- 🔧 [Full Implementation Guide](../reference/PRESET-MANAGEMENT.md)
|
||||
- 📡 [WebSocket Events Documentation](../reference/WEBSOCKET-EVENTS.md)
|
||||
- 💻 [Preset Management Example](https://github.com/gesellix/Bose-SoundTouch/tree/main/examples/preset-management)
|
||||
- 📚 [API Endpoints Overview](../reference/API-ENDPOINTS.md)
|
||||
|
||||
## Need Help?
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
# Project Structure Patterns: Bose SoundTouch API Client
|
||||
---
|
||||
title: "Project Structure Patterns: Bose SoundTouch API Client"
|
||||
sidebar:
|
||||
exclude: true
|
||||
---
|
||||
## Summary for Reuse in API Client Projects
|
||||
|
||||
This document describes the most important patterns for the Bose SoundTouch API client, especially for XML-based API clients with Web UI, CLI tool, and WASM support.
|
||||
+5
-2
@@ -1,5 +1,8 @@
|
||||
# Request Recording Concept
|
||||
|
||||
---
|
||||
title: "Request Recording Concept"
|
||||
sidebar:
|
||||
exclude: true
|
||||
---
|
||||
## Problem Statement
|
||||
|
||||
The current request recording system has fundamental issues when dealing with request cloning, body consumption, and multiple response scenarios. Specifically:
|
||||
+5
-2
@@ -1,5 +1,8 @@
|
||||
# SCMUDC Enrichment Implementation Summary
|
||||
|
||||
---
|
||||
title: "SCMUDC Enrichment Implementation Summary"
|
||||
sidebar:
|
||||
exclude: true
|
||||
---
|
||||
## Overview
|
||||
|
||||
This document summarizes the implementation of SCMUDC (Sound Control Management Usage Data Collection) event enrichment in the AfterTouch toolkit. The enhancement provides human-readable analysis of device telemetry data to improve usability and debugging capabilities.
|
||||
+5
-2
@@ -1,5 +1,8 @@
|
||||
# Service Availability Implementation Summary
|
||||
|
||||
---
|
||||
title: "Service Availability Implementation Summary"
|
||||
sidebar:
|
||||
exclude: true
|
||||
---
|
||||
## Overview
|
||||
|
||||
This document summarizes the implementation of the `/serviceAvailability` endpoint support in the Bose SoundTouch Go client library. This feature enables applications to query which music services and input sources are available on a SoundTouch device, providing better user feedback about supported stations and sources.
|
||||
+12
-9
@@ -1,5 +1,8 @@
|
||||
# 🎉 Introducing SoundTouch Service: Local Cloud Service Emulation
|
||||
|
||||
---
|
||||
title: "🎉 Introducing SoundTouch Service: Local Cloud Service Emulation"
|
||||
sidebar:
|
||||
exclude: true
|
||||
---
|
||||
**Date**: February 2026
|
||||
**Version**: v2.0.0+
|
||||
**Status**: Production Ready
|
||||
@@ -144,10 +147,10 @@ LOG_PROXY_BODY=true soundtouch-service
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
- **[Complete Service Guide](guides/SOUNDTOUCH-SERVICE.md)**: Comprehensive setup and configuration
|
||||
- **[API Reference](guides/SOUNDTOUCH-SERVICE.md#api-reference)**: Full endpoint documentation
|
||||
- **[Migration Guide](guides/SOUNDTOUCH-SERVICE.md#device-migration)**: Step-by-step device migration
|
||||
- **[Troubleshooting](guides/SOUNDTOUCH-SERVICE.md#troubleshooting)**: Common issues and solutions
|
||||
- **[Complete Service Guide](../guides/SOUNDTOUCH-SERVICE.md)**: Comprehensive setup and configuration
|
||||
- **[API Reference](../guides/SOUNDTOUCH-SERVICE.md#api-reference)**: Full endpoint documentation
|
||||
- **[Migration Guide](../guides/SOUNDTOUCH-SERVICE.md#device-migration)**: Step-by-step device migration
|
||||
- **[Troubleshooting](../guides/SOUNDTOUCH-SERVICE.md#troubleshooting)**: Common issues and solutions
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
@@ -172,9 +175,9 @@ The collaborative spirit of reverse engineering and documentation in the SoundTo
|
||||
## 🔗 Links
|
||||
|
||||
- **[Main Repository](https://github.com/gesellix/bose-soundtouch)**
|
||||
- **[Service Documentation](guides/SOUNDTOUCH-SERVICE.md)**
|
||||
- **[CLI Documentation](guides/CLI-REFERENCE.md)**
|
||||
- **[Getting Started Guide](guides/GETTING-STARTED.md)**
|
||||
- **[Service Documentation](../guides/SOUNDTOUCH-SERVICE.md)**
|
||||
- **[CLI Documentation](../guides/CLI-REFERENCE.md)**
|
||||
- **[Getting Started Guide](../guides/GETTING-STARTED.md)**
|
||||
- **[SoundCork Project](https://github.com/deborahgu/soundcork)**
|
||||
- **[ÜberBöse API](https://github.com/julius-d/ueberboese-api)**
|
||||
|
||||
+5
-1
@@ -1,4 +1,8 @@
|
||||
# Undocumented Community Features & API Discoveries
|
||||
---
|
||||
title: "Undocumented Community Features & API Discoveries"
|
||||
sidebar:
|
||||
exclude: true
|
||||
---
|
||||
This document captures advanced API endpoints and device behaviors discovered by the SoundTouch community through reverse engineering projects like **SoundCork** and **ÜberBöse API**. These features are not documented in the official Bose SoundTouch Web API v1.0 but are crucial for full device emulation and offline operation.
|
||||
## Cloud Emulation (Marge/BMX) Discoveries
|
||||
While the local `/8090` API is well-documented, the cloud-side service emulation reveals deeper device integration points.
|
||||
+5
-2
@@ -1,5 +1,8 @@
|
||||
# Unimplemented SoundTouch API Endpoints
|
||||
|
||||
---
|
||||
title: "Unimplemented SoundTouch API Endpoints"
|
||||
sidebar:
|
||||
exclude: true
|
||||
---
|
||||
**Last Updated:** January 2026
|
||||
**Source:** [SoundTouch Plus Wiki](https://github.com/thlucas1/homeassistantcomponent_soundtouchplus/wiki/SoundTouch-WebServices-API)
|
||||
**Current Implementation:** 35 endpoints (including preset & navigation management discovered via SoundTouch Plus Wiki)
|
||||
@@ -0,0 +1,9 @@
|
||||
---
|
||||
title: "Appendix"
|
||||
weight: 99
|
||||
sidebar:
|
||||
open: false
|
||||
---
|
||||
|
||||
Internal design documents, implementation notes, and historical records.
|
||||
These are developer and maintainer references, not user guides.
|
||||
+5
-2
@@ -1,5 +1,8 @@
|
||||
# Device Lifecycle and /power_on Enhancement
|
||||
|
||||
---
|
||||
title: "Device Lifecycle and /power_on Enhancement"
|
||||
sidebar:
|
||||
exclude: true
|
||||
---
|
||||
## Overview
|
||||
|
||||
This document provides a comprehensive analysis of the current SoundTouch device registration and lifecycle management implementation, and proposes enhancements using the `/power_on` endpoint to reduce dependency on local network connectivity.
|
||||
+5
-2
@@ -1,5 +1,8 @@
|
||||
# Device Lifecycle Analysis - Executive Summary
|
||||
|
||||
---
|
||||
title: "Device Lifecycle Analysis - Executive Summary"
|
||||
sidebar:
|
||||
exclude: true
|
||||
---
|
||||
## Current State Assessment
|
||||
|
||||
The SoundTouch service currently relies heavily on local network connectivity for device discovery and management:
|
||||
+5
-2
@@ -1,5 +1,8 @@
|
||||
# /power_on Implementation Guide
|
||||
|
||||
---
|
||||
title: "/power_on Implementation Guide"
|
||||
sidebar:
|
||||
exclude: true
|
||||
---
|
||||
## Overview
|
||||
|
||||
This guide provides detailed technical specifications for implementing `/power_on` endpoint enhancements to reduce network dependency and improve device lifecycle management in the SoundTouch service.
|
||||
@@ -1,5 +1,8 @@
|
||||
# SoundTouch `/storePreset` Implementation Guide
|
||||
|
||||
---
|
||||
title: "SoundTouch `/storePreset` Implementation Guide"
|
||||
sidebar:
|
||||
exclude: true
|
||||
---
|
||||
## Overview
|
||||
|
||||
This document analyzes the feasibility and implementation approach for adding `/storePreset` functionality to the Bose SoundTouch API client, based on [GitHub Issue #14](https://github.com/gesellix/Bose-SoundTouch/issues/14) and endpoints discovered through the comprehensive [SoundTouch Plus Wiki](https://github.com/thlucas1/homeassistantcomponent_soundtouchplus/wiki/SoundTouch-WebServices-API).
|
||||
+5
-2
@@ -1,5 +1,8 @@
|
||||
# SCMUDC Events Analysis
|
||||
|
||||
---
|
||||
title: "SCMUDC Events Analysis"
|
||||
sidebar:
|
||||
exclude: true
|
||||
---
|
||||
## Overview
|
||||
|
||||
SCMUDC (Sound Control Management Usage Data Collection) events are telemetry data sent from SoundTouch devices to `events.api.bosecm.com` via `/v1/scmudc/{deviceId}` endpoints. These events track user interactions and device behaviors for analytics and monitoring.
|
||||
+20
-5
@@ -1,10 +1,19 @@
|
||||
# soundtouch-web: remaining features
|
||||
|
||||
---
|
||||
title: "soundtouch-web: remaining features"
|
||||
sidebar:
|
||||
exclude: true
|
||||
---
|
||||
Four features complete the parity gap between soundtouch-web and the Stockholm
|
||||
app's local-control functionality. Everything else in Stockholm (OAuth flows,
|
||||
setup wizard, service account linking, onboarding, analytics) is cloud
|
||||
infrastructure that is either shut down or already handled by soundtouch-service.
|
||||
|
||||
> **Shipped:** Saving the current content to a preset slot (slots 1–6) is
|
||||
> already implemented — a ★ star button in the top-right corner of the Now
|
||||
> Playing card opens a slot picker, and a **+** button on each preset tile
|
||||
> saves to that slot directly. See [PRESET-QUICKSTART.md](PRESET-QUICKSTART.md)
|
||||
> for usage details.
|
||||
|
||||
---
|
||||
|
||||
## 1. Seek / scrub
|
||||
@@ -34,10 +43,16 @@ func (c *Client) Seek(positionSeconds int) error {
|
||||
|
||||
---
|
||||
|
||||
## 2. Favorites
|
||||
## 2. Favorites (device-native, distinct from presets)
|
||||
|
||||
Mark or unmark the currently playing track as a favourite directly from the
|
||||
Now Playing card.
|
||||
> **Note:** This section is about the speaker's **built-in** `/favorites` API —
|
||||
> a separate concept from the 6 preset slots. Preset-slot saving (★ star /
|
||||
> **+** button) is already shipped; the native Favorites API is not yet
|
||||
> surfaced in soundtouch-web.
|
||||
|
||||
Mark or unmark the currently playing track as a device favourite directly from
|
||||
the Now Playing card. Unlike presets (maximum 6, numbered slots), the device
|
||||
can hold a larger favourites list; support varies by source.
|
||||
|
||||
**Device API:**
|
||||
- `GET /favorites` — returns `<favorites>` list
|
||||
@@ -1,5 +1,8 @@
|
||||
# Stockholm Backend — Port Guide for Bose-SoundTouch (Go)
|
||||
|
||||
---
|
||||
title: "Stockholm Backend — Port Guide for Bose-SoundTouch (Go)"
|
||||
sidebar:
|
||||
exclude: true
|
||||
---
|
||||
This document describes everything needed to integrate the
|
||||
[krahl/soundcork-stockholm-app](https://github.com/krahl/soundcork-stockholm-app)
|
||||
functionality into the Go service. It is written as a reference; nothing here
|
||||
+12
-1
@@ -1,4 +1,15 @@
|
||||
# Device-Local Install: Four User Journeys
|
||||
---
|
||||
title: "Device-Local Install: Four User Journeys"
|
||||
---
|
||||
> **Looking for how to actually install AfterTouch?**
|
||||
> See the [Deployment Overview](../guides/DEPLOYMENT-OVERVIEW.md) for user-friendly
|
||||
> step-by-step guides for both deployment scenarios (external host and on-device).
|
||||
>
|
||||
> This document is an **architectural analysis** — user journeys, install patterns,
|
||||
> technology tradeoffs, and future directions. It is aimed at contributors and
|
||||
> project planning, not at end users.
|
||||
|
||||
---
|
||||
|
||||
A user-journey-shaped view of where AfterTouch sits today and where it could go. The same speaker, the same constraints, but four different audiences with non-overlapping needs:
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Architecture"
|
||||
weight: 5
|
||||
---
|
||||
@@ -1,5 +1,6 @@
|
||||
# Encrypting Sensitive Data Exports with SSH/age or GPG
|
||||
|
||||
---
|
||||
title: "Encrypting Sensitive Data Exports with SSH/age or GPG"
|
||||
---
|
||||
## Problem
|
||||
|
||||
Allow users of our software to export potentially sensitive data, encrypt it locally, and send it to us. We decrypt on our side. Goal: no key exchange, minimal user friction.
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
title: "Concepts"
|
||||
weight: 3
|
||||
---
|
||||
+3
-2
@@ -1,5 +1,6 @@
|
||||
# Amazon Music OAuth Integration
|
||||
|
||||
---
|
||||
title: "Amazon Music OAuth Integration"
|
||||
---
|
||||
This document describes the plan and specification for adding Amazon Music OAuth support to the SoundTouch service, enabling continued Amazon Music playback after the Bose cloud shutdown (May 2026).
|
||||
|
||||
The implementation mirrors the [Spotify OAuth integration](spotify-oauth.md) closely. Read that document first — this one calls out only the differences.
|
||||
@@ -1,5 +1,6 @@
|
||||
# Spotify OAuth Integration
|
||||
|
||||
---
|
||||
title: "Spotify OAuth Integration"
|
||||
---
|
||||
> **New here?** Start with [spotify-overview.md](spotify-overview.md) for the
|
||||
> mental model (Spotify Connect vs OAuth-intercept, DNS rewrite gotcha,
|
||||
> end-to-end token lifecycle). This document zooms in on the OAuth flows and
|
||||
@@ -1,5 +1,6 @@
|
||||
# Spotify on SoundTouch — Overview
|
||||
|
||||
---
|
||||
title: "Spotify on SoundTouch — Overview"
|
||||
---
|
||||
This is the entry point for understanding how Spotify works on a SoundTouch
|
||||
speaker behind AfterTouch. Read this first; the deeper docs assume you already
|
||||
have the mental model below.
|
||||
+3
-2
@@ -1,5 +1,6 @@
|
||||
# Spotify Priming Strategy
|
||||
|
||||
---
|
||||
title: "Spotify Priming Strategy"
|
||||
---
|
||||
> **New here?** Start with [spotify-overview.md](spotify-overview.md) for the
|
||||
> mental model. This document goes deep on the priming protocol, ZeroConf DH
|
||||
> exchange, and deployment topologies.
|
||||
@@ -1,5 +1,6 @@
|
||||
# Migration Flow Diagrams
|
||||
|
||||
---
|
||||
title: "Migration Flow Diagrams"
|
||||
---
|
||||
This document specifies the diagrams needed for the migration guide, with descriptions that can be used to create actual visual diagrams.
|
||||
|
||||
## 1. Overall Migration Process Flow
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user