usability and docker launch fixes

This commit is contained in:
Liam Pettigrew
2026-02-09 17:28:53 +11:00
parent cad3d7f2c7
commit e500daa08f
10 changed files with 112 additions and 74 deletions
+1
View File
@@ -59,6 +59,7 @@ data/token.json
# Local Data and Models (mount separately)
# =============================================================================
data/models/
data/voices/
data/cache/
*.gguf
+1
View File
@@ -99,3 +99,4 @@ data/voices/private
!data/config.example.yml
!.env.example\
publish.sh
searxng_data/settings.yml.new
+11 -8
View File
@@ -2,8 +2,6 @@ FROM python:3.12-slim
WORKDIR /app
# Point HuggingFace cache at the mounted data volume so from_pretrained()
# finds models downloaded by launch.sh into data/models/hub/
ENV HF_HOME=/app/data/models
ENV HF_HUB_OFFLINE=1
@@ -15,20 +13,25 @@ RUN apt-get update && apt-get install -y \
ffmpeg \
git \
libportaudio2 \
procps \
&& rm -rf /var/lib/apt/lists/*
# Install Python dependencies (Force transformers 4.57.3 for offline Qwen3 ASR to work)
RUN pip install torch torchaudio --index-url https://download.pytorch.org/whl/cu130
RUN pip install torch torchaudio --index-url https://download.pytorch.org/whl/cpu
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt && \
pip install --no-deps git+https://github.com/rekuenkdr/Qwen3-TTS-streaming.git@97da215
# Create non-root user
RUN useradd -m -u 1000 -s /bin/bash appuser
USER appuser
# Copy application code
COPY app.py .
COPY core/ core/
COPY tools/ tools/
COPY utils/ utils/
COPY audio/ audio/
COPY --chown=appuser:appuser app.py .
COPY --chown=appuser:appuser core/ core/
COPY --chown=appuser:appuser tools/ tools/
COPY --chown=appuser:appuser utils/ utils/
COPY --chown=appuser:appuser audio/ audio/
# Run the app
CMD ["python", "app.py"]
+33 -26
View File
@@ -1,42 +1,49 @@
# Use PyTorch image with CUDA support (includes Python 3.11, PyTorch, CUDA)
FROM pytorch/pytorch:2.5.1-cuda12.4-cudnn9-devel
# Stage 1: Build compiled CUDA extensions
FROM pytorch/pytorch:2.8.0-cuda12.8-cudnn9-devel AS builder
ENV DEBIAN_FRONTEND=noninteractive
ENV CMAKE_ARGS="-DGGML_CUDA=on"
ENV FORCE_CMAKE=1
RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*
COPY requirements.txt /tmp/requirements.txt
RUN pip install --no-cache-dir -r /tmp/requirements.txt && \
pip install --no-deps git+https://github.com/rekuenkdr/Qwen3-TTS-streaming.git@97da215 && \
pip install flash-attn --no-build-isolation
# Stage 2: Runtime (no CUDA compilers/headers)
FROM pytorch/pytorch:2.8.0-cuda12.8-cudnn9-runtime
# Avoid interactive prompts
ENV DEBIAN_FRONTEND=noninteractive
# Install system dependencies
WORKDIR /app
ENV HF_HOME=/app/data/models
ENV HF_HUB_OFFLINE=1
# Install runtime system dependencies
RUN apt-get update && apt-get install -y \
git \
sox \
libsox-dev \
libsox-fmt-all \
ffmpeg \
libportaudio2 \
procps \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
# Copy Python environment with compiled packages from builder
COPY --from=builder /opt/conda /opt/conda
# Point HuggingFace cache at the mounted data volume so from_pretrained()
# finds models downloaded by launch.sh into data/models/hub/
ENV HF_HOME=/app/data/models
ENV HF_HUB_OFFLINE=1
# Set Environment Variables for llama-cpp-python CUDA build
ENV CMAKE_ARGS="-DGGML_CUDA=on"
ENV FORCE_CMAKE=1
# Install Python dependencies (Force transformers 4.57.3 for offline Qwen3 ASR to work)
RUN pip install torch torchaudio --index-url https://download.pytorch.org/whl/cu130
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt && \
pip install --no-deps git+https://github.com/rekuenkdr/Qwen3-TTS-streaming.git@97da215 && \
pip install flash-attn --no-build-isolation
# Create non-root user
RUN useradd -m -u 1000 -s /bin/bash appuser
USER appuser
# Copy application code
COPY app.py .
COPY core/ core/
COPY tools/ tools/
COPY utils/ utils/
COPY audio/ audio/
COPY --chown=appuser:appuser app.py .
COPY --chown=appuser:appuser core/ core/
COPY --chown=appuser:appuser tools/ tools/
COPY --chown=appuser:appuser utils/ utils/
COPY --chown=appuser:appuser audio/ audio/
CMD ["python", "app.py"]
+3 -4
View File
@@ -11,11 +11,12 @@ Usage:
"""
import os
import warnings
import logging
import yaml
from pathlib import Path
warnings.filterwarnings("ignore", message="Setting `pad_token_id` to `eos_token_id`")
# Suppress noisy "Setting pad_token_id to eos_token_id" from transformers
logging.getLogger("transformers.generation.utils").setLevel(logging.ERROR)
# Load configuration
with open("./data/config.yml", "r") as f:
@@ -32,8 +33,6 @@ os.environ["DO_NOT_TRACK"] = "1"
os.environ["ANONYMIZED_TELEMETRY"] = "False"
os.environ["VLLM_NO_USAGE_STATS"] = "1"
import logging
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
+12 -5
View File
@@ -2,17 +2,28 @@ services:
# Fulloch AI
app:
build:
context: .
context: .
env_file:
- .env
container_name: fulloch-ai
network_mode: host
restart: unless-stopped
devices:
- /dev/snd:/dev/snd
group_add:
- audio
environment:
- PULSE_SERVER=unix:${XDG_RUNTIME_DIR}/pulse/native
volumes:
- ./data:/app/data:rw
- ${XDG_RUNTIME_DIR}/pulse:${XDG_RUNTIME_DIR}/pulse:ro
- ${HOME}/.config/pulse/cookie:/home/appuser/.config/pulse/cookie:ro
healthcheck:
test: ["CMD-SHELL", "pgrep -f 'python app.py' || exit 1"]
interval: 30s
timeout: 5s
retries: 3
start_period: 60s
# Web Search - Searxng
searxng:
@@ -30,7 +41,3 @@ services:
- SETGID
- SETUID
- DAC_OVERRIDE
volumes:
searxng_data: {}
app_data: {}
+11 -4
View File
@@ -8,12 +8,23 @@ services:
- .env
container_name: fulloch-ai
network_mode: host
restart: unless-stopped
devices:
- /dev/snd:/dev/snd
group_add:
- audio
environment:
- PULSE_SERVER=unix:${XDG_RUNTIME_DIR}/pulse/native
volumes:
- ./data:/app/data:rw
- ${XDG_RUNTIME_DIR}/pulse:${XDG_RUNTIME_DIR}/pulse:ro
- ${HOME}/.config/pulse/cookie:/home/appuser/.config/pulse/cookie:ro
healthcheck:
test: ["CMD-SHELL", "pgrep -f 'python app.py' || exit 1"]
interval: 30s
timeout: 5s
retries: 3
start_period: 60s
deploy:
resources:
reservations:
@@ -38,7 +49,3 @@ services:
- SETGID
- SETUID
- DAC_OVERRIDE
volumes:
searxng_data: {}
app_data: {}
+31 -19
View File
@@ -74,40 +74,44 @@ echo "✅ All dependencies found."
echo "📂 Checking directory structure..."
mkdir -p "$HUB_DIR" "$GRAMMAR_DIR"
# 2a. Check for config.yml
# 2a. Check for config.yml and .env, create from templates if missing
CONFIG_FILE="$(pwd)/data/config.yml"
CONFIG_EXAMPLE="$(pwd)/data/config.example.yml"
ENV_FILE="$(pwd)/.env"
ENV_EXAMPLE="$(pwd)/.env.example"
CREATED_FILES=()
if [ ! -f "$CONFIG_FILE" ]; then
echo "📝 config.yml not found. Creating from template..."
cp "$CONFIG_EXAMPLE" "$CONFIG_FILE"
echo ""
echo "⚠️ Please edit data/config.yml with your settings before continuing."
echo " See data/config.example.yml for documentation on each option."
echo ""
echo " Run ./launch.sh again when ready."
exit 0
CREATED_FILES+=("data/config.yml")
else
echo "✅ config.yml exists."
fi
# 2b. Check for .env
ENV_FILE="$(pwd)/.env"
ENV_EXAMPLE="$(pwd)/.env.example"
if [ ! -f "$ENV_FILE" ]; then
echo "📝 .env not found. Creating from template..."
cp "$ENV_EXAMPLE" "$ENV_FILE"
echo ""
echo "⚠️ Please edit .env with your credentials before continuing."
echo " See .env.example for documentation on each variable."
echo ""
echo " Run ./launch.sh again when ready."
exit 0
CREATED_FILES+=(".env")
else
echo "✅ .env exists."
fi
if [ ${#CREATED_FILES[@]} -gt 0 ]; then
echo ""
echo "📄 Created: ${CREATED_FILES[*]}"
echo ""
read -p "Continue with defaults or exit to edit these files first? (c)ontinue / (e)xit: " response
response=${response,,}
if [[ "$response" == "e" || "$response" == "exit" ]]; then
echo ""
echo " Edit the files and run ./launch.sh again when ready."
exit 0
fi
echo "✅ Continuing with defaults."
fi
# 3. Check and Download json.gbnf
if [ ! -f "$GRAMMAR_DIR/json.gbnf" ]; then
if ask_download "json.gbnf (grammar file)"; then
@@ -187,7 +191,7 @@ else
fi
# 7. Prompt the user
read -p "Are you using a GPU? (y/n): " response
read -p "Are you using an NVIDIA GPU? (y/n): " response
response=${response,,}
if [[ "$response" == "y" || "$response" == "yes" ]]; then
COMPOSE_FILE="compose_gpu.yml"
@@ -197,6 +201,14 @@ else
echo "✅ Using default containers"
fi
# 8. Launch Docker Compose
# 8. Prepare runtime environment
# Ensure XDG_RUNTIME_DIR is set with the correct UID (compose files reference it)
export XDG_RUNTIME_DIR="${XDG_RUNTIME_DIR:-/run/user/$(id -u)}"
# Ensure PulseAudio cookie exists (PipeWire systems may not create one)
mkdir -p "${HOME}/.config/pulse"
[ -f "${HOME}/.config/pulse/cookie" ] || touch "${HOME}/.config/pulse/cookie"
# 9. Launch Docker Compose
echo "🚀 All files checked. Starting services..."
docker compose -f "$COMPOSE_FILE" up -d
+1
View File
@@ -58,6 +58,7 @@ A privacy-focused voice assistant that runs speech recognition, text-to-speech,
## Prerequisites
- Linux-based OS
- Python 3.10+
- CUDA-capable GPU (recommended) or CPU
- ~4GB disk space for models
+8 -8
View File
@@ -96,15 +96,15 @@ def external_information(query: str = "get me the latest news stories") -> str:
today = datetime.now().strftime("%B %d, %Y")
prompt = f"""
Today is {today}.
lines = [f"Today is {today}.", ""]
if website_snippets:
lines.append("A web search has retrieved the following information:")
lines.extend(website_snippets)
lines.append("")
lines.append("User question:")
lines.append(query)
{f"A web search has retrieved the following information:\n{chr(10).join(website_snippets)}" if len(website_snippets) > 0 else ""}
User question:
{query}
"""
return prompt.strip()
return "\n".join(lines)
if __name__ == "__main__":
print("Web Search")