Add version and hash to docker container (#167)
* Add version and git hash to image * Add version reporting to HA via api
This commit is contained in:
1 parent
76308cc209
commit
7de510cb1f
13 files changed
+123
-12
No files matched your search
@@ -8,3 +8,5 @@
|
|||||||
!/sounds/*
|
!/sounds/*
|
||||||
!/wakewords/*
|
!/wakewords/*
|
||||||
!/wakewords/openWakeWord/*
|
!/wakewords/openWakeWord/*
|
||||||
|
!/version.txt
|
||||||
|
!/version_githash.txt
|
||||||
@@ -44,6 +44,12 @@ jobs:
|
|||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Create version files
|
||||||
|
run: |
|
||||||
|
echo "${{ env.CLEAN_BRANCH_NAME }}" > version.txt
|
||||||
|
echo "${{ github.sha }}" > version_githash.txt
|
||||||
|
ls -ltr
|
||||||
|
|
||||||
- name: Build and push Docker image
|
- name: Build and push Docker image
|
||||||
id: push
|
id: push
|
||||||
uses: docker/build-push-action@v5.3.0
|
uses: docker/build-push-action@v5.3.0
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo "REGISTRY_LOWER=$(echo '${{ env.REGISTRY }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV
|
echo "REGISTRY_LOWER=$(echo '${{ env.REGISTRY }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV
|
||||||
echo "REPOSITORY_LOWER=$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV
|
echo "REPOSITORY_LOWER=$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Log in to the Container registry
|
- name: Log in to the Container registry
|
||||||
uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0
|
uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0
|
||||||
with:
|
with:
|
||||||
@@ -36,6 +37,13 @@ jobs:
|
|||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@v3
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Create version files
|
||||||
|
run: |
|
||||||
|
echo "nightly" > version.txt
|
||||||
|
echo "${{ github.sha }}" > version_githash.txt
|
||||||
|
ls -ltr
|
||||||
|
|
||||||
- name: Build and push Docker image
|
- name: Build and push Docker image
|
||||||
id: push
|
id: push
|
||||||
uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0
|
uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0
|
||||||
|
|||||||
@@ -45,6 +45,13 @@ jobs:
|
|||||||
type=semver,pattern={{major}}.{{minor}}
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
type=semver,pattern={{major}}
|
type=semver,pattern={{major}}
|
||||||
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }}
|
type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||||
|
|
||||||
|
- name: Create version files
|
||||||
|
run: |
|
||||||
|
echo "${{ steps.meta.outputs.version }}" > version.txt
|
||||||
|
echo "${{ github.sha }}" > version_githash.txt
|
||||||
|
ls -ltr
|
||||||
|
|
||||||
- name: Build and push Docker image
|
- name: Build and push Docker image
|
||||||
id: push
|
id: push
|
||||||
uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0
|
uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0
|
||||||
|
|||||||
@@ -21,3 +21,5 @@ venv/
|
|||||||
|
|
||||||
# Docker
|
# Docker
|
||||||
.env
|
.env
|
||||||
|
version.txt
|
||||||
|
version_githash.txt
|
||||||
@@ -54,6 +54,8 @@ COPY sounds/ ./sounds/
|
|||||||
COPY ../wakewords/ ./wakewords/
|
COPY ../wakewords/ ./wakewords/
|
||||||
COPY linux_voice_assistant/ ./linux_voice_assistant/
|
COPY linux_voice_assistant/ ./linux_voice_assistant/
|
||||||
COPY docker-entrypoint.sh ./
|
COPY docker-entrypoint.sh ./
|
||||||
|
COPY version.txt ./
|
||||||
|
COPY version_githash.txt ./
|
||||||
|
|
||||||
### Run installation:
|
### Run installation:
|
||||||
RUN chmod +x docker-entrypoint.sh
|
RUN chmod +x docker-entrypoint.sh
|
||||||
|
|||||||
@@ -47,6 +47,8 @@ services:
|
|||||||
- ${LVA_XDG_RUNTIME_DIR}:${LVA_XDG_RUNTIME_DIR}
|
- ${LVA_XDG_RUNTIME_DIR}:${LVA_XDG_RUNTIME_DIR}
|
||||||
# Mount source code for development
|
# Mount source code for development
|
||||||
- ./linux_voice_assistant:/app/linux_voice_assistant
|
- ./linux_voice_assistant:/app/linux_voice_assistant
|
||||||
|
- ./version.txt:/app/version.txt
|
||||||
|
- ./version_githash.txt:/app/version_githash.txt
|
||||||
depends_on:
|
depends_on:
|
||||||
- fix-permissions
|
- fix-permissions
|
||||||
# Health check for process
|
# Health check for process
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ from getmac import get_mac_address
|
|||||||
from .zeroconf import HomeAssistantZeroconf
|
from .zeroconf import HomeAssistantZeroconf
|
||||||
from .util import get_default_interface
|
from .util import get_default_interface
|
||||||
from .util import get_default_ipv4
|
from .util import get_default_ipv4
|
||||||
|
from .util import get_version
|
||||||
|
from .util import get_esphome_version
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
_MODULE_DIR = Path(__file__).parent
|
_MODULE_DIR = Path(__file__).parent
|
||||||
@@ -183,13 +185,25 @@ async def main() -> None:
|
|||||||
|
|
||||||
# Resolve name
|
# Resolve name
|
||||||
if not args.name:
|
if not args.name:
|
||||||
print("No name specified, try to autogenerate name")
|
print("No friendly name specified, try to autogenerate name")
|
||||||
device_name = f"lva-{mac_address_clean}"
|
friendly_name = f"LVA - {mac_address_clean}"
|
||||||
print(f"Name autogenerated: {device_name}")
|
print(f"Friendly name autogenerated: {friendly_name}")
|
||||||
else:
|
else:
|
||||||
print("Name specified")
|
print("Friendly name specified")
|
||||||
print(f"Using name: {args.name}")
|
print(f"Using friendly name: {args.name}")
|
||||||
device_name = args.name
|
friendly_name = args.name
|
||||||
|
|
||||||
|
mac_no_colon = mac_address.replace(":", "").lower()
|
||||||
|
device_name = f"lva-{mac_no_colon}"
|
||||||
|
print(f"Device name: {device_name}")
|
||||||
|
|
||||||
|
# Resolve version
|
||||||
|
version = get_version()
|
||||||
|
print(f"Version: {version}")
|
||||||
|
|
||||||
|
# Resolve esphome version
|
||||||
|
esphome_version = get_esphome_version()
|
||||||
|
print(f"ESPHome api version: {esphome_version}")
|
||||||
|
|
||||||
# Resolve download dir
|
# Resolve download dir
|
||||||
args.download_dir = Path(args.download_dir)
|
args.download_dir = Path(args.download_dir)
|
||||||
@@ -288,9 +302,12 @@ async def main() -> None:
|
|||||||
|
|
||||||
state = ServerState(
|
state = ServerState(
|
||||||
name=device_name,
|
name=device_name,
|
||||||
|
friendly_name=friendly_name,
|
||||||
network_interface=network_interface,
|
network_interface=network_interface,
|
||||||
mac_address=get_mac_address(interface=network_interface),
|
mac_address=get_mac_address(interface=network_interface),
|
||||||
ip_address=host_ip_address,
|
ip_address=host_ip_address,
|
||||||
|
version=version,
|
||||||
|
esphome_version=esphome_version,
|
||||||
audio_queue=Queue(),
|
audio_queue=Queue(),
|
||||||
entities=[],
|
entities=[],
|
||||||
available_wake_words=available_wake_words,
|
available_wake_words=available_wake_words,
|
||||||
|
|||||||
@@ -63,9 +63,12 @@ class Preferences:
|
|||||||
@dataclass
|
@dataclass
|
||||||
class ServerState:
|
class ServerState:
|
||||||
name: str
|
name: str
|
||||||
|
friendly_name: str
|
||||||
mac_address: str
|
mac_address: str
|
||||||
ip_address: str
|
ip_address: str
|
||||||
network_interface: str
|
network_interface: str
|
||||||
|
version: str
|
||||||
|
esphome_version: str
|
||||||
audio_queue: "Queue[Optional[bytes]]"
|
audio_queue: "Queue[Optional[bytes]]"
|
||||||
entities: "List[ESPHomeEntity]"
|
entities: "List[ESPHomeEntity]"
|
||||||
available_wake_words: "Dict[str, AvailableWakeWord]"
|
available_wake_words: "Dict[str, AvailableWakeWord]"
|
||||||
|
|||||||
@@ -291,15 +291,15 @@ class VoiceSatelliteProtocol(APIServer):
|
|||||||
elif isinstance(msg, VoiceAssistantTimerEventResponse):
|
elif isinstance(msg, VoiceAssistantTimerEventResponse):
|
||||||
self.handle_timer_event(VoiceAssistantTimerEventType(msg.event_type), msg)
|
self.handle_timer_event(VoiceAssistantTimerEventType(msg.event_type), msg)
|
||||||
elif isinstance(msg, DeviceInfoRequest):
|
elif isinstance(msg, DeviceInfoRequest):
|
||||||
# Compute dynamic device name
|
_LOGGER.debug("Device info request")
|
||||||
base_name = re.sub(r"[\s-]+", "-", self.state.name.lower()).strip("-")
|
|
||||||
mac_no_colon = self.state.mac_address.replace(":", "").lower()
|
|
||||||
mac_last6 = mac_no_colon[-6:]
|
|
||||||
device_name = f"{base_name}-{mac_last6}"
|
|
||||||
|
|
||||||
yield DeviceInfoResponse(
|
yield DeviceInfoResponse(
|
||||||
uses_password=False,
|
uses_password=False,
|
||||||
name=device_name,
|
name=self.state.name,
|
||||||
|
friendly_name=self.state.friendly_name,
|
||||||
|
project_name="Open Home Foundation.Linux Voice Assistant",
|
||||||
|
project_version=self.state.version,
|
||||||
|
esphome_version=self.state.esphome_version,
|
||||||
mac_address=self.state.mac_address,
|
mac_address=self.state.mac_address,
|
||||||
manufacturer="Open Home Foundation",
|
manufacturer="Open Home Foundation",
|
||||||
model="Linux Voice Assistant",
|
model="Linux Voice Assistant",
|
||||||
|
|||||||
@@ -4,8 +4,68 @@ import uuid
|
|||||||
# netifaces lib is from netifaces2
|
# netifaces lib is from netifaces2
|
||||||
import netifaces
|
import netifaces
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable
|
||||||
|
from importlib.metadata import version, PackageNotFoundError
|
||||||
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
# Cache for version to avoid repeated file reading
|
||||||
|
_version_cache: Optional[str] = None
|
||||||
|
_esphome_version_cache: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
def get_version() -> str:
|
||||||
|
"""
|
||||||
|
Read the version from version.txt file.
|
||||||
|
|
||||||
|
This function reads the content safely without risk of code injection,
|
||||||
|
as it only reads raw text and performs no evaluation.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: The version from version.txt or 'unknown' if the file
|
||||||
|
does not exist or cannot be read.
|
||||||
|
"""
|
||||||
|
global _version_cache
|
||||||
|
|
||||||
|
if _version_cache is not None:
|
||||||
|
return _version_cache
|
||||||
|
|
||||||
|
version_file = Path(__file__).parent.parent / "version.txt"
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Sicher lesen: nur Rohtext, keine Evaluierung
|
||||||
|
version = version_file.read_text(encoding="utf-8").strip()
|
||||||
|
_version_cache = version if version else "unknown"
|
||||||
|
except (FileNotFoundError, PermissionError, OSError):
|
||||||
|
_version_cache = "unknown"
|
||||||
|
|
||||||
|
return _version_cache
|
||||||
|
|
||||||
|
|
||||||
|
def get_esphome_version() -> str:
|
||||||
|
"""
|
||||||
|
Read the version of the installed aioesphomeapi package.
|
||||||
|
|
||||||
|
This function uses importlib.metadata to safely retrieve the version
|
||||||
|
of an installed Python package without executing any code from the
|
||||||
|
package itself.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: The version of aioesphomeapi (e.g., '42.7.0'), or 'unknown'
|
||||||
|
if the package is not installed or the version cannot be read.
|
||||||
|
"""
|
||||||
|
global _esphome_version_cache
|
||||||
|
|
||||||
|
if _esphome_version_cache is not None:
|
||||||
|
return _esphome_version_cache
|
||||||
|
|
||||||
|
try:
|
||||||
|
_esphome_version_cache = version("aioesphomeapi")
|
||||||
|
except PackageNotFoundError:
|
||||||
|
_esphome_version_cache = "unknown"
|
||||||
|
|
||||||
|
return _esphome_version_cache
|
||||||
|
|
||||||
|
|
||||||
def call_all(*callables: Optional[Callable[[], None]]) -> None:
|
def call_all(*callables: Optional[Callable[[], None]]) -> None:
|
||||||
for item in filter(None, callables):
|
for item in filter(None, callables):
|
||||||
item()
|
item()
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
placeholder
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
placeholder
|
||||||
Reference in new issue
Block a user