Skip to content

Commit 4ccf5aa

Browse files
committed
V2.2.0: Big release, OpenAI endpointing and CPU tier returns
1 parent 0d4661e commit 4ccf5aa

129 files changed

Lines changed: 11044 additions & 1024 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.dockerignore

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,11 @@ Thumbs.db
8484
Makefile
8585
docker-compose*.yml
8686
compose*.yml
87+
88+
# =============================================================================
89+
# Re-include first-run seed files the Dockerfiles COPY into /app/seed
90+
# (must come AFTER the broad excludes above — last match wins)
91+
# =============================================================================
92+
!data/config.example.yml
93+
!data/models/grammars/
94+
!data/models/grammars/agent.gbnf

.env.example

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
# Fulloch Environment Variables
22
# Copy to .env and fill in your values; loaded automatically via python-dotenv.
3+
# The whole file is OPTIONAL — `docker compose up` works without it (sensible
4+
# defaults apply). Create it to set the values below.
35

46
# =============================================================================
57
# Web Search (SearXNG)
68
# =============================================================================
79
# Overrides server.secret_key in searxng_data/settings.yml at runtime.
8-
# Required by compose.yml. Generate with: openssl rand -hex 32
10+
# Optional — compose falls back to a placeholder for local use. Set a real value
11+
# before exposing SearXNG beyond localhost. Generate with: openssl rand -hex 32
912
SEARXNG_SECRET=
1013

1114
# =============================================================================
@@ -17,6 +20,17 @@ SEARXNG_SECRET=
1720
# openssl rand -hex 32. (HTTPS is separate — dashboard_ssl_* in config.yml.)
1821
FULLOCH_DASHBOARD_TOKEN=
1922

23+
# =============================================================================
24+
# Remote LLM / Parloch mode (optional — only for models.llm.backend: openai)
25+
# =============================================================================
26+
# When the language model runs off-device on an OpenAI-compatible endpoint, the
27+
# endpoint (models.llm.base_url) and model (models.llm.model) live in config.yml;
28+
# only the API key belongs here, to keep the secret out of committed config.
29+
# OPENAI_API_KEY is used as a fallback; if neither is set the client sends
30+
# "not-needed" (fine for local servers that ignore it).
31+
FULLOCH_LLM_API_KEY=
32+
# OPENAI_API_KEY=
33+
2034
# =============================================================================
2135
# Container healthcheck port (optional)
2236
# =============================================================================

.github/workflows/release.yml

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
name: Release images
2+
3+
# Tag-triggered build: the slow CUDA + flash-attn compile happens once here and
4+
# is pushed to GHCR, so users `docker pull` instead of building. Two variants:
5+
# - GPU (default): ghcr.io/<owner>/fulloch:<version> + :latest
6+
# - CPU (slim): ghcr.io/<owner>/fulloch:<version>-cpu + :latest-cpu + :cpu
7+
# Weights stay out of the images (the first-run wizard downloads them).
8+
on:
9+
push:
10+
tags: ["v*"]
11+
workflow_dispatch: # allow a manual build of the current ref
12+
13+
permissions:
14+
contents: read
15+
packages: write
16+
17+
jobs:
18+
build-push:
19+
runs-on: ubuntu-latest
20+
strategy:
21+
fail-fast: false
22+
matrix:
23+
include:
24+
- variant: gpu
25+
dockerfile: Dockerfile
26+
suffix: ""
27+
- variant: cpu
28+
dockerfile: Dockerfile.cpu
29+
suffix: "-cpu"
30+
steps:
31+
- uses: actions/checkout@v4
32+
33+
# The CUDA devel base + flash-attn build is large; reclaim runner disk so
34+
# the GPU build doesn't run out of space (cheap no-op for the CPU build).
35+
- name: Free disk space
36+
run: |
37+
sudo rm -rf /usr/share/dotnet /usr/local/lib/android /opt/ghc \
38+
/opt/hostedtoolcache/CodeQL
39+
sudo docker image prune --all --force || true
40+
df -h
41+
42+
- uses: docker/setup-buildx-action@v3
43+
44+
- name: Log in to GHCR
45+
uses: docker/login-action@v3
46+
with:
47+
registry: ghcr.io
48+
username: ${{ github.actor }}
49+
password: ${{ secrets.GITHUB_TOKEN }}
50+
51+
- name: Image metadata
52+
id: meta
53+
uses: docker/metadata-action@v5
54+
with:
55+
images: ghcr.io/${{ github.repository_owner }}/fulloch
56+
flavor: |
57+
latest=false
58+
suffix=${{ matrix.suffix }}
59+
tags: |
60+
type=semver,pattern={{version}}
61+
type=raw,value=latest
62+
# Short, suffix-free alias for the CPU image (README/run docs pull
63+
# `:cpu`). suffix= overrides the global `-cpu` flavor so it lands as
64+
# exactly `:cpu`, not `:cpu-cpu`; only emitted on the CPU matrix leg.
65+
type=raw,value=cpu,suffix=,enable=${{ matrix.variant == 'cpu' }}
66+
67+
- name: Build and push (${{ matrix.variant }})
68+
uses: docker/build-push-action@v6
69+
with:
70+
context: .
71+
file: ${{ matrix.dockerfile }}
72+
push: true
73+
tags: ${{ steps.meta.outputs.tags }}
74+
labels: ${{ steps.meta.outputs.labels }}
75+
cache-from: type=gha,scope=${{ matrix.variant }}
76+
cache-to: type=gha,mode=max,scope=${{ matrix.variant }}

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ Thumbs.db
100100
0ld/
101101
dev/
102102
data/voices/private
103+
scripts/gen_kokoro_demos.py
103104

104105
# =============================================================================
105106
# Keep Example Configs (negation patterns)

CONTRIBUTING.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@ Thank you for your interest in contributing to Fulloch! This document provides g
1010
```bash
1111
python -m venv .venv
1212
source .venv/bin/activate # On Windows: .venv\Scripts\activate
13-
pip install -r requirements.txt
13+
pip install -r requirements.txt # full GPU stack (tiny/CPU set: requirements-cpu.txt)
1414
# Install special packages (see requirements.txt for details)
1515
pip install --no-deps git+https://github.com/liampetti/Qwen3-TTS-streaming.git@97da215
1616
pip install --no-build-isolation --no-deps git+https://github.com/Dao-AILab/flash-attention.git@ef9e6a6
1717
pip install -e ".[dev]" # Install dev dependencies
1818
```
19+
The test suite stubs the heavy GPU stack, so `pip install -r requirements-ci.txt`
20+
is enough to run `pytest` (no GPU needed).
1921
4. Copy configuration files:
2022
```bash
2123
cp data/config.example.yml data/config.yml

Dockerfile

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,18 @@ FROM pytorch/pytorch:2.8.0-cuda12.8-cudnn9-devel AS builder
44
ENV DEBIAN_FRONTEND=noninteractive
55
ENV CMAKE_ARGS="-DGGML_CUDA=on"
66
ENV FORCE_CMAKE=1
7+
# Cap flash-attn's parallel nvcc jobs so the build doesn't exhaust RAM on small
8+
# CI runners (each job needs a few GB). Override with --build-arg MAX_JOBS=N.
9+
ARG MAX_JOBS=4
10+
ENV MAX_JOBS=${MAX_JOBS}
711

812
RUN apt-get update && apt-get install -y git && rm -rf /var/lib/apt/lists/*
913

1014
COPY requirements.txt /tmp/requirements.txt
1115
RUN pip install --no-cache-dir -r /tmp/requirements.txt && \
1216
pip install --no-deps git+https://github.com/liampetti/Qwen3-TTS-streaming.git@97da215 && \
13-
pip install flash-attn --no-build-isolation
17+
pip install flash-attn --no-build-isolation && \
18+
python -m spacy download en_core_web_sm
1419

1520
# Stage 2: Runtime (no CUDA compilers/headers)
1621
FROM pytorch/pytorch:2.8.0-cuda12.8-cudnn9-runtime
@@ -20,7 +25,10 @@ ENV DEBIAN_FRONTEND=noninteractive
2025
WORKDIR /app
2126

2227
ENV HF_HOME=/app/data/models
23-
ENV HF_HUB_OFFLINE=1
28+
# HF_HUB_OFFLINE is managed at runtime by app.py (online for the first-run
29+
# wizard download, offline once models are cached) — not pinned here.
30+
# Image variant — the wizard offers all tiers on the GPU image.
31+
ENV FULLOCH_VARIANT=gpu
2432

2533
# Install runtime system dependencies
2634
# - gcc: needed by Triton JIT for TTS kernels
@@ -33,6 +41,7 @@ RUN apt-get update && apt-get install -y \
3341
libsox-dev \
3442
libsox-fmt-all \
3543
ffmpeg \
44+
espeak-ng \
3645
libportaudio2 \
3746
libasound2-plugins \
3847
libpulse0 \
@@ -52,12 +61,21 @@ RUN useradd -m -u 1000 -s /bin/bash appuser
5261
USER appuser
5362

5463
# Copy application code
55-
COPY --chown=appuser:appuser app.py fulloch.png ./
64+
COPY --chown=appuser:appuser app.py fulloch.png parloch.png ./
5665
COPY --chown=appuser:appuser core/ core/
5766
COPY --chown=appuser:appuser tools/ tools/
5867
COPY --chown=appuser:appuser utils/ utils/
5968
COPY --chown=appuser:appuser audio/ audio/
6069
COPY --chown=appuser:appuser server/ server/
6170
COPY --chown=appuser:appuser wav/ wav/
6271

72+
# First-run seeds (copied into the empty ./data volume by core/bootstrap.py):
73+
# the config template the wizard fills in, and the app's own GBNF grammar (which
74+
# the wizard's downloader can't fetch). Weights stay OUT of the image — the
75+
# wizard pulls them on first run.
76+
COPY --chown=appuser:appuser data/config.example.yml /app/seed/config.example.yml
77+
COPY --chown=appuser:appuser data/models/grammars/agent.gbnf /app/seed/grammars/agent.gbnf
78+
79+
# Bootstrap + setup-or-run is handled in app.main() (core/bootstrap.py): an empty
80+
# ./data boots into the setup wizard, a populated one straight to the assistant.
6381
CMD ["python", "app.py"]

Dockerfile.cpu

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Fulloch — CPU image (slim "tiny + remote" variant).
2+
#
3+
# No CUDA, no flash-attn, no llama-cpp, no Qwen speech. Ships Moonshine ASR +
4+
# Kokoro TTS (both CPU) and the regex-only / remote-OpenAI LLM paths, so a
5+
# low-power / no-GPU host can run the tiny tier or offload reasoning to a remote
6+
# endpoint. Much smaller than the GPU image. The wizard hides GPU-only backends
7+
# (FULLOCH_VARIANT=cpu).
8+
FROM python:3.11-slim
9+
10+
ENV DEBIAN_FRONTEND=noninteractive
11+
WORKDIR /app
12+
13+
ENV HF_HOME=/app/data/models
14+
# HF_HUB_OFFLINE is managed at runtime by app.py (online for the first-run
15+
# wizard download, offline once models are cached) — not pinned here.
16+
# Image variant — the wizard offers only the CPU-runnable backends/tiers.
17+
ENV FULLOCH_VARIANT=cpu
18+
19+
# Runtime system deps:
20+
# - espeak-ng: Kokoro TTS grapheme-to-phoneme
21+
# - libsndfile1: soundfile I/O; ffmpeg: audio decode
22+
# - libportaudio2: PortAudio (sounddevice); libasound2-plugins + libpulse0:
23+
# ALSA->PulseAudio bridge for host audio passthrough
24+
# - gcc: a couple of wheels JIT/compile at runtime
25+
RUN apt-get update && apt-get install -y \
26+
gcc \
27+
ffmpeg \
28+
espeak-ng \
29+
libsndfile1 \
30+
libportaudio2 \
31+
libasound2-plugins \
32+
libpulse0 \
33+
procps \
34+
curl \
35+
&& rm -rf /var/lib/apt/lists/*
36+
37+
# Route ALSA's default device through PulseAudio (same as the GPU image).
38+
RUN printf 'pcm.!default { type pulse }\nctl.!default { type pulse }\n' > /etc/asound.conf
39+
40+
# CPU PyTorch from the CPU wheel index (keeps the CUDA build out), then the
41+
# CPU dependency set.
42+
RUN pip install --no-cache-dir torch==2.8.0 torchaudio==2.8.0 \
43+
--index-url https://download.pytorch.org/whl/cpu
44+
COPY requirements-cpu.txt /tmp/requirements-cpu.txt
45+
RUN pip install --no-cache-dir -r /tmp/requirements-cpu.txt
46+
RUN python -m spacy download en_core_web_sm
47+
48+
# Non-root user
49+
RUN useradd -m -u 1000 -s /bin/bash appuser
50+
USER appuser
51+
52+
# Application code
53+
COPY --chown=appuser:appuser app.py fulloch.png parloch.png ./
54+
COPY --chown=appuser:appuser core/ core/
55+
COPY --chown=appuser:appuser tools/ tools/
56+
COPY --chown=appuser:appuser utils/ utils/
57+
COPY --chown=appuser:appuser audio/ audio/
58+
COPY --chown=appuser:appuser server/ server/
59+
COPY --chown=appuser:appuser wav/ wav/
60+
61+
# First-run seeds (copied into the empty ./data volume by core/bootstrap.py).
62+
COPY --chown=appuser:appuser data/config.example.yml /app/seed/config.example.yml
63+
COPY --chown=appuser:appuser data/models/grammars/agent.gbnf /app/seed/grammars/agent.gbnf
64+
65+
CMD ["python", "app.py"]

0 commit comments

Comments
 (0)