add docker build and compose

This commit is contained in:
Florian Asche committed 2026-02-01 20:20:31 +01:00
1 parent b958678f11
commit ebd18dd234
11 files changed
+511 -12

No files matched your search

+7 -12
View File
@@ -1,15 +1,10 @@
* *
!/linux_voice_assistant/*.py
!/pyproject.toml !/pyproject.toml
!/README.md !/setup.cfg
!/script/setup !/script/*
!/script/run !/docker-entrypoint.sh
!/linux_voice_assistant/*
!/sounds/*.flac !/sounds/*
!/wakewords/*
!/wakewords/*.json !/wakewords/openWakeWord/*
!/wakewords/*.tflite
!/wakewords/openWakeWord/*.json
!/wakewords/openWakeWord/*.tflite
+51
View File
@@ -0,0 +1,51 @@
# Linux-Voice-Assistant - Docker Environment Configuration
# Copy this file to .env and customize for your setup by 'cp .env.example .env'
### Enable debug mode (optional):
# ENABLE_DEBUG="1"
### List audio devices (optional):
# if enabled normal startup is disabled
# LIST_DEVICES="1"
### User ID:
# This is used to set the correct permissions for the accessing the audio device and accessing the PulseAudio socket
USER_ID="1000"
### Name for the client (optional):
# by default it uses the HOSTNAME variable from the piCompose environment which includes the MAC from the network card
# CLIENT_NAME="My Voice Assistant Speaker"
### PulseAudio socket path on the host:
# PulseAudio Server: /run/user/1000/pulse
# Pipewire Server: /run/user/1000/pulse/native
PULSE_SERVER="unix:/run/user/${USER_ID}/pulse/native"
XDG_RUNTIME_DIR="/run/user/${USER_ID}"
### Path to the preferences file (optional):
# PREFERENCES_FILE="/app/configuration/preferences.json"
### Port for the api (optional):
# PORT="6053"
### Audio input device (optional):
# AUDIO_INPUT_DEVICE="default"
### Audio output device (optional):
# AUDIO_OUTPUT_DEVICE="default"
# Enable thinking sound (optional):
# ENABLE_THINKING_SOUND="1"
# Wake model (optional):
# WAKE-MODEL="okay_nabu"
# Refactory seconds (optional):
# REFACTORY_SECONDS="2"
# Sound files (optional):
# WAKEUP_SOUND="sounds/wake_word_triggered.flac"
# TIMER_FINISHED_SOUND="sounds/timer_finished.flac"
# PROCESSING_SOUND="sounds/processing.wav"
# MUTE_SOUND="sounds/mute_switch_on.flac"
# UNMUTE_SOUND="sounds/mute_switch_off.flac"
+77
View File
@@ -0,0 +1,77 @@
# GitHub Actions Workflows
This project uses three separate Docker build workflows that automatically build and publish Docker images to GitHub Container Registry (ghcr.io).
## Workflows Overview
| Workflow | Trigger | Image Tag(s) |
|----------|---------|--------------|
| [docker-build-release.yml](.github/workflows/docker-build-release.yml) | Tags: `v1.2.0`, `v1.2.0-alpha1` | `1.2.0`, `1.2`, `latest` |
| [docker-build-nightly.yml](.github/workflows/docker-build-nightly.yml) | Push to `main` | `nightly` |
| [docker-build-branch.yml](.github/workflows/docker-build-branch.yml) | All other branches | Branch name |
## Detailed Description
### Release Workflow (`docker-build-release.yml`)
Publishes versioned images for new releases.
**Triggers:**
- Tags matching `v[0-9]+.[0-9]+.[0-9]+` (e.g., `v1.0.0`)
- Tags matching `v[0-9]+.[0-9]+.[0-9]+-[a-zA-Z0-9]+` (e.g., `v1.0.0-alpha1`)
**Image Tags:**
```
ghcr.io/ohf-voice/linux-voice-assistant:1.0.0
ghcr.io/ohf-voice/linux-voice-assistant:1.0
ghcr.io/ohf-voice/linux-voice-assistant:1
ghcr.io/ohf-voice/linux-voice-assistant:latest
```
### Nightly Workflow (`docker-build-nightly.yml`)
Automatically builds nightly images from the main branch.
**Triggers:**
- Push to `main` branch
**Image Tag:**
```
ghcr.io/ohf-voice/linux-voice-assistant:nightly
```
### Branch Workflow (`docker-build-branch.yml`)
Builds images for all other branches.
**Triggers:**
- Push to all branches except `main`
- No tags
**Image Tag:**
```
ghcr.io/ohf-voice/linux-voice-assistant:<branch-name>
```
## Multi-Architecture Support
All workflows build for multiple architectures:
- `linux/amd64` (x86_64)
- `linux/aarch64` (ARM 64-bit, e.g., Raspberry Pi 4)
## Setup Requirements
**No additional setup needed!** The workflows use `GITHUB_TOKEN` which is automatically provided by GitHub Actions.
## Creating a Release
```bash
git tag v1.0.0
git push origin v1.0.0
```
This creates images with the following tags:
- `ghcr.io/ohf-voice/linux-voice-assistant:1.0.0`
- `ghcr.io/ohf-voice/linux-voice-assistant:1.0`
- `ghcr.io/ohf-voice/linux-voice-assistant:1`
- `ghcr.io/ohf-voice/linux-voice-assistant:latest`
+61
View File
@@ -0,0 +1,61 @@
name: Build and publish the docker image
on:
push:
branches-ignore:
- "main"
tags-ignore:
- "**"
env:
REGISTRY: ghcr.io
jobs:
build-and-push-image:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write
attestations: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Extract clean branch name
id: extract_branch
run: echo "CLEAN_BRANCH_NAME=$(echo ${{ github.head_ref || github.ref_name }} | sed 's|.*/||')" >> $GITHUB_ENV
- name: Log in to the Container registry
uses: docker/login-action@v3.1.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ github.token }}
# Build Docker Images (amd64 and arm64)
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push Docker image
id: push
uses: docker/build-push-action@v5.3.0
with:
context: .
platforms: linux/amd64,linux/aarch64
push: true
tags: |
${{ env.REGISTRY }}/${{ github.repository }}:${{ env.CLEAN_BRANCH_NAME }}
labels: |
branch=${{ env.CLEAN_BRANCH_NAME }}
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v1.4.4
with:
subject-name: ${{ env.REGISTRY }}/${{ github.repository }}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
@@ -0,0 +1,49 @@
---
name: Build and publish the docker image
on:
push:
branches:
- "main"
env:
REGISTRY: ghcr.io
jobs:
build-and-push-image:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write
attestations: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Log in to the Container registry
uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ github.token }}
# Build Docker Images (amd64 and arm64)
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push Docker image
id: push
uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0
with:
context: .
platforms: linux/amd64,linux/aarch64
push: true
tags: ${{ env.REGISTRY }}/${{ github.repository }}:nightly
labels: "nightly"
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v1.4.4
with:
subject-name: ${{ env.REGISTRY }}/${{ github.repository }}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
@@ -0,0 +1,63 @@
---
name: Build and publish the docker image
on:
push:
tags:
- "v[0-9]+.[0-9]+.[0-9]+" # For releases, e.g. v1.2.0
- "v[0-9]+.[0-9]+.[0-9]+-[a-zA-Z0-9]+" # For prereleases, e.g. v1.3.0-alpha1
env:
REGISTRY: ghcr.io
jobs:
build-and-push-image:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write
attestations: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Log in to the Container registry
uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ github.token }}
# Build Docker Images (amd64 and arm64)
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Extract metadata for Docker
id: meta
uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81 # v5.5.1
with:
images: ${{ env.REGISTRY }}/${{ github.repository }}
# E.g. tag v1.2.0 produces 3 tags: '1.2.0', '1.2', and 'latest'
# https://github.com/docker/metadata-action?tab=readme-ov-file#semver
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }}
- name: Build and push Docker image
id: push
uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0
with:
context: .
platforms: linux/amd64,linux/aarch64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
- name: Generate artifact attestation
uses: actions/attest-build-provenance@v1
with:
subject-name: ${{ env.REGISTRY }}/${{ github.repository }}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
+3
View File
@@ -18,3 +18,6 @@ venv/
/dist/ /dist/
/preferences.json /preferences.json
/local/ /local/
# Docker
.env
+65
View File
@@ -0,0 +1,65 @@
FROM python:3.13-slim-trixie
ENV LANG C.UTF-8
ENV DEBIAN_FRONTEND=noninteractive
ENV PYTHONUNBUFFERED=1
LABEL \
org.opencontainers.image.authors="Open Home Foundation" \
org.opencontainers.image.description="Voice assistant for Home Assistant" \
org.opencontainers.image.documentation="https://github.com/OHF-Voice/linux-voice-assistant/blob/main/README.md" \
org.opencontainers.image.licenses="Apache-2.0" \
org.opencontainers.image.source="https://github.com/OHF-Voice/linux-voice-assistant" \
org.opencontainers.image.title="Linux-Voice-Assistant" \
org.opencontainers.image.url="https://github.com/OHF-Voice/linux-voice-assistant"
### Install packages:
# - avahi-utils: For zeroconf/mDNS discovery by Home Assistant
# - pulseaudio-utils: Required by soundcard library for audio I/O
# - alsa-utils: ALSA tools for audio device management
# - pipewire-bin: Required for pipewire support
# - pipewire-alsa: Required for pipewire support
# - pipewire-pulse: Required for pipewire support
# - build-essential: Required to compile pymicro-features
# - libmpv-dev: Required by python-mpv for audio playback
# - libasound2-plugins: Required by python-mpv for audio playback
# - ca-certificates: For encrypted connections
# - iproute2: For ss command in entrypoint (port check)
# - procps: For pgrep in healthcheck
RUN apt-get update && \
apt-get install --yes --no-install-recommends \
avahi-utils \
pulseaudio-utils \
alsa-utils \
pipewire-bin \
pipewire-alsa \
pipewire-pulse \
build-essential \
libmpv-dev \
libasound2-plugins \
ca-certificates \
iproute2 \
procps && \
apt-get clean
### Set workdir:
WORKDIR /app
### Copy all application files:
COPY script/ ./script/
COPY pyproject.toml ./
COPY setup.cfg ./
COPY sounds/ ./sounds/
COPY ../wakewords/ ./wakewords/
COPY linux_voice_assistant/ ./linux_voice_assistant/
COPY docker-entrypoint.sh ./
### Run installation:
RUN chmod +x docker-entrypoint.sh
RUN ./script/setup
### Set ports for ESPHome API:
EXPOSE 6053
### Set start script:
ENTRYPOINT ["./docker-entrypoint.sh"]
+43
View File
@@ -0,0 +1,43 @@
---
services:
linux-voice-assistant:
container_name: linux-voice-assistant
image: "ghcr.io/ohf-voice/linux-voice-assistant:latest"
restart: unless-stopped
network_mode: "host"
user: "${USER_ID}"
environment:
# Pulseaudio
- XDG_RUNTIME_DIR=${XDG_RUNTIME_DIR}
- PULSE_SERVER=${PULSE_SERVER}
env_file: .env
group_add:
- audio
# Priority for audio process
cap_add:
- SYS_NICE
volumes:
# Wakeword data and configuration
- wakeword_data:/app/local
- configuration:/app/configuration
# Time and zone
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
# Pulseaudio
# /run/user/1000:/run/user/1000
- ${XDG_RUNTIME_DIR}:${XDG_RUNTIME_DIR}
# Health check for process
healthcheck:
test: ["CMD", "pgrep", "-f", "linux_voice_assistant"]
interval: 30s
timeout: 5s
retries: 3
start_period: 90s
#command:
# - --additional-parameters-here
# - --but-use-env-if-possible
volumes:
wakeword_data:
configuration:
+91
View File
@@ -0,0 +1,91 @@
#!/bin/bash
set -e
# Handle parameters
EXTRA_ARGS=""
if [ "$ENABLE_DEBUG" = "1" ]; then
EXTRA_ARGS="$EXTRA_ARGS --debug"
fi
CLIENT_NAME=${CLIENT_NAME:-$HOSTNAME}
if [ -n "${CLIENT_NAME}" ]; then
EXTRA_ARGS="$EXTRA_ARGS --name $CLIENT_NAME"
fi
PREFERENCES_FILE=${PREFERENCES_FILE:-"/app/configuration/preferences.json"}
if [ -n "${PREFERENCES_FILE}" ]; then
EXTRA_ARGS="$EXTRA_ARGS --preferences-file $PREFERENCES_FILE"
fi
PORT=${PORT:-6053}
if [ -n "${PORT}" ]; then
EXTRA_ARGS="$EXTRA_ARGS --port $PORT"
fi
if [ -n "${AUDIO_INPUT_DEVICE}" ]; then
EXTRA_ARGS="$EXTRA_ARGS --audio-input-device $AUDIO_INPUT_DEVICE"
fi
if [ -n "${AUDIO_OUTPUT_DEVICE}" ]; then
EXTRA_ARGS="$EXTRA_ARGS --audio-output-device $AUDIO_OUTPUT_DEVICE"
fi
if [ "$ENABLE_THINKING_SOUND" = "1" ]; then
EXTRA_ARGS="$EXTRA_ARGS --enable-thinking-sound"
fi
# Wait for PulseAudio to be available before starting the application
CP_MAX_RETRIES=30
CP_RETRY_DELAY=1
### while maybe besser?
echo "Checking port $PORT..."
for i in $(seq 1 $CP_MAX_RETRIES); do
# Check if PulseAudio is running
if pactl info >/dev/null 2>&1; then
echo "✅ PulseAudio is running (checked $CP_COUNT/$CP_MAX_RETRIES)"
break
fi
if [ $i -eq $CP_MAX_RETRIES ]; then
echo "❌ PulseAudio did not start after $CP_MAX_RETRIES seconds"
exit 2
fi
echo "⏳ PulseAudio not running yet ($CP_COUNT/$CP_MAX_RETRIES), retrying in $CP_RETRY_DELAY s..."
sleep $CP_RETRY_DELAY
done
# Check port availability
# PORT variable is used from env
PA_MAX_RETRIES=30
PA_RETRY_DELAY=2
echo "Checking port $PORT..."
for i in $(seq 1 $PA_MAX_RETRIES); do
# Wait for port to be free (in case of rapid restarts)
if ! ss -tln | grep -q ":${PORT} "; then
echo "Port $PORT is available"
break
fi
if [ $i -eq $PA_MAX_RETRIES ]; then
echo "ERROR: Port $PORT still in use after $((PA_MAX_RETRIES * PA_RETRY_DELAY)) seconds"
exit 2
fi
echo "Attempt $i/$PA_MAX_RETRIES: Port $PORT in use, waiting ${PA_RETRY_DELAY}s..."
sleep $PA_RETRY_DELAY
done
# Start application
if [ "$LIST_DEVICES" = "1" ]; then
echo "list input devices"
./script/run "$@" $EXTRA_ARGS --list-input-devices
echo "list output devices"
./script/run "$@" $EXTRA_ARGS --list-output-devices
else
echo "starting application"
exec ./script/run "$@" $EXTRA_ARGS
fi
+1
View File
@@ -101,6 +101,7 @@ async def main() -> None:
default="0.0.0.0", default="0.0.0.0",
help="Address for ESPHome server (default: 0.0.0.0)", help="Address for ESPHome server (default: 0.0.0.0)",
) )
# Note that default port is also set in docker-entrypoint.sh
parser.add_argument( parser.add_argument(
"--port", type=int, default=6053, help="Port for ESPHome server (default: 6053)" "--port", type=int, default=6053, help="Port for ESPHome server (default: 6053)"
) )