Files
goclaw/Dockerfile
T
Duc Nguyen 2cc9d68cdc fix(tts): config save, Edge provider, media dispatch + dark mode chat (#265)
* fix(tts): config save + Edge provider registration + dark mode chat bubbles

- Wrap TTS config payload in `raw` field for config.patch RPC (#229)
- Always register Edge TTS provider (free, no API key) instead of gating on `enabled` flag
- Fix low-contrast user message bubbles in dark mode chat

* fix(tts): skip duplicate media dispatch when temp file already delivered

When both the agent loop and the message tool dispatch the same TTS
temp file, the first dispatch succeeds and cleanup deletes it. Filter
out missing temp media files before sending to prevent "file not found"
errors and spurious error notifications on Telegram/Slack/Discord.

* feat(tts): include edge-tts in Docker image when Python enabled

Edge TTS is free (no API key) and serves as a universal TTS fallback.
Install it alongside Python in both ENABLE_PYTHON and ENABLE_FULL_SKILLS builds.

* chore(docker): expose build args from .env for compose builds

Pass ENABLE_OTEL, ENABLE_PYTHON, ENABLE_FULL_SKILLS as env-driven
build args so .env can control Docker build features without editing
docker-compose.yml directly.

* fix(tts): hot-reload TTS config on settings change via pub/sub

TTS providers were only registered at startup, so changing provider/API
key via the Web UI had no effect until container restart. Add a
tts-config-reload bus subscriber that rebuilds the TTS manager on
config changes, matching the pattern used by quota, cron, and web_fetch.
Always create a TtsTool at startup (even without providers) so the
reload subscriber can populate it when settings are first configured.

* fix(tts): protect TtsTool.UpdateManager with RWMutex to prevent data race

UpdateManager() can be called from the config reload goroutine while
Execute() reads t.manager concurrently from agent goroutines. Add
sync.RWMutex following the same pattern as WebFetchTool.UpdatePolicy().

Also update setupTTS doc comment which incorrectly stated it could
return nil — Edge TTS is now always registered.

---------

Co-authored-by: viettranx <viettranx@gmail.com>
2026-03-19 08:21:06 +07:00

108 lines
3.6 KiB
Docker

# syntax=docker/dockerfile:1
# ── Stage 1: Build ──
FROM golang:1.26-bookworm AS builder
WORKDIR /src
# Cache dependencies
COPY go.mod go.sum ./
RUN go mod download
# Copy source
COPY . .
# Build args
ARG ENABLE_OTEL=false
ARG ENABLE_TSNET=false
ARG ENABLE_REDIS=false
ARG VERSION=dev
# Build static binary (CGO disabled for scratch/alpine compatibility)
RUN set -eux; \
TAGS=""; \
if [ "$ENABLE_OTEL" = "true" ]; then TAGS="otel"; fi; \
if [ "$ENABLE_TSNET" = "true" ]; then \
if [ -n "$TAGS" ]; then TAGS="$TAGS,tsnet"; else TAGS="tsnet"; fi; \
fi; \
if [ "$ENABLE_REDIS" = "true" ]; then \
if [ -n "$TAGS" ]; then TAGS="$TAGS,redis"; else TAGS="redis"; fi; \
fi; \
if [ -n "$TAGS" ]; then TAGS="-tags $TAGS"; fi; \
CGO_ENABLED=0 GOOS=linux \
go build -ldflags="-s -w -X github.com/nextlevelbuilder/goclaw/cmd.Version=${VERSION}" \
${TAGS} -o /out/goclaw . && \
CGO_ENABLED=0 GOOS=linux \
go build -ldflags="-s -w" -o /out/pkg-helper ./cmd/pkg-helper
# ── Stage 2: Runtime ──
FROM alpine:3.22
ARG ENABLE_SANDBOX=false
ARG ENABLE_PYTHON=false
ARG ENABLE_NODE=false
ARG ENABLE_FULL_SKILLS=false
# Install ca-certificates + wget (healthcheck) + optional runtimes.
# ENABLE_FULL_SKILLS=true pre-installs all skill deps (larger image, no on-demand install needed).
# Otherwise, skill packages are installed on-demand via the admin UI.
RUN set -eux; \
apk add --no-cache ca-certificates wget su-exec; \
if [ "$ENABLE_SANDBOX" = "true" ]; then \
apk add --no-cache docker-cli; \
fi; \
if [ "$ENABLE_FULL_SKILLS" = "true" ]; then \
apk add --no-cache python3 py3-pip nodejs npm pandoc github-cli; \
pip3 install --no-cache-dir --break-system-packages \
pypdf openpyxl pandas python-pptx markitdown defusedxml lxml; \
npm install -g --cache /tmp/npm-cache docx pptxgenjs; \
rm -rf /tmp/npm-cache /root/.cache /var/cache/apk/*; \
else \
if [ "$ENABLE_PYTHON" = "true" ]; then \
apk add --no-cache python3 py3-pip; \
pip3 install --no-cache-dir --break-system-packages edge-tts; \
fi; \
if [ "$ENABLE_NODE" = "true" ]; then \
apk add --no-cache nodejs npm; \
fi; \
fi
# Non-root user
RUN adduser -D -u 1000 -h /app goclaw
WORKDIR /app
# Copy binary, migrations, and bundled skills
COPY --from=builder /out/goclaw /app/goclaw
COPY --from=builder /out/pkg-helper /app/pkg-helper
COPY --from=builder /src/migrations/ /app/migrations/
COPY --from=builder /src/skills/ /app/bundled-skills/
COPY docker-entrypoint.sh /app/docker-entrypoint.sh
RUN chmod +x /app/docker-entrypoint.sh && \
chmod 755 /app/pkg-helper && chown root:root /app/pkg-helper
# Create data directories (owned by goclaw user).
# Binaries and entrypoint stay root-owned (readable by all).
RUN mkdir -p /app/workspace /app/data /app/skills /app/tsnet-state /app/.goclaw \
&& chown -R goclaw:goclaw /app/workspace /app/data /app/skills /app/tsnet-state /app/.goclaw \
&& chown goclaw:goclaw /app/bundled-skills
# Default environment
ENV GOCLAW_CONFIG=/app/config.json \
GOCLAW_WORKSPACE=/app/workspace \
GOCLAW_DATA_DIR=/app/data \
GOCLAW_SKILLS_DIR=/app/skills \
GOCLAW_MIGRATIONS_DIR=/app/migrations \
GOCLAW_HOST=0.0.0.0 \
GOCLAW_PORT=18790
# Entrypoint runs as root to install persisted packages and start pkg-helper,
# then drops to goclaw user via su-exec before starting the app.
EXPOSE 18790
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD wget -qO- http://localhost:18790/health || exit 1
ENTRYPOINT ["/app/docker-entrypoint.sh"]
CMD ["serve"]