Add Docker support and CI/CD integration for SoundTouch service (#19)

This commit is contained in:
Tobias Gesellchen
2026-02-08 00:36:49 +01:00
committed by GitHub
parent 059498b16e
commit c560d399b5
8 changed files with 301 additions and 17 deletions
+33
View File
@@ -0,0 +1,33 @@
# .dockerignore
# Exclude large firmware files and archives
firmware/
data/
# Exclude local build artifacts
build/
soundtouch-cli
soundtouch-service
# Exclude Go specific files that aren't needed for build context
# (go.mod and go.sum ARE needed, but other local stuff isn't)
.cache/
vendor/
# Exclude IDE and system files
.idea/
.vscode/
.DS_Store
# Exclude Git history
.git/
.gitignore
# Exclude documentation and other non-essential files for the binary build
docs/
examples/
scripts/
CONTRIBUTING.md
CODE_OF_CONDUCT.md
LICENSE
README.md
+21
View File
@@ -77,3 +77,24 @@ updates:
- "*scan*"
- "securecodewarrior/*"
- "codecov/*"
# Docker dependency updates
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"
day: "wednesday"
time: "09:00"
timezone: "UTC"
open-pull-requests-limit: 3
reviewers:
- "gesellix"
assignees:
- "gesellix"
commit-message:
prefix: "docker"
include: "scope"
labels:
- "dependencies"
- "docker"
rebase-strategy: "auto"
+45 -2
View File
@@ -218,10 +218,51 @@ jobs:
go run test_import.go
rm test_import.go
docker:
name: Docker Build
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=raw,value=edge,enable=${{ github.ref == 'refs/heads/main' }}
type=ref,event=pr
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
push: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
notify:
name: Notify Status
runs-on: ubuntu-latest
needs: [test, lint, build, security, docs]
needs: [test, lint, build, security, docs, docker]
if: always()
permissions:
statuses: write
@@ -234,7 +275,8 @@ jobs:
"${{ needs.lint.result }}" == "success" && \
"${{ needs.build.result }}" == "success" && \
"${{ needs.security.result }}" == "success" && \
"${{ needs.docs.result }}" == "success" ]]; then
"${{ needs.docs.result }}" == "success" && \
"${{ needs.docker.result }}" == "success" ]]; then
echo "✅ All CI checks passed!"
echo "status=success" >> $GITHUB_OUTPUT
else
@@ -244,6 +286,7 @@ jobs:
echo "Build: ${{ needs.build.result }}"
echo "Security: ${{ needs.security.result }}"
echo "Docs: ${{ needs.docs.result }}"
echo "Docker: ${{ needs.docker.result }}"
echo "status=failure" >> $GITHUB_OUTPUT
fi
id: status
+43 -2
View File
@@ -13,6 +13,7 @@ on:
permissions:
contents: write
actions: read
packages: write
env:
GO_VERSION_FILE: "go.mod"
@@ -477,17 +478,57 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
docker:
name: Build and Push Docker Image
runs-on: ubuntu-latest
needs: validate
steps:
- name: Checkout code
uses: actions/checkout@v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=semver,pattern={{version}},value=v${{ needs.validate.outputs.version }}
type=semver,pattern={{major}}.{{minor}},value=v${{ needs.validate.outputs.version }}
type=raw,value=latest,enable=${{ needs.validate.outputs.is_prerelease == 'false' }}
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
notify:
name: Post-Release Notifications
runs-on: ubuntu-latest
needs: [validate, create_release, update_release]
if: always() && (needs.create_release.result == 'success' || needs.update_release.result == 'success')
needs: [validate, create_release, update_release, docker]
if: always() && (needs.create_release.result == 'success' || needs.update_release.result == 'success' || needs.docker.result == 'success')
steps:
- name: Notify success
run: |
echo "🎉 Release ${{ needs.validate.outputs.version }} completed successfully!"
echo "📦 Binaries built for 7 platforms (CLI and Service)"
echo "🐳 Docker image published to ghcr.io"
echo "🔐 Checksums generated and verified"
echo "📋 Release notes automatically generated"
echo ""
+40
View File
@@ -0,0 +1,40 @@
# Build stage
FROM golang:1.25.7-alpine AS builder
WORKDIR /app
# Copy go mod and sum files
COPY go.mod go.sum ./
RUN go mod download
# Copy the rest of the source code
COPY . .
# Build the soundtouch-service
RUN CGO_ENABLED=0 GOOS=linux go build -o /soundtouch-service ./cmd/soundtouch-service
# Final stage
FROM alpine:3.21
# Install necessary runtime dependencies
RUN apk add --no-cache ca-certificates tzdata
WORKDIR /app
# Copy the binary from the builder stage
COPY --from=builder /soundtouch-service /app/soundtouch-service
# Create data directory for persistence
RUN mkdir -p /app/data
# Set environment variables with defaults
ENV PORT=8000
ENV DATA_DIR=/app/data
ENV LOG_PROXY_BODY=false
ENV REDACT_PROXY_LOGS=true
# Expose the service port
EXPOSE 8000
# Run the service
ENTRYPOINT ["/app/soundtouch-service"]
+46 -11
View File
@@ -12,6 +12,8 @@ GOFMT=gofmt
# Build parameters
BINARY_NAME=soundtouch-cli
BINARY_PATH=./cmd/$(BINARY_NAME)
SERVICE_NAME=soundtouch-service
SERVICE_PATH=./cmd/$(SERVICE_NAME)
EXAMPLE_MDNS_NAME=example-mdns
EXAMPLE_MDNS_PATH=./cmd/$(EXAMPLE_MDNS_NAME)
EXAMPLE_UPNP_NAME=example-upnp
@@ -25,13 +27,18 @@ BUILD_DIR=./build
all: check build
build: build-cli build-examples
build: build-cli build-service build-examples
build-cli:
@echo "Building $(BINARY_NAME)..."
@mkdir -p $(BUILD_DIR)
$(GOBUILD) -o $(BUILD_DIR)/$(BINARY_NAME) $(BINARY_PATH)
build-service:
@echo "Building $(SERVICE_NAME)..."
@mkdir -p $(BUILD_DIR)
$(GOBUILD) -o $(BUILD_DIR)/$(SERVICE_NAME) $(SERVICE_PATH)
build-examples:
@echo "Building $(EXAMPLE_MDNS_NAME)..."
@mkdir -p $(BUILD_DIR)
@@ -47,17 +54,21 @@ build-linux:
@echo "Building for Linux..."
@mkdir -p $(BUILD_DIR)
GOOS=linux GOARCH=amd64 $(GOBUILD) -o $(BUILD_DIR)/$(BINARY_NAME)-linux-amd64 $(BINARY_PATH)
GOOS=linux GOARCH=amd64 $(GOBUILD) -o $(BUILD_DIR)/$(SERVICE_NAME)-linux-amd64 $(SERVICE_PATH)
build-darwin:
@echo "Building for macOS..."
@mkdir -p $(BUILD_DIR)
GOOS=darwin GOARCH=amd64 $(GOBUILD) -o $(BUILD_DIR)/$(BINARY_NAME)-darwin-amd64 $(BINARY_PATH)
GOOS=darwin GOARCH=arm64 $(GOBUILD) -o $(BUILD_DIR)/$(BINARY_NAME)-darwin-arm64 $(BINARY_PATH)
GOOS=darwin GOARCH=amd64 $(GOBUILD) -o $(BUILD_DIR)/$(SERVICE_NAME)-darwin-amd64 $(SERVICE_PATH)
GOOS=darwin GOARCH=arm64 $(GOBUILD) -o $(BUILD_DIR)/$(SERVICE_NAME)-darwin-arm64 $(SERVICE_PATH)
build-windows:
@echo "Building for Windows..."
@mkdir -p $(BUILD_DIR)
GOOS=windows GOARCH=amd64 $(GOBUILD) -o $(BUILD_DIR)/$(BINARY_NAME)-windows-amd64.exe $(BINARY_PATH)
GOOS=windows GOARCH=amd64 $(GOBUILD) -o $(BUILD_DIR)/$(SERVICE_NAME)-windows-amd64.exe $(SERVICE_PATH)
build-examples-all:
@echo "Building examples for all platforms..."
@@ -108,6 +119,18 @@ dev: build-cli
@echo "Starting development CLI..."
$(BUILD_DIR)/$(BINARY_NAME) -help
dev-service: build-service
@echo "Starting development service..."
$(BUILD_DIR)/$(SERVICE_NAME)
dev-service-proxy: build-service
@echo "Starting development service with proxy..."
@if [ -z "$(PROXY_URL)" ]; then \
echo "Usage: make dev-service-proxy PROXY_URL=http://localhost:8001"; \
exit 1; \
fi
PYTHON_BACKEND_URL=$(PROXY_URL) $(BUILD_DIR)/$(SERVICE_NAME)
dev-discover: build-cli
@echo "Running device discovery..."
$(BUILD_DIR)/$(BINARY_NAME) -discover
@@ -164,9 +187,10 @@ dev-scan-http: build-examples
@echo "Scanning for HTTP mDNS services..."
$(BUILD_DIR)/$(SCANNER_NAME) -service _http._tcp -v
install: build-cli
@echo "Installing $(BINARY_NAME) to $(GOPATH)/bin..."
install: build-cli build-service
@echo "Installing binaries to $(GOPATH)/bin..."
cp $(BUILD_DIR)/$(BINARY_NAME) $(GOPATH)/bin/
cp $(BUILD_DIR)/$(SERVICE_NAME) $(GOPATH)/bin/
clean:
@echo "Cleaning..."
@@ -177,7 +201,7 @@ clean:
release: clean check build-all
@echo "Creating release archive..."
@mkdir -p $(BUILD_DIR)/release
@for binary in $(BUILD_DIR)/$(BINARY_NAME)-*; do \
@for binary in $(BUILD_DIR)/$(BINARY_NAME)-* $(BUILD_DIR)/$(SERVICE_NAME)-*; do \
if [ -f "$$binary" ]; then \
cp "$$binary" $(BUILD_DIR)/release/; \
fi \
@@ -186,16 +210,22 @@ release: clean check build-all
docker-build:
@echo "Building Docker image..."
docker build -t soundtouch-go:$(VERSION) .
docker build -t soundtouch-service .
docker-dev: docker-build
@echo "Running development container..."
docker run --rm -it --network host soundtouch-go:$(VERSION)
docker-run-host:
@echo "Running Docker container..."
@echo "Note: --network host is used for discovery (Linux only). For macOS/Windows use port mapping."
docker run --rm -it --network host -v $$(pwd)/data:/app/data soundtouch-service
docker-run-ports:
@echo "Running Docker container with port mapping (discovery will be manual)..."
docker run --rm -it -p 8000:8000 -v $$(pwd)/data:/app/data soundtouch-service
help:
@echo "Available targets:"
@echo " build - Build the CLI tool and examples"
@echo " build - Build the CLI tool, service, and examples"
@echo " build-cli - Build only the CLI tool"
@echo " build-service - Build only the service"
@echo " build-examples - Build only the example programs"
@echo " build-all - Build for all platforms"
@echo " test - Run tests"
@@ -206,6 +236,8 @@ help:
@echo " lint - Run golangci-lint"
@echo " tidy - Tidy dependencies"
@echo " dev - Build and show CLI help"
@echo " dev-service - Build and run service locally"
@echo " dev-service-proxy - Build and run service with proxy (PROXY_URL=url required)"
@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"
@@ -217,14 +249,17 @@ help:
@echo " dev-scan-all - Scan all mDNS services on network"
@echo " dev-scan-soundtouch - Scan specifically for SoundTouch mDNS services"
@echo " dev-scan-http - Scan for HTTP mDNS services"
@echo " install - Install binary to GOPATH/bin"
@echo " install - Install binaries to GOPATH/bin"
@echo " clean - Clean build artifacts"
@echo " release - Create release binaries"
@echo " docker-build - Build Docker image"
@echo " docker-dev - Run development container"
@echo " docker-run-host - Run container with host networking (Linux discovery)"
@echo " docker-run-ports - Run container with port mapping (macOS/Windows/No discovery)"
@echo " help - Show this help message"
@echo ""
@echo "Examples:"
@echo " make dev-service"
@echo " make dev-service-proxy PROXY_URL=http://192.168.1.50:8001"
@echo " make dev-discover"
@echo " make dev-info HOST=192.168.1.10"
@echo " make dev-mdns"
+58 -2
View File
@@ -97,13 +97,69 @@ The `soundtouch-service` is a local server that emulates Bose's cloud services,
# Install the service
go install github.com/gesellix/bose-soundtouch/cmd/soundtouch-service@latest
# Start with default settings (http://localhost:8000)
# Start with default settings (http://localhost:8000, proxying to http://localhost:8001)
soundtouch-service
# Or configure with environment variables
PORT=9000 DATA_DIR=/my/data soundtouch-service
PORT=9000 PYTHON_BACKEND_URL=http://your-python-backend:8001 DATA_DIR=/my/data soundtouch-service
```
#### Running with Docker
You can also run the SoundTouch service using Docker or Docker Compose.
> **Note for macOS and Windows users**: The `--net host` option is only supported on Linux. On macOS and Windows, service discovery (mDNS, UPnP) will not work automatically within the container. You will need to manually enter your device's IP address in the management UI, and the service will communicate with it directly.
##### Using Docker
**Linux (with host networking for discovery):**
```bash
docker run -d \
--name soundtouch-service \
--network host \
-v $(pwd)/data:/app/data \
ghcr.io/gesellix/bose-soundtouch:latest
```
**macOS / Windows (with port mapping):**
```bash
docker run -d \
--name soundtouch-service \
-p 8000:8000 \
-v $(pwd)/data:/app/data \
ghcr.io/gesellix/bose-soundtouch:latest
```
##### Using Docker Compose
Create a `docker-compose.yml` file:
```yaml
services:
soundtouch-service:
image: ghcr.io/gesellix/bose-soundtouch:latest
container_name: soundtouch-service
# Linux users: use host networking for device discovery
# network_mode: host
# macOS/Windows users: use port mapping (discovery will be manual)
ports:
- "8000:8000"
environment:
- PORT=8000
- DATA_DIR=/app/data
volumes:
- ./data:/app/data
restart: unless-stopped
```
And run:
```bash
docker-compose up -d
```
> **Note**: `--network host` is required for device discovery via UPnP and mDNS to work correctly within the container.
#### Device Migration Example
```bash
+15
View File
@@ -0,0 +1,15 @@
services:
soundtouch-service:
build: .
container_name: soundtouch-service
# network_mode: host # Linux only, required for discovery
ports:
- "8000:8000"
environment:
- PORT=8000
- DATA_DIR=/app/data
- LOG_PROXY_BODY=false
- REDACT_PROXY_LOGS=true
volumes:
- ./data:/app/data
restart: unless-stopped