Commit Graph
247 Commits
Author SHA1 Message Date
Tobias GesellchenandClaude Opus 4.8 bd62fd6658 refactor: rename soundtouch-web to soundtouch-player (transitional alias) (refs #451)
The web player is intrinsically LAN-resident: it reaches speakers directly
and only delegates cloud-only features (e.g. TTS) to a possibly-remote
AfterTouch service via --service-url. That is exactly what a cloud-hosted
soundtouch-service cannot do, so the standalone player binary stays useful
and is not being deprecated. Rename it to state its purpose, with a
transition window so existing downloads keep working.

- cmd/soundtouch-web -> cmd/soundtouch-player; CLI name is now
  soundtouch-player. When the binary is invoked under its old name it prints
  a one-line rename notice (filepath.Base(os.Args[0])).
- Build/release both names from the same source: Makefile (build-player +
  build-web alias, dev-player* targets), Dockerfile (soundtouch-player image
  + transitional soundtouch-web image), release.yml and ci.yml (player +
  web artifacts, checksums, Docker images; release notes announce the
  rename). The soundtouch-web binary, image, and install script remain a
  transitional alias to be dropped in a future release (which will break
  stale fetch scripts and nudge users to the release notes).
- scripts/raspberry-pi/install-player.sh is canonical; install-web.sh keeps
  working but warns.
- Sweep docs, code comments, user-facing strings, and assets
  (soundtouch-web-ui.png, soundtouch-web-tunein.png, soundtouch-web-roadmap.md)
  to soundtouch-player; README documents the rename and why the player
  remains separate from the embedded /app.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 16:33:39 +02:00
Tobias GesellchenandClaude Opus 4.8 3d67e99b2d docs(architecture): correct merge-overlap analysis + sequence the web migration (refs #451)
Verified the actual overlap between the service and soundtouch-web routers; the
doc's "/, /health, /ws are all collisions" was too broad:

- `/` is the only true collision -> resolve with a landing page (Admin/Setup vs App).
- `/health` is a merge (both define it; standardise on the service's richer body,
  and check nothing depends on the web's {"status":"ok","version"} shape).
- `/ws` and `/static/*` are additive -- the service registers neither.

Sequence the merge to mirror the proven service approach but adapted to -web:
- Migrate `-web` in place to the target shape (`/api/control/*`, `/app/*`) FIRST,
  as a direct restructure -- no dual-mount, no deprecation signal -- because its
  only client is its own bundled frontend (reload-to-fix). The careful
  add-alias-then-deprecate dance stays reserved for the central `-service`.
- The subsequent fold-in is then a near-additive mount plus the `/` landing page
  and `/health` standardisation.

Also: resolve overlaps structurally before merging (a flag that conditionally
registers routes hides a collision, it does not fix it; do not rely on chi to
warn); ship the merged variant behind an opt-in flag whose purpose is optional
testing/feedback (default-off also keeps the surface unexposed until auth lands),
not a collision guard. Note the deprecation signal is already implemented for
/setup and /mgmt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 23:05:45 +02:00
Tobias GesellchenandClaude Opus 4.8 44f3ccfc18 docs(anonymization): keep the non-conformant 192.168.1.10 example
Revert an over-eager sanitization: this paragraph explains *why* RFC-1918 ranges
make poor placeholders, and deliberately uses 192.168.1.10 as the
non-conformant counter-example. Rewriting it to an RFC-5737 address defeated the
point (192.0.2.10 is obviously a documentation placeholder). Restore the
illustrative bad example.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 19:11:24 +02:00
Tobias Gesellchen 2c2bb54eff chore 2026-06-06 19:11:24 +02:00
Tobias GesellchenandClaude Opus 4.8 7e0573032c chore(sanitize): remove real device ID and personal LAN IPs from tracked files
Per the repo's no-real-data rule (CLAUDE.md), scrub committed files only (the
gitignored _/ local captures are left as-is):

- Real Bose-OUI device ID 08DF1F0BA325 -> placeholder AABBCCDDEE0A across 4 docs
  and 8 Go test files (consistent 1:1 rename; affected packages tested green).
- Personal/topology LAN IPs -> RFC-5737: the lab runbook's AP subnet
  192.168.10.x -> 198.51.100.x (192.0.2.x is already used contrastively there)
  and 192.168.100.1 -> 203.0.113.1; illustrative example IPs in
  ANONYMIZATION-SUMMARY / spotify-overview / TROUBLESHOOTING -> 192.0.2.x.
- Kept factual RFC-1918 range citations (10.0.0.0/8 trusted-proxy example,
  192.168.0.0/16 "all private subnets") since they name the ranges themselves.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 19:11:24 +02:00
Tobias GesellchenandClaude Opus 4.8 02be18c19c docs(api): reconcile stale endpoint-coverage docs against pkg/client (refs #451)
The device-API coverage docs had drifted from the code. Verified each claim
against pkg/client and corrected:

- UNIMPLEMENTED-ENDPOINTS.md: re-marked endpoints now implemented but still
  listed as candidates — setMusicServiceAccount / removeMusicServiceAccount and
  the stereo-pair group set (getGroup/addGroup/removeGroup/updateGroup); added a
  reconciliation note and clarified this tracks the speaker :8090 API, not the
  service router.
- SUPPORTED-URLS.md: fixed the "Not Yet Implemented" lists (music services,
  presets, stations, navigate, speaker, requestToken/notification/playNotification
  are all implemented), the contradictory storePreset double-listing, the native
  group section, and the System Info over-claim (trackInfo non-functional,
  bluetoothInfo not implemented).
- API-COVERAGE.md: fixed the exec-summary count (18/19 -> 20/21) to match its own
  table and refreshed the date.

Also sanitised a real device ID (08DF1F0BA325 -> placeholder) found in
SUPPORTED-URLS.md, per the repo's no-real-MACs rule.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 19:11:24 +02:00
Tobias GesellchenandClaude Opus 4.8 59f7ed5543 docs(architecture): cross-link the API route layout note (refs #451)
- architecture/_index.md: list the section's docs with links.
- reference/CLOUD-API.md: "See also" pointer (service cloud-emulation routes).
- reference/API-ENDPOINTS.md: note distinguishing the speaker device API from
  the service route layout, with a link.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 19:11:24 +02:00
Tobias GesellchenandClaude Opus 4.8 b4b23a3716 docs(architecture): add API route layout and refactoring plan (refs #451)
Architectural reference for the staged API refactoring that precedes the
soundtouch-web / soundtouch-service merge:

- Route classification by client audience and what pins each path (frozen
  firmware contract vs externally-pinned OAuth callbacks vs our movable
  admin/control surface), with service + web route tables.
- Actors model (speaker / app / cloud) and deployment topologies; speaker-direct
  vs data-plane reachability.
- deployment-mode parameter (private/shared/public), trust tiers, auth posture
  (opt-none -> opt-in -> opt-out?), and auth mechanisms (Marge as one auth
  provider like EntraID; native/headless clients via RFC 8252 loopback or a
  headless token; identity in logs).
- /app/* single role-gated app with code-splitting for on-device size.
- Versioning policy: no path versioning; semver with 0.x dual-routing and a 1.x
  cutover that removes obsolete routes.
- Staged migration (add+alias, fold in web, deprecate the binary, observable
  old-route warnings) with a "before 1.x" definition of done.
- Regression safety: contract tests from the frozen recordings, building on the
  existing tests/integration/http-client suite.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-06 19:11:24 +02:00
Tobias Gesellchen c297a90be3 chore: update version to v0.107.0 in docs/scripts 2026-06-04 17:17:09 +02:00
Tobias Gesellchen 8232fd1401 chore: update version to v0.104.1 in all installer scripts 2026-05-31 23:43:44 +02:00
Tobias GesellchenandClaude Opus 4.8 d94b1bc067 fix(web): trust service CA and send a known target for TTS
soundtouch-web's "Speak" feature proxies to the AfterTouch service's
/setup/tts/speak endpoint. Two issues blocked it end to end.

1. TLS: the proxy used http.DefaultClient, which trusts only system
   roots, so the HTTPS call to a service using its own self-signed CA
   failed with "x509: certificate signed by unknown authority". Add a
   --service-ca flag (SERVICE_CA env) that loads the CA PEM, appends it
   to the system pool, and uses a custom client for the TTS call.

2. Target: soundtouch-web sent device.Client.Host() (a full base URL
   like http://ip:8090), but the service's SSRF guard exact-matches the
   target against bare datastore IPs, returning "host ... is not a known
   device". Prefer the device ID (the canonical key) and send a bare-IP
   host fallback. Also normalize the incoming host in resolveTTSHost so a
   URL/host:port form still resolves; it still only ever returns a
   datastore IP, so the SSRF guarantee is unchanged.

Adds unit tests for the CA client builder, hostOnly, and resolveTTSHost
(including the preserved unknown-host/device rejections). Documents
--service-ca in the soundtouch-web README and TROUBLESHOOTING guide.
Wires SERVICE_URL and SERVICE_CA (empty defaults) into the Raspberry Pi
install-web.sh env file and documents them in the Pi guide.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 23:37:33 +02:00
Tobias GesellchenandClaude Opus 4.8 7051793e81 chore: bump version to v0.104.0 and refresh UI screenshots
Update v0.103.0 -> v0.104.0 across installer scripts, walkthrough docs,
and example go.mod files, and refresh the devices/migration/settings/sync
UI screenshots.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 22:48:24 +02:00
Tobias GesellchenandClaude Opus 4.8 258cc6198f fix(web): stop TTS proxy from using a browser-supplied service URL (SSRF)
CodeQL flagged "uncontrolled data used in network request": the
soundtouch-web TTS proxy built its outbound request URL from the
client-supplied serviceUrl, letting any LAN caller use the endpoint as an
SSRF proxy. The proxy target must be the operator-configured --service-url.

- handler: use only app.ServiceURL; drop the client-supplied serviceUrl
  field and fallback.
- web TTS view: show the configured service URL read-only with an
  explanation of why it can't be edited here (Play URL differs — its URL
  is handed to the speaker, not fetched by soundtouch-web, so no SSRF).
- api.speak no longer sends serviceUrl.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 22:35:31 +02:00
Tobias GesellchenandClaude Opus 4.8 382c68d2b6 fix(setup): seed audionotification host(s) into /etc/hosts for /speaker TTS
soundcork#104 confirms speakers validate the /speaker audio-notification
app_key against audionotification.api.bosecm.com (100 calls/day on real
Bose). Our /v1/auth shim accepts it, but a host-seeded migration only
worked if the speaker resolved that host to us. DNS interception already
covers it (bosecm.com substring), but the /etc/hosts migration domain
list did not — so the speaker method would fail on hosts-based setups.

Seed both audionotification.api.bosecm.com and the dev variant
(audionotificationdev.api.bosecm.com; firmware may use either) into the
migration /etc/hosts lists, and update the mock fixtures/docs accordingly.
/v1/auth is path-based, so it already answers regardless of which host the
speaker thinks it is calling.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 22:35:31 +02:00
Tobias GesellchenandClaude Opus 4.8 80cfb03f6e feat(tts): default to /speaker playback; drop /v1/auth debug dump
Confirmed working on a real speaker (Bose_Lisa/27.0.6): the speaker GETs
/v1/auth at audionotification.api.bosecm.com (DNS-redirected to us) with
the app_key in an "Apikeyheader" header, and an empty 200 is sufficient.

- Make "speaker" the default playback method (ducks + resumes the current
  playback, supports volume) for the speak endpoint, the CLI --method flag,
  and the web UI button; "radio" remains opt-in.
- Remove the temporary full-request debug dump from /v1/auth now that the
  contract is understood; document it in the handler comment instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 22:35:31 +02:00
Tobias GesellchenandClaude Opus 4.8 22c3142a79 refactor(cli): move Cloud TTS under speaker tts-cloud, use global --host
Replaces the awkward top-level `tts speak --speaker-host` with a
`speaker tts-cloud` subcommand that sits alongside the existing
`speaker tts` and uses the global --host flag (--device still works as
an alternative). The two are now clearly related: `speaker tts` sends a
Google Translate URL straight to the speaker, while `speaker tts-cloud`
routes through the service for server-side synthesis (Cloud TTS) and
playback. --speaker-host is gone.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 22:35:31 +02:00
Tobias GesellchenandClaude Opus 4.8 169c1c5b9f fix(tts): move TTS endpoints from /mgmt to /setup (no Basic Auth)
The TTS speak/config endpoints were under /mgmt (Basic-Auth protected),
but the soundtouch-web proxy and CLI authenticated with their own
mgmt-password default (empty) while the service defaults to "change_me!",
so speaking from -web returned 401.

This was also inconsistent: the Google API key is configured via the
unauthenticated /setup/settings, and Play URL already proxies to /setup,
so gating only TTS playback behind mgmt auth made no sense. Move
/mgmt/tts/{speak,config} to /setup/tts/{speak,config} (LAN-trust, like
the rest of the setup surface), rename the handlers accordingly, and drop
the now-unused mgmt-credential plumbing from soundtouch-web and the CLI
tts command.

Verified: POST /setup/tts/speak now reaches the handler without auth
(502 only because the test speaker IP is unreachable; previously 401).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 22:35:31 +02:00
Tobias GesellchenandClaude Opus 4.8 c852d07da1 feat(tts): add Google Cloud Text-to-Speech via a pluggable provider
Adds text-to-speech that synthesizes higher-quality audio (Google Cloud
TTS) and plays it on a speaker via the /speaker endpoint. Because Cloud
TTS returns audio bytes (not a fetchable URL), the service caches the
clip and hosts it at GET /media/tts/{id}, mirroring the "ding" endpoint,
then points the speaker at that local URL.

The design is a pluggable Provider interface (pkg/service/tts) wrapping
two modes:
- translate: hands the speaker the (undocumented) Google Translate URL
  directly (no credentials), reusing models.BuildTranslateTTSURL.
- google-cloud: REST API key auth (no SDK/gRPC), bytes cached locally.

Surfaces:
- service: POST /mgmt/tts/speak, GET /mgmt/tts/config, GET /media/tts/{id};
  configured via TTS_PROVIDER / TTS_GOOGLE_API_KEY / TTS_LANGUAGE /
  TTS_VOICE / TTS_APP_KEY / TTS_VOLUME.
- CLI: `soundtouch-cli tts speak` (calls the service with mgmt Basic Auth).
- web: a "TTS" source view (like Play URL / TuneIn), proxied to the
  service via /api/device-speak/{id}.

The /speaker app_key requirement and model limitations still apply; see
docs/content/docs/reference/SPEAKER-ENDPOINT.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 22:35:31 +02:00
Tobias Gesellchen 80cab16239 chore: update version to v0.103.0 in all installer scripts 2026-05-31 13:25:42 +02:00
Tobias Gesellchen d8166facf0 chore: update version to v0.102.0 in all installer scripts 2026-05-30 23:45:48 +02:00
Tobias GesellchenandClaude Opus 4.8 c7eda7ed7b feat(cli)!: deprecate speaker-based station search in favour of find
The `find` family runs the search inside the CLI, querying the radio
provider's public API directly (no speaker cloud, no soundtouch-service).
Make it the canonical path and deprecate the speaker-based search family.

- Add `find-tunein` and `find-radiobrowser` siblings; refactor the find
  actions onto a shared `runFind` helper (all support `--more`).
- Rename the unreleased `search-radiobrowser` to `find-radiobrowser`.
- Deprecate `search`, `search-tunein`, `search-pandora`, `search-spotify`:
  they keep working but print a stderr deprecation notice (new
  `PrintDeprecation` helper) pointing at the `find*` replacement. Pandora
  and Spotify have no built-in equivalent yet (they need the speaker +
  account), so their notices say so.
- Docs: lead with the `find` family as recommended; mark the speaker-based
  search commands deprecated; drop the misleading "service-side" wording
  in favour of "built-in / queries the provider directly".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 20:53:40 +02:00
Tobias GesellchenandClaude Opus 4.8 21efb412fe docs(cli): document service-side station find + search-radiobrowser
Add CLI-REFERENCE entries for the new service-side search commands
(`station find --provider tunein|radiobrowser [--more]` and
`station search-radiobrowser`), with a subsection explaining they run
the search in AfterTouch itself — working without the speaker's live
cloud and without a reachable --host. Also document the pre-existing
but undocumented `station list`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-30 20:53:40 +02:00
Tobias GesellchenandClaude Sonnet 4.6 8defb0b833 docs(troubleshooting): add cross-subnet / VLAN isolation section
Documents the iptables block that SoundTouch firmware (since 2018)
applies to traffic from other subnets, which prevents AfterTouch from
being reachable when the speaker and server are on different VLANs.

Two fixes: targeted ACCEPT rule (from spookie85, discussion #354) and
the simpler DROP-line comment-out (from dekiesel). Also notes the ST20
Series I outbound-port restriction on non-standard ports (gmuth).

Outgoing link kept to our own discussion #354 for attribution; the
external third-party issue link is omitted as it may go stale.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 11:26:40 +02:00
Tobias Gesellchen 6071146851 chore: update version to v0.100.0 in all installer scripts 2026-05-30 11:18:02 +02:00
Tobias GesellchenandClaude Sonnet 4.6 1685b2f442 docs: make docs the single source of truth for install/update/removal flows
Following user feedback (Lang, issue #432 thread) the docs guides now
contain all operational detail — installation, configuration, service
management, logs, updates, and removal — and the scripts READMEs become
thin pointers to the docs rather than the other way around.

RASPBERRY-PI.md: expanded to cover soundtouch-web alongside
soundtouch-service (install, config, port-conflict note, service
management, logs, update, removal, arch auto-detection, security).
scripts/raspberry-pi/README.md: trimmed to a quick-start with the two
one-liners plus a link to the docs guide.

EXTERNAL-HOST-WALKTHROUGH.md Step 7: replaces the vague "download from
Releases" note with the actual install-web.sh one-liner and a link to
RASPBERRY-PI.md#soundtouch-web; adds a non-Pi install option too.

ON-DEVICE-INSTALL-WALKTHROUGH.md: removed both back-references to
scripts/on-device-install/README.md; added self-contained sections for
Updating (with rollback tip), Service management, Logs, and Uninstalling
so the walkthrough is complete without leaving the docs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-30 11:15:40 +02:00
Tobias Gesellchen c521414eb1 chore: update version to v0.99.0 in all installer scripts 2026-05-29 00:36:07 +02:00
Tobias Gesellchen d70e336e52 chore: update version to v0.98.0 in all installer scripts 2026-05-28 23:11:15 +02:00
Tobias GesellchenandClaude Sonnet 4.6 93bc04b334 fix(docs): fix font 404 on GitHub Pages (../../fonts/ path in custom.css)
Hextra's production build bundles assets/css/custom.css into
css/compiled/main.css. The original '../fonts/' relative path resolved
correctly from css/custom.css (dev) but landed at css/fonts/ in
production — one directory too deep.

Fix: use '../../fonts/' so the URL resolves correctly from every
output location browsers may encounter:

  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/

Browsers clamp traversal at the origin root, so going two levels up
from /css/custom.css still reaches /fonts/ — safe in dev, correct in
production.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 23:19:04 +02:00
Tobias GesellchenandClaude Sonnet 4.6 6c4c420b29 docs: document soundtouch-web preset-saving UI (★ star and + button)
EXTERNAL-HOST-WALKTHROUGH.md — Step 7 "Via soundtouch-web":
  Replaced the single save path with two labelled options:
  - ★ Star button: appears in the Now Playing card's top-right corner,
    opens a slot picker (1–6), turns gold once mapped.
  - + button: appears on each preset tile on hover, saves directly to
    that slot without a picker.
  Added a one-liner on when to use each.

PRESET-QUICKSTART.md:
  New "Via soundtouch-web (browser UI)" section added above the CLI
  section, covering both the ★ star and + paths with step-by-step
  instructions.

soundtouch-web-roadmap.md:
  - Added a "Shipped" callout noting that preset-slot saving is done.
  - Retitled the Favorites section to "Favorites (device-native, distinct
    from presets)" and added a note clarifying it refers to the speaker's
    /favorites API (different from the 6 preset slots) which is still
    pending.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 22:47:13 +02:00
Tobias GesellchenandJunie 09770f55e7 docs: use local Noto Sans font
Co-authored-by: Junie <junie@jetbrains.com>
2026-05-26 22:47:13 +02:00
Tobias GesellchenandJunie e09476da79 docs: enable search menu item in navbar
Co-authored-by: Junie <junie@jetbrains.com>
2026-05-26 22:47:13 +02:00
Tobias GesellchenandClaude Sonnet 4.6 7c71818027 fix(docs): resolve .md links to page RelPermalink in render hook
Stripping the .md extension alone is not enough under Hugo pretty URLs.
A page rendered at /guides/DEPLOYMENT-OVERVIEW/ treats a bare relative
href like 'CLOUD-DEPLOY-WALKTHROUGH' as relative to that directory,
producing /guides/DEPLOYMENT-OVERVIEW/CLOUD-DEPLOY-WALKTHROUGH (404).

Switch to site.GetPage to look up the target page by its content path
(resolved relative to the current file's directory) and write its
RelPermalink into the href.  This gives an absolute path that is correct
in both the dev server and the GitHub Pages build (where --baseURL
injects the /Bose-SoundTouch/ prefix via RelPermalink automatically).

Also handles anchored links (OTHER.md#section) and falls back to
bare-stripped path when GetPage finds no match.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-26 00:01:45 +02:00
Tobias GesellchenandClaude Sonnet 4.6 7d9f3d6a39 docs: remove duplicate H1 headings from 91 pages (closes #414)
The docs framework renders frontmatter title: as the page heading.
Every file that also had a matching # Heading as the first content
line displayed the title twice. Removed the redundant H1 and its
following blank line from all 91 affected files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 21:48:43 +02:00
Tobias GesellchenandClaude Sonnet 4.6 d93d9a3e26 docs+ui: surface SSH context for remote_services (closes #409)
Migration guide: expand the one-liner after SSH setup into a concrete
'To disable SSH' section covering both the USB-stick and persistent-file
cases, with the button name and CLI command.

Admin UI:
- Preconditions label: 'remote_services' → 'SSH (remote_services)'
  with a tooltip explaining the connection
- Buttons: 'Enable/Remove Persistent Remote Services' →
  'Enable SSH (Persist remote_services)' /
  'Disable SSH (Remove remote_services)'
- Confirm dialog: mentions SSH and reboot requirement explicitly
- Verdict text: all three states now lead with 'SSH ...' so users
  recognise what the check controls

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 21:46:57 +02:00
Tobias Gesellchen f5ebe92d3c Fix external link to opencloudtouch/opencloudtouch/issues/167 2026-05-25 11:31:10 +02:00
Tobias GesellchenandClaude Sonnet 4.6 1d864c88f6 fix(docs): open sponsor footer link in same tab
Internal page — no target="_blank" needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 00:39:18 +02:00
Tobias GesellchenandClaude Sonnet 4.6 d71a5c3bed fix(docs): fix sponsor link baseURL and add git commit hash to footer
- Use relURL (no leading slash) for the sponsor link so it respects
  the /Bose-SoundTouch/ base path on GitHub Pages; absURL and relURL
  both ignore the base path when the input starts with /
- Inject HUGO_PARAMS_GITHASH (github.sha) via the docs workflow and
  forward it into the Hugo container via docker-compose.docs.yml +
  make dev-docs, so the deployed footer shows a clickable short hash
  linking to the exact commit
- Use site.Params.githash (global) instead of .Site.Params.githash
  because Hextra calls custom/footer.html with a dict context, not a
  page; .Site is nil in that scope
- Use substr not slice to trim the hash to 7 chars

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 00:37:30 +02:00
Tobias Gesellchen 70dea42c10 Use site-relative URL 2026-05-25 00:21:43 +02:00
Tobias GesellchenandClaude Sonnet 4.6 8613b2901d fix(docs): suppress semgrep var-in-href false positive in navbar-title
$logoLink is sourced from site config (never user input) and Hugo
auto-escapes template values. Pipe through safeURL to make the intent
explicit and satisfy the generic.html-templates.security.var-in-href rule.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 00:17:48 +02:00
Tobias GesellchenandClaude Sonnet 4.6 0b69153df6 feat(docs): add sponsor page with GitHub Sponsors and PayPal options
- /sponsor landing page lists both options with feature cards
- Navbar heart icon and footer sponsor link both point to /sponsor
  instead of directly to GitHub Sponsors, so PayPal is equally reachable
- No GitHub account required for PayPal path

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 00:17:48 +02:00
Tobias GesellchenandClaude Sonnet 4.6 1fcb9e4ca5 docs: improve Migration Guide and add TuneIn screenshot
Migration Guide step 1:
- Add 'Download pre-built binary' as the first option (no Go required)
- Add install-script option for Raspberry Pi / on-device deployments
- Move 'go install' to last (developer option)
- Add data/ directory callout: single directory to back up for a full restore

SoundTouch Service guide:
- Mention RadioBrowser alongside TuneIn in the BMX section
- Add soundtouch-web TuneIn search screenshot

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 00:17:48 +02:00
Tobias GesellchenandClaude Sonnet 4.6 3867f6040c fix(docs): point homepage Get Started button to Migration Guide
The previous link aimed at a Go-developer getting-started page. Most
users are not Go developers — they want to migrate their speakers.
MIGRATION-GUIDE is the right first destination.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 00:17:48 +02:00
Tobias GesellchenandClaude Sonnet 4.6 23be85925d feat(docs): add inaugural blog post
Covers what AfterTouch delivers today: migration (existing account and
factory-reset paths), marge+bmx replacement, TuneIn+RadioBrowser, Spotify,
presets, ST-10 stereo pairing, soundtouch-cli automation, soundtouch-web
browser UI, and the three installation options (on-device, local host /
Raspberry Pi Zero 2W, cloud/VPS).

Includes screenshot of the soundtouch-web UI (Spotify playback, presets,
sources, zone management).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 00:17:48 +02:00
Tobias GesellchenandClaude Sonnet 4.6 6f392699f1 feat(docs): blog infrastructure — index page and /blog-update skill
- blog/_index.md: add introductory sentence to the News & Updates index
- .claude/commands/blog-update.md: project skill that drafts a monthly
  update post from git history and opens a draft PR for review
- .gitignore: .claude/* + !.claude/commands/ so the skill is tracked
  while session state (settings.local.json, worktrees/) stays ignored

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 00:17:48 +02:00
Tobias GesellchenandClaude Sonnet 4.6 e173ed389d feat(docs): branding — logo, favicon, subtitle, and footer
- Enable navbar logo (favicon-braille.svg, 24×24)
- Override navbar-title partial to add 'Bose SoundTouch Toolkit' subtitle
- Add favicon.svg to static root (picked up by Hextra head automatically)
- Custom footer: sponsor link (left) + copyright (right) in a single row
- i18n/en.yaml: copyright text with link to github.com/gesellix
- hugo.toml: blog list sorted by date desc, tags enabled

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-25 00:17:48 +02:00
Tobias Gesellchen 872a121cbd chore: bump to v0.93.1 2026-05-24 17:49:17 +02:00
Tobias GesellchenandClaude Sonnet 4.6 81674aede4 fix(docs): use relative links in homepage shortcodes
The hextra/hero-button and hextra/feature-card shortcodes call
Hugo's relURL on any link starting with '/'. relURL prepends the
baseURL sub-path — but the deployed site was producing /docs/...
instead of /Bose-SoundTouch/docs/..., meaning relURL was seeing
a baseURL with no sub-path (likely just the domain).

Rather than depend on relURL working correctly at build time,
remove the leading slash from all four internal links. Bare paths
are emitted verbatim by the shortcode and are resolved by the
browser relative to the page's own URL (/Bose-SoundTouch/ on
GitHub Pages, / on local dev) — correct in both environments.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 14:21:48 +02:00
Tobias GesellchenandClaude Sonnet 4.6 14e9d54b4e fix(docs): pass baseURL from configure-pages to Hugo build
actions/configure-pages v5+ exports HUGO_BASEURL automatically,
which overrides hugo.toml. By adding id: pages to the step and
passing --baseURL explicitly, we get the correct sub-path
(https://gesellix.github.io/Bose-SoundTouch/) on GitHub Pages
while local dev (docker-compose.docs.yml already passes --baseURL /)
continues to work unchanged.

Also change hugo.toml baseURL to '/' as the neutral local default.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 14:08:19 +02:00
Tobias GesellchenandClaude Sonnet 4.6 63a179d987 fix: repair three remaining dead links
- docs/archive/PLAN.md: ../PROJECT-PATTERNS.md → new path under
  docs/content/docs/appendix/PROJECT-PATTERNS.md
- docs/content/docs/_index.md: fix moved-to-appendix links
  (device-lifecycle, power-on-implementation-guide, REQUEST_RECORDING_CONCEPT),
  remove dead SUMMARY.md references, fix docs/archive/ path (../../archive/)
- docs/content/docs/analysis/bose-soundtouch-community-tools.md:
  ../PARITY-SOUNDCORK.md → ../appendix/PARITY-SOUNDCORK.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 13:30:36 +02:00
Tobias GesellchenandClaude Sonnet 4.6 7fc13370de fix: repair broken links after Jekyll-to-Hugo restructure
- Extend image ignorePattern in markdown-link-check.json to cover all
  /images/*.png (covers ui-settings, ui-devices, ui-sync, ui-migration,
  speaker-ap-wifi-setup that live under docs/static/images/ but are
  referenced as absolute /images/ paths in Markdown)
- Fix appendix cross-section links: add ../ prefix to guides/, reference/,
  and analysis/ paths in PRESET-QUICKSTART, SOUNDTOUCH-SERVICE-ANNOUNCEMENT,
  CONTENT-SELECTION-IMPLEMENTATION, DEVICE-LOGGING, NAVIGATION-GUIDE,
  PARITY-SOUNDCORK, and CLAUDE.md
- Convert ../examples/* relative links in appendix to GitHub URLs (the
  examples/ dir is at repo root, not under docs/content/)
- Fix CLAUDE.md in appendix: archive/PLAN.md → ../../../archive/PLAN.md;
  remove dead PDF link
- Fix TROUBLESHOOTING.md: ../DEVICE-LOGGING.md → ../appendix/DEVICE-LOGGING.md
- Fix CAPTURE-DEVICE-PAIRING.md: ../DEVICE-SETUP.md → ../appendix/DEVICE-SETUP.md
- Fix RASPBERRY-PI.md: remove accidental ../ prefix from GitHub URL
- Fix CONTRIBUTING.md: update docs/reference/ and docs/PROJECT-PATTERNS.md
  to their new paths under docs/content/docs/
- Fix README.md: update deployment overview link to new path
- Fix BASS-CONTROLS.md and SOURCE-SELECTION.md: convert ../../pkg/models/
  relative links to GitHub URLs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 13:30:36 +02:00