mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-19 00:56:16 +00:00
Allow toggling HTTP interaction recording via CLI, environment, and Web UI
This commit is contained in:
@@ -30,7 +30,7 @@ func main() {
|
||||
ds := initDataStore(config.dataDir)
|
||||
cm := initCertificateManager(config.dataDir)
|
||||
sm := setup.NewManager(config.serverURL, ds, cm)
|
||||
server := handlers.NewServer(ds, sm, config.serverURL, config.redact, config.logBody)
|
||||
server := handlers.NewServer(ds, sm, config.serverURL, config.redact, config.logBody, config.record)
|
||||
|
||||
recorder := proxy.NewRecorder(config.dataDir)
|
||||
recorder.Redact = config.redact
|
||||
@@ -48,7 +48,7 @@ func main() {
|
||||
log.Printf("Warning: Failed to setup TLS: %v", err)
|
||||
}
|
||||
|
||||
pyProxy := setupPythonProxy(config.targetURL, config.redact, config.logBody, recorder)
|
||||
pyProxy := setupPythonProxy(config.targetURL, config.redact, config.logBody, recorder, server)
|
||||
|
||||
startDeviceDiscovery(server)
|
||||
|
||||
@@ -74,6 +74,7 @@ type serviceConfig struct {
|
||||
httpsAddr string
|
||||
redact bool
|
||||
logBody bool
|
||||
record bool
|
||||
domains []string
|
||||
}
|
||||
|
||||
@@ -88,6 +89,7 @@ func loadConfig() serviceConfig {
|
||||
fHttpsServerURL := flag.String("https-server-url", "", "External HTTPS URL (env: HTTPS_SERVER_URL)")
|
||||
fRedact := flag.String("redact-logs", "", "Redact sensitive data in proxy logs (true/false, env: REDACT_PROXY_LOGS)")
|
||||
fLogBody := flag.String("log-bodies", "", "Log full request/response bodies (true/false, env: LOG_PROXY_BODY)")
|
||||
fRecord := flag.String("record-interactions", "", "Record HTTP interactions to disk (true/false, env: RECORD_INTERACTIONS)")
|
||||
|
||||
flag.Usage = func() {
|
||||
fmt.Fprintf(os.Stderr, "Usage of soundtouch-service:\n")
|
||||
@@ -214,6 +216,12 @@ func loadConfig() serviceConfig {
|
||||
}
|
||||
logBody := logBodyVal == "true"
|
||||
|
||||
recordVal := *fRecord
|
||||
if recordVal == "" {
|
||||
recordVal = os.Getenv("RECORD_INTERACTIONS")
|
||||
}
|
||||
record := recordVal != "false"
|
||||
|
||||
return serviceConfig{
|
||||
port: port,
|
||||
bindAddr: bindAddr,
|
||||
@@ -225,6 +233,7 @@ func loadConfig() serviceConfig {
|
||||
httpsAddr: httpsAddr,
|
||||
redact: redact,
|
||||
logBody: logBody,
|
||||
record: record,
|
||||
domains: domains,
|
||||
}
|
||||
}
|
||||
@@ -247,7 +256,7 @@ func initCertificateManager(dataDir string) *certmanager.CertificateManager {
|
||||
return cm
|
||||
}
|
||||
|
||||
func setupPythonProxy(targetURL string, redact, logBody bool, recorder *proxy.Recorder) *httputil.ReverseProxy {
|
||||
func setupPythonProxy(targetURL string, redact, logBody bool, recorder *proxy.Recorder, server *handlers.Server) *httputil.ReverseProxy {
|
||||
target, err := url.Parse(targetURL)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to parse target URL: %v", err)
|
||||
@@ -262,6 +271,7 @@ func setupPythonProxy(targetURL string, redact, logBody bool, recorder *proxy.Re
|
||||
|
||||
currentLp := proxy.NewLoggingProxy(target.String(), redact)
|
||||
currentLp.LogBody = logBody
|
||||
currentLp.RecordEnabled = server.GetRecordEnabled()
|
||||
currentLp.SetRecorder(recorder)
|
||||
currentLp.LogResponse(res)
|
||||
|
||||
@@ -274,6 +284,7 @@ func setupPythonProxy(targetURL string, redact, logBody bool, recorder *proxy.Re
|
||||
|
||||
currentLp := proxy.NewLoggingProxy(target.String(), redact)
|
||||
currentLp.LogBody = logBody
|
||||
currentLp.RecordEnabled = server.GetRecordEnabled()
|
||||
currentLp.SetRecorder(recorder)
|
||||
currentLp.LogRequest(req)
|
||||
}
|
||||
|
||||
+12
-11
@@ -135,17 +135,18 @@ Use the web interface or API to migrate devices from Bose cloud services to your
|
||||
|
||||
The service can be configured via environment variables or command-line flags:
|
||||
|
||||
| Variable | Flag | Description | Default |
|
||||
|----------------------|------------------------|--------------------------------------------------|---------------------------|
|
||||
| `PORT` | `--port` | Port to bind the service to | `8000` |
|
||||
| `BIND_ADDR` | `--bind` | Network interface to bind to | all (ipv4 and ipv6) |
|
||||
| `DATA_DIR` | `--data-dir` | Directory for persistent data | `./data` |
|
||||
| `SERVER_URL` | `--server-url` | External URL of this service | `http://<hostname>:8000` |
|
||||
| `HTTPS_SERVER_URL` | `--https-server-url` | External HTTPS URL | `https://<hostname>:8443` |
|
||||
| `PYTHON_BACKEND_URL` | | URL for Python-based service components (legacy) | |
|
||||
| `REDACT_PROXY_LOGS` | `--redact-logs` | Redact sensitive data in proxy logs | `true` |
|
||||
| `LOG_PROXY_BODY` | `--log-bodies` | Log full request/response bodies | `false` |
|
||||
| `DISCOVERY_INTERVAL` | `--discovery-interval` | Device discovery interval | `5m` |
|
||||
| Variable | Flag | Description | Default |
|
||||
|-----------------------|-------------------------|--------------------------------------------------|---------------------------|
|
||||
| `PORT` | `--port` | Port to bind the service to | `8000` |
|
||||
| `BIND_ADDR` | `--bind` | Network interface to bind to | all (ipv4 and ipv6) |
|
||||
| `DATA_DIR` | `--data-dir` | Directory for persistent data | `./data` |
|
||||
| `SERVER_URL` | `--server-url` | External URL of this service | `http://<hostname>:8000` |
|
||||
| `HTTPS_SERVER_URL` | `--https-server-url` | External HTTPS URL | `https://<hostname>:8443` |
|
||||
| `PYTHON_BACKEND_URL` | | URL for Python-based service components (legacy) | |
|
||||
| `REDACT_PROXY_LOGS` | `--redact-logs` | Redact sensitive data in proxy logs | `true` |
|
||||
| `LOG_PROXY_BODY` | `--log-bodies` | Log full request/response bodies | `false` |
|
||||
| `RECORD_INTERACTIONS` | `--record-interactions` | Record HTTP interactions to disk | `true` |
|
||||
| `DISCOVERY_INTERVAL` | `--discovery-interval` | Device discovery interval | `5m` |
|
||||
|
||||
### Configuration Examples
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ func (s *Server) HandleProxyRequest(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
lp := proxy.NewLoggingProxy(target.String(), s.proxyRedact)
|
||||
lp.LogBody = s.proxyLogBody
|
||||
lp.RecordEnabled = s.recordEnabled
|
||||
lp.SetRecorder(s.recorder)
|
||||
|
||||
proxy := httputil.NewSingleHostReverseProxy(target)
|
||||
|
||||
@@ -351,6 +351,7 @@ func (s *Server) HandleGetProxySettings(w http.ResponseWriter, _ *http.Request)
|
||||
if err := json.NewEncoder(w).Encode(map[string]bool{
|
||||
"redact": s.proxyRedact,
|
||||
"log_body": s.proxyLogBody,
|
||||
"record": s.recordEnabled,
|
||||
}); err != nil {
|
||||
http.Error(w, "Failed to encode response", http.StatusInternalServerError)
|
||||
return
|
||||
@@ -377,6 +378,7 @@ func (s *Server) HandleUpdateProxySettings(w http.ResponseWriter, r *http.Reques
|
||||
var settings struct {
|
||||
Redact bool `json:"redact"`
|
||||
LogBody bool `json:"log_body"`
|
||||
Record bool `json:"record"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&settings); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
@@ -385,6 +387,7 @@ func (s *Server) HandleUpdateProxySettings(w http.ResponseWriter, r *http.Reques
|
||||
|
||||
s.proxyRedact = settings.Redact
|
||||
s.proxyLogBody = settings.LogBody
|
||||
s.recordEnabled = settings.Record
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
// RecordMiddleware returns a middleware that records "self" requests and responses.
|
||||
func (s *Server) RecordMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if s.recorder == nil {
|
||||
if s.recorder == nil || !s.recordEnabled {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -14,25 +14,27 @@ import (
|
||||
|
||||
// Server handles HTTP requests for the SoundTouch service.
|
||||
type Server struct {
|
||||
ds *datastore.DataStore
|
||||
sm *setup.Manager
|
||||
serverURL string
|
||||
proxyURL string
|
||||
discovering bool
|
||||
proxyRedact bool
|
||||
proxyLogBody bool
|
||||
recorder *proxy.Recorder
|
||||
ds *datastore.DataStore
|
||||
sm *setup.Manager
|
||||
serverURL string
|
||||
proxyURL string
|
||||
discovering bool
|
||||
proxyRedact bool
|
||||
proxyLogBody bool
|
||||
recordEnabled bool
|
||||
recorder *proxy.Recorder
|
||||
}
|
||||
|
||||
// NewServer creates a new SoundTouch service server.
|
||||
func NewServer(ds *datastore.DataStore, sm *setup.Manager, serverURL string, proxyRedact, proxyLogBody bool) *Server {
|
||||
func NewServer(ds *datastore.DataStore, sm *setup.Manager, serverURL string, proxyRedact, proxyLogBody, recordEnabled bool) *Server {
|
||||
return &Server{
|
||||
ds: ds,
|
||||
sm: sm,
|
||||
serverURL: serverURL,
|
||||
proxyURL: serverURL,
|
||||
proxyRedact: proxyRedact,
|
||||
proxyLogBody: proxyLogBody,
|
||||
ds: ds,
|
||||
sm: sm,
|
||||
serverURL: serverURL,
|
||||
proxyURL: serverURL,
|
||||
proxyRedact: proxyRedact,
|
||||
proxyLogBody: proxyLogBody,
|
||||
recordEnabled: recordEnabled,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +43,11 @@ func (s *Server) SetRecorder(r *proxy.Recorder) {
|
||||
s.recorder = r
|
||||
}
|
||||
|
||||
// GetRecordEnabled returns whether recording is enabled.
|
||||
func (s *Server) GetRecordEnabled() bool {
|
||||
return s.recordEnabled
|
||||
}
|
||||
|
||||
// DiscoverDevices starts a background device discovery process.
|
||||
//
|
||||
//nolint:contextcheck
|
||||
|
||||
@@ -194,6 +194,7 @@
|
||||
Proxy Logging:
|
||||
<label><input type="checkbox" id="proxy-redact" onchange="updateProxySettings()"> Redact Sensitive Headers</label>
|
||||
<label style="margin-left: 15px;"><input type="checkbox" id="proxy-log-body" onchange="updateProxySettings()"> Log Bodies</label>
|
||||
<label style="margin-left: 15px;"><input type="checkbox" id="proxy-record" onchange="updateProxySettings()"> Record Interactions</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -20,6 +20,7 @@ async function fetchProxySettings() {
|
||||
const settings = await response.json();
|
||||
document.getElementById('proxy-redact').checked = settings.redact;
|
||||
document.getElementById('proxy-log-body').checked = settings.log_body;
|
||||
document.getElementById('proxy-record').checked = settings.record;
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch proxy settings', error);
|
||||
}
|
||||
@@ -28,7 +29,8 @@ async function fetchProxySettings() {
|
||||
async function updateProxySettings() {
|
||||
const settings = {
|
||||
redact: document.getElementById('proxy-redact').checked,
|
||||
log_body: document.getElementById('proxy-log-body').checked
|
||||
log_body: document.getElementById('proxy-log-body').checked,
|
||||
record: document.getElementById('proxy-record').checked
|
||||
};
|
||||
try {
|
||||
await fetch('/setup/proxy-settings', {
|
||||
@@ -127,7 +129,7 @@ function openTab(evt, tabId) {
|
||||
if (content) {
|
||||
content.className += " active";
|
||||
}
|
||||
|
||||
|
||||
if (evt) {
|
||||
evt.currentTarget.className += " active";
|
||||
} else {
|
||||
@@ -251,13 +253,13 @@ async function updateDeviceInfo(ip) {
|
||||
if (row) {
|
||||
const nameEl = row.querySelector('.col-name');
|
||||
if (nameEl && info.name) nameEl.innerText = info.name;
|
||||
|
||||
|
||||
const modelEl = row.querySelector('.col-model');
|
||||
if (modelEl && info.type) modelEl.innerText = info.type;
|
||||
|
||||
|
||||
const serialEl = row.querySelector('.col-serial');
|
||||
if (serialEl && info.serialNumber) serialEl.innerText = info.serialNumber;
|
||||
|
||||
|
||||
const firmwareEl = row.querySelector('.col-firmware');
|
||||
if (firmwareEl && info.softwareVersion) firmwareEl.innerText = info.softwareVersion;
|
||||
}
|
||||
@@ -308,13 +310,13 @@ async function showSummary(ip) {
|
||||
if (row) {
|
||||
const nameEl = row.querySelector('.col-name');
|
||||
if (nameEl && summary.device_name) nameEl.innerText = summary.device_name;
|
||||
|
||||
|
||||
const modelEl = row.querySelector('.col-model');
|
||||
if (modelEl && summary.device_model) modelEl.innerText = summary.device_model;
|
||||
|
||||
|
||||
const serialEl = row.querySelector('.col-serial');
|
||||
if (serialEl && summary.device_serial) serialEl.innerText = summary.device_serial;
|
||||
|
||||
|
||||
const firmwareEl = row.querySelector('.col-firmware');
|
||||
if (firmwareEl && summary.firmware_version) firmwareEl.innerText = summary.firmware_version;
|
||||
}
|
||||
|
||||
@@ -20,11 +20,12 @@ var sensitiveHeaders = []string{
|
||||
|
||||
// LoggingProxy wraps a ReverseProxy to provide instrumentation.
|
||||
type LoggingProxy struct {
|
||||
Proxy *httputil.ReverseProxy
|
||||
Redact bool
|
||||
LogBody bool
|
||||
MaxBodySize int64
|
||||
Recorder *Recorder
|
||||
Proxy *httputil.ReverseProxy
|
||||
Redact bool
|
||||
LogBody bool
|
||||
RecordEnabled bool
|
||||
MaxBodySize int64
|
||||
Recorder *Recorder
|
||||
}
|
||||
|
||||
// NewLoggingProxy creates a lightweight logger for HTTP requests/responses.
|
||||
@@ -89,7 +90,7 @@ func (lp *LoggingProxy) LogResponse(r *http.Response) {
|
||||
|
||||
log.Printf("[PROXY_RES] %d %s\n Headers:\n%s\n Body: %s", r.StatusCode, r.Request.URL.String(), headers, bodyStr)
|
||||
|
||||
if lp.Recorder != nil {
|
||||
if lp.Recorder != nil && lp.RecordEnabled {
|
||||
_ = lp.Recorder.Record("upstream", r.Request, r)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user