Compare commits

..

1 Commits

Author SHA1 Message Date
Thierry Pouplier
b5030df475 feat(hermes): add chromium for local browser tool
- Create custom Dockerfile extending nousresearch/hermes-agent with chromium installed
- Update compose.yml to build from local Dockerfile instead of pulling image
- Enables browser tool to work without Browserbase cloud service
2026-04-30 00:10:41 +00:00
13 changed files with 44 additions and 697 deletions

View File

@@ -1,31 +0,0 @@
name: Build Hermes agent
on:
pull_request:
branches: [ master ]
paths:
- 'ai/hermes/**'
- 'ai/compose.yml'
push:
branches: [ master ]
paths:
- 'ai/hermes/**'
- 'ai/compose.yml'
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
run: |
git clone -b "${{ github.head_ref || github.ref_name }}" \
https://gitea:${{ secrets.GITHUB_TOKEN }}@code.lazyworkhorse.net/gortium/compose.git .
git log --oneline -3
- name: Build hermes image
run: |
cd ai
docker compose build hermes 2>&1
- name: Verify image
run: |
docker run --rm ai-hermes /opt/hermes/.venv/bin/python --version 2>&1

View File

@@ -1,31 +0,0 @@
name: Build ollama (gfx906)
on:
pull_request:
branches: [ master ]
paths:
- 'ai/ollama/**'
- 'ai/compose.yml'
push:
branches: [ master ]
paths:
- 'ai/ollama/**'
- 'ai/compose.yml'
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
run: |
git clone -b "${{ github.head_ref || github.ref_name }}" \
https://gitea:${{ secrets.GITHUB_TOKEN }}@code.lazyworkhorse.net/gortium/compose.git .
git log --oneline -3
- name: Build ollama image
run: |
cd ai
docker compose build ollama --no-cache 2>&1
- name: Verify version
run: |
docker run --rm ollama/ollama:rocm-gfx906 ollama --version 2>&1

View File

@@ -1,39 +1,35 @@
version: "3.8"
services:
# webui:
# image: ghcr.io/open-webui/open-webui:main
# volumes:
# - /mnt/HoardingCow_docker_data/Ollama/open-webui:/app/backend/data
# restart: always
# environment:
# - OLLAMA_API_BASE_URL=http://ollama:11434/api
# networks:
# - ai_net
# - ai_backend
# labels:
# - "traefik.enable=true"
webui:
image: ghcr.io/open-webui/open-webui:main
volumes:
- /mnt/HoardingCow_docker_data/Ollama/open-webui:/app/backend/data
restart: always
environment:
- OLLAMA_API_BASE_URL=http://ollama:11434/api
networks:
- ai_net
- ai_backend
labels:
- "traefik.enable=true"
# # Router for HTTP + redirection to HTTPS
# - "traefik.http.routers.webui-http.rule=Host(`ai.lazyworkhorse.net`)"
# - "traefik.http.routers.webui-http.entrypoints=web"
# - "traefik.http.routers.webui-http.middlewares=redirect-to-https"
# Router for HTTP + redirection to HTTPS
- "traefik.http.routers.webui-http.rule=Host(`ai.lazyworkhorse.net`)"
- "traefik.http.routers.webui-http.entrypoints=web"
- "traefik.http.routers.webui-http.middlewares=redirect-to-https"
# # Router for HTTPS with TLS
# - "traefik.http.routers.webui-https.rule=Host(`ai.lazyworkhorse.net`)"
# - "traefik.http.routers.webui-https.entrypoints=websecure"
# - "traefik.http.routers.webui-https.tls=true"
# - "traefik.http.routers.webui-https.tls.certresolver=njalla"
# Router for HTTPS with TLS
- "traefik.http.routers.webui-https.rule=Host(`ai.lazyworkhorse.net`)"
- "traefik.http.routers.webui-https.entrypoints=websecure"
- "traefik.http.routers.webui-https.tls=true"
- "traefik.http.routers.webui-https.tls.certresolver=njalla"
hermes:
build:
context: ./hermes
ssh:
- default
dockerfile: Dockerfile
container_name: hermes
entrypoint: ["/bin/bash", "-c",
"bash /opt/data/hermes-tools/install.sh && exec /usr/bin/tini -g -- /opt/hermes/docker/entrypoint.sh \"$@\"",
"hermes-entrypoint"]
restart: always
# Gateway run enables the internal API server on port 8642
command: gateway run
@@ -45,19 +41,8 @@ services:
- API_SERVER_KEY=hermes_local_key
- GATEWAY_ALLOW_ALL_USERS=true
- OPENROUTER_API_KEY=${OPENROUTER_API_KEY}
# ROCm for GPU-accelerated faster-whisper STT
- HSA_OVERRIDE_GFX_VERSION=9.0.6
- HCC_AMDGPU_TARGET=gfx906
- HIP_VISIBLE_DEVICES=0,1
- ROCR_VISIBLE_DEVICES=0,1
- HSA_ENABLE_SDMA=0
- TZ=America/Montreal
volumes:
- /mnt/HoardingCow_docker_data/Hermes/data:/opt/data
# Syncthing-shared org files — read-only view of user's agenda
- /mnt/HoardingCow_docker_data/Syncthing/telos-ro:/opt/data/telos-ro:ro
# Syncthing-shared inbox — write tasks here, they sync to user's laptop
- /mnt/HoardingCow_docker_data/Syncthing/telos-rw:/opt/data/telos-rw:rw
devices:
- /dev/kfd:/dev/kfd
- /dev/dri:/dev/dri
@@ -67,41 +52,10 @@ services:
networks:
- ai_backend
syncthing:
image: syncthing/syncthing:latest
container_name: syncthing
hostname: syncthing
restart: always
ports:
- "8384:8384"
- "22000:22000"
- "21027:21027/udp"
environment:
- TZ=America/Montreal
volumes:
- /mnt/HoardingCow_docker_data/Syncthing/config:/var/syncthing/config
- /mnt/HoardingCow_docker_data/Syncthing/telos-ro:/telos-ro
- /mnt/HoardingCow_docker_data/Syncthing/telos-rw:/telos-rw
networks:
- ai_backend
- ai_net
labels:
- "traefik.enable=true"
- "traefik.http.routers.syncthing-http.rule=Host(`syncthing.lazyworkhorse.net`)"
- "traefik.http.routers.syncthing-http.entrypoints=web"
- "traefik.http.routers.syncthing-http.middlewares=redirect-to-https"
- "traefik.http.routers.syncthing-https.rule=Host(`syncthing.lazyworkhorse.net`)"
- "traefik.http.routers.syncthing-https.entrypoints=websecure"
- "traefik.http.routers.syncthing-https.tls=true"
- "traefik.http.routers.syncthing-https.tls.certresolver=njalla"
- "traefik.http.services.syncthing.loadbalancer.server.port=8384"
ollama:
build:
context: ./ollama
dockerfile: Dockerfile
image: ollama/ollama:rocm-gfx906
image: ollama/ollama:latest
container_name: ollama
privileged: true
tty: true
restart: always
ports:
@@ -119,7 +73,7 @@ services:
- HSA_ENABLE_SDMA=0
- OLLAMA_HOST=0.0.0.0
- OLLAMA_DEBUG=1
- OLLAMA_FLASH_ATTENTION=1
- OLLAMA_FLASH_ATTENTION=0
- OLLAMA_NUM_PARALLEL=2
devices:
# Map the render nodes and KFD for ROCm to work inside the container

View File

@@ -1,106 +1,10 @@
# syntax=docker/dockerfile:1
# Hermes Agent with Chromium -- local browser tool support
# Based on python:3.11-slim for minimal footprint.
# Chromium installed via apt-get for system-level browser automation.
#
# Build:
# docker build -t hermes-agent:chromium .
#
# Environment variables:
# CHROME_EXECUTABLE -- path to the Chromium binary
FROM nousresearch/hermes-agent:latest
# ---------- Base image ----------
FROM python:3.11-slim
# Install Chromium for browser tool
RUN apt-get update && apt-get install -y --no-install-recommends \
chromium \
&& rm -rf /var/lib/apt/lists/* \
&& apt-get clean
ENV DEBIAN_FRONTEND=noninteractive
ENV PYTHONUNBUFFERED=1
# ---------- System dependencies for Chromium ----------
# The minimum set required to run headless Chromium on Linux.
# python:3.11-slim is Debian Bookworm (12) -- package names without t64 suffix.
RUN apt-get update && \
apt-get install -y --no-install-recommends \
# Chromium and its launcher
chromium \
chromium-common \
chromium-sandbox \
# Font rendering for proper page rendering
fonts-liberation \
fonts-noto-color-emoji \
fonts-dejavu-core \
# System libraries required by Chromium at runtime
libnss3 \
libnspr4 \
libatk1.0-0 \
libatk-bridge2.0-0 \
libcups2 \
libdrm2 \
libxdamage1 \
libxfixes3 \
libxcomposite1 \
libxrandr2 \
libgbm1 \
libpango-1.0-0 \
libcairo2 \
libasound2 \
libxkbcommon0 \
libxshmfence1 \
# Virtual framebuffer for headless operation
xvfb \
# Process supervisor for orphan reaping
tini \
# Git for Hermes source operations
git \
# SSL certificates for HTTPS connections
ca-certificates \
# Curl for health checks
curl \
&& rm -rf /var/lib/apt/lists/*
# ---------- Hermes Agent installation ----------
# Install uv (fast Python package manager)
COPY --chmod=0755 --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
COPY --chmod=0755 --from=ghcr.io/astral-sh/uv:latest /uvx /usr/local/bin/uvx
# Create hermes user (non-root runtime)
RUN useradd -u 10000 -m -d /opt/data hermes
# Install Hermes Agent from PyPI with gateway support for messaging
RUN uv pip install --system --no-cache-dir \
'hermes-agent[gateway]' \
croniter && \
uv cache clean
# Create the /opt/hermes directory structure expected by entrypoint
RUN mkdir -p /opt/hermes/.venv/bin && \
mkdir -p /opt/hermes/docker && \
ln -sf /usr/local/bin/uv /opt/hermes/.venv/bin/uv && \
ln -sf /usr/local/bin/uvx /opt/hermes/.venv/bin/uvx
# ---------- Entrypoint script ----------
COPY entrypoint.sh /opt/hermes/docker/entrypoint.sh
RUN chmod +x /opt/hermes/docker/entrypoint.sh
# ---------- Environment variables ----------
# Point browser tool to system Chromium (installed via apt-get)
# Set environment variable for Chromium path
ENV CHROME_EXECUTABLE=/usr/bin/chromium
# Hermes paths
ENV HERMES_HOME=/opt/data
ENV PATH="/opt/data/.local/bin:${PATH}"
# Playwright browsers path (for agent-browser install at runtime)
ENV PLAYWRIGHT_BROWSERS_PATH=/opt/hermes/.playwright
# Virtual framebuffer display for headless Chromium
ENV DISPLAY=:99
# ---------- Data volume ----------
VOLUME [ "/opt/data" ]
# ---------- Runtime ----------
USER hermes
WORKDIR /opt/data
ENTRYPOINT [ "/opt/hermes/docker/entrypoint.sh" ]
CMD [ "gateway", "run" ]

View File

@@ -1,20 +0,0 @@
#!/bin/bash
set -e
# Hermes Agent entrypoint script
# Installs custom tools and runtime dependencies,
# then delegates to the passed command (usually "gateway run").
# Install custom tools from persistent volume if available
if [ -f /opt/data/hermes-tools/install.sh ]; then
bash /opt/data/hermes-tools/install.sh
fi
# Install additional runtime deps (idempotent)
if command -v uv &>/dev/null; then
uv pip install --system --no-cache-dir --quiet \
openai mautrix[encryption] 2>/dev/null || true
fi
# Execute the passed command with tini for proper signal handling
exec tini -g -- "$@"

View File

@@ -1,73 +0,0 @@
#!/usr/bin/env bash
# ─────────────────────────────────────────────────────────────
# himalaya-ro — Read-only wrapper for himalaya
#
# Blocks destructive commands and logs audit trail.
# Pass-through for read-only commands (list, read, search).
#
# Usage: himalaya-ro [options] <command> [args...]
#
# Install: place in PATH before the real himalaya, or use
# `ln -sf himalaya-ro /usr/local/bin/himalaya`
# ─────────────────────────────────────────────────────────────
set -o pipefail
# ── Configuration ───────────────────────────────────────────
HIMALAYA_BIN="${HIMALAYA_BIN:-/usr/local/bin/himalaya}"
AUDIT_LOG="${HIMALAYA_AUDIT_LOG:-/var/log/himalaya-audit.log}"
# ── Destructive commands we block ──────────────────────────
BLOCKED_CMDS=(
"message move"
"message delete"
"message copy"
"flag add"
"flag remove"
"folder create"
"folder delete"
"folder rename"
"template send"
"account configure"
"account delete"
)
# ── Determine the subcommand being invoked ─────────────────
# Strip leading options (--account, --output, etc.) to find the verb
ARGS=()
SKIP_NEXT=false
for arg in "$@"; do
if $SKIP_NEXT; then
SKIP_NEXT=false
continue
fi
if [[ "$arg" == --* ]]; then
case "$arg" in
--account|--output|--page|--page-size|--folder|--color|--format)
SKIP_NEXT=true ;;
esac
continue
fi
ARGS+=("$arg")
done
# Build subcommand string and check against blocklist
CMD_STR=""
for ((i=0; i<${#ARGS[@]}; i++)); do
if [ -z "$CMD_STR" ]; then
CMD_STR="${ARGS[$i]}"
else
CMD_STR="$CMD_STR ${ARGS[$i]}"
fi
for blocked in "${BLOCKED_CMDS[@]}"; do
if [[ "$CMD_STR" == "$blocked" ]]; then
TS=$(date '+%Y-%m-%d %H:%M:%S')
echo "[AUDIT] $TS BLOCKED: himalaya $*" >> "$AUDIT_LOG"
echo "ERROR: Command 'himalaya $CMD_STR ...' is blocked by read-only policy." >&2
echo " Audit log: $AUDIT_LOG" >&2
exit 100
fi
done
done
# ── Allow pass-through ─────────────────────────────────────
exec "$HIMALAYA_BIN" "$@"

View File

@@ -1,181 +0,0 @@
#!/usr/bin/env python3
"""Patch Hermes TTS tool: add Piper TTS provider, remove Edge TTS as default.
Patches ALL copies of tts_tool.py found (venv site-packages + /opt/hermes/tools/).
Searches multiple paths for tts_tool.py so it works both at build time
(in the image venv) and at runtime (on the mounted data volume).
Idempotent: if already patched, does nothing.
"""
import sys
import os
# ---------------------------------------------------------------------------
# Search for all copies of tts_tool.py
# ---------------------------------------------------------------------------
CANDIDATE_PATHS = [
"/opt/hermes/.venv/lib/python3.13/site-packages/tools/tts_tool.py",
"/opt/hermes/tools/tts_tool.py",
]
found_paths = []
for p in CANDIDATE_PATHS:
if os.path.exists(p):
found_paths.append(p)
print(f"Found tts_tool.py at: {p}")
# Also try to find via Python import
import subprocess
try:
result = subprocess.run(
[sys.executable, "-c", "import tools.tts_tool; print(tools.tts_tool.__file__)"],
capture_output=True, text=True, timeout=5
)
if result.returncode == 0:
p = result.stdout.strip()
if os.path.exists(p) and p not in found_paths:
found_paths.append(p)
print(f"Found tts_tool.py via import at: {p}")
except Exception:
pass
if not found_paths:
print("WARNING: tts_tool.py not found anywhere. Patching deferred to runtime.")
print(f"Searched: {CANDIDATE_PATHS}")
sys.exit(0)
# ---------------------------------------------------------------------------
# Old else block: the Edge TTS default fallback to replace
# ---------------------------------------------------------------------------
old_else = ''' else:
# Default: Edge TTS (free), with NeuTTS as local fallback
edge_available = True
try:
_import_edge_tts()
except ImportError:
edge_available = False
if edge_available:
logger.info("Generating speech with Edge TTS...")
try:
import concurrent.futures
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as pool:
pool.submit(
lambda: asyncio.run(_generate_edge_tts(text, file_str, tts_config))
).result(timeout=60)
except RuntimeError:
asyncio.run(_generate_edge_tts(text, file_str, tts_config))
elif _check_neutts_available():
logger.info("Edge TTS not available, falling back to NeuTTS (local)...")
provider = "neutts"
_generate_neutts(text, file_str, tts_config)
else:
return json.dumps({
"success": False,
"error": "No TTS provider available. Install edge-tts (pip install edge-tts) "
"or set up NeuTTS for local synthesis."
}, ensure_ascii=False)'''
# ---------------------------------------------------------------------------
# New block: elif provider == "piper" + else: fallback with Piper only
# ---------------------------------------------------------------------------
new_block = ''' elif provider == "piper":
# Piper TTS (local, CPU, no cloud, no Microsoft)
piper_binary = "/opt/hermes/.venv/bin/piper"
piper_config = tts_config.get("piper", {})
voice = piper_config.get("voice", "en_US-lessac-medium")
model_dir = piper_config.get("model_dir", "/opt/hermes/.venv/share/piper/voices")
model_path = os.path.join(model_dir, f"{voice}.onnx")
if not os.path.exists(model_path):
return json.dumps({
"success": False,
"error": "Piper TTS voice model not found. "
"Install Piper TTS and download a voice model."
}, ensure_ascii=False)
logger.info("Generating speech with Piper TTS (local, CPU)...")
import subprocess as _sp
cmd = [piper_binary, "--model", model_path, "--output-raw"]
try:
proc = _sp.Popen(cmd, stdin=_sp.PIPE, stdout=_sp.PIPE, stderr=_sp.PIPE)
raw_audio, stderr = proc.communicate(input=text.encode(), timeout=60)
if proc.returncode != 0:
raise RuntimeError(f"Piper TTS failed: {stderr.decode()[:200]}")
ffmpeg_cmd = ["ffmpeg", "-f", "s16le", "-ar", "22050", "-ac", "1", "-i", "-", "-y", file_str]
_sp.run(ffmpeg_cmd, input=raw_audio, capture_output=True, timeout=30)
except Exception as e:
return json.dumps({
"success": False,
"error": f"Piper TTS failed: {e}"
}, ensure_ascii=False)
else:
# Default: Piper TTS (local, CPU, no cloud, no Microsoft)
piper_binary = "/opt/hermes/.venv/bin/piper"
piper_config = tts_config.get("piper", {})
voice = piper_config.get("voice", "en_US-lessac-medium")
model_dir = piper_config.get("model_dir", "/opt/hermes/.venv/share/piper/voices")
model_path = os.path.join(model_dir, f"{voice}.onnx")
if os.path.exists(model_path) and os.path.exists(piper_binary):
logger.info("Generating speech with Piper TTS (local, CPU)...")
import subprocess as _sp
cmd = [piper_binary, "--model", model_path, "--output-raw"]
try:
proc = _sp.Popen(cmd, stdin=_sp.PIPE, stdout=_sp.PIPE, stderr=_sp.PIPE)
raw_audio, stderr = proc.communicate(input=text.encode(), timeout=60)
if proc.returncode != 0:
raise RuntimeError(stderr.decode()[:200])
ffmpeg_cmd = ["ffmpeg", "-f", "s16le", "-ar", "22050", "-ac", "1", "-i", "-", "-y", file_str]
_sp.run(ffmpeg_cmd, input=raw_audio, capture_output=True, timeout=30)
except Exception:
pass
else:
return json.dumps({
"success": False,
"error": "Piper TTS not available. Install piper-tts and download a voice model."
}, ensure_ascii=False)'''
# ---------------------------------------------------------------------------
# Apply the patch to all copies found
# ---------------------------------------------------------------------------
patched_any = False
for tts_path in found_paths:
with open(tts_path) as f:
code = f.read()
if 'provider == "piper"' in code:
print(f"ALREADY PATCHED: {tts_path}")
continue
if old_else in code:
code = code.replace(old_else, new_block, 1)
with open(tts_path, 'w') as f:
f.write(code)
print(f"PATCHED: {tts_path}")
patched_any = True
else:
print(f"SKIP {tts_path}: Edge fallback pattern not found")
import re
for m in re.finditer(r' else:\n # Default:', code):
start = max(0, m.start() - 100)
end = min(len(code), m.end() + 300)
print(f" Found 'else:/# Default:' at position {m.start()}:")
print(f" {code[start:end]}")
print(" ---")
# Don't exit with error — if one copy isn't patchable, try the others
if not patched_any:
all_patched = all(
'provider == "piper"' in open(p).read()
for p in found_paths
)
if all_patched:
print("All copies already patched.")
sys.exit(0)
print("WARNING: Could not patch any copy of tts_tool.py")
sys.exit(1)
print("tts_tool.py patched successfully across all copies.")

View File

@@ -1,106 +0,0 @@
# ollama-gfx906/Dockerfile
#
# Custom ollama image with ROCm 6.1 + gfx906 (MI50) support.
# The official ollama/rocm image ships ROCm 7.2 which dropped gfx906.
# This uses v0.23.2's native CMake build system with AMDGPU_TARGETS including gfx906.
#
# Build: docker build -t ollama/ollama:rocm-gfx906 ai/ollama
FROM rocm/dev-ubuntu-22.04:6.1.2-complete AS builder
# Build dependencies (CMake, Ninja, Go)
ARG CMAKEVERSION=3.31.2
ARG NINJAVERSION=1.12.1
ARG GOLANG_VERSION=1.22.0
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \
curl git ccache build-essential pkg-config unzip \
&& rm -rf /var/lib/apt/lists/*
# Install CMake from official binaries
RUN curl -fsSL https://github.com/Kitware/CMake/releases/download/v${CMAKEVERSION}/cmake-${CMAKEVERSION}-linux-x86_64.tar.gz \
| tar xz -C /usr/local --strip-components 1
# Install Ninja
RUN curl -fsSL -o /tmp/ninja.zip \
https://github.com/ninja-build/ninja/releases/download/v${NINJAVERSION}/ninja-linux.zip \
&& unzip /tmp/ninja.zip -d /usr/local/bin && rm /tmp/ninja.zip
# Install Go
RUN curl -fsSL https://go.dev/dl/go${GOLANG_VERSION}.linux-amd64.tar.gz \
| tar xz -C /usr/local
ENV PATH=/usr/local/go/bin:$PATH
ARG OLLAMA_VERSION=v0.23.2
RUN git clone --depth 1 --branch ${OLLAMA_VERSION} https://github.com/ollama/ollama.git /build
WORKDIR /build
# ROCm paths
ENV HIP_PATH=/opt/rocm
ENV ROCM_PATH=/opt/rocm
ENV CMAKE_GENERATOR=Ninja
ENV LDFLAGS=-s
# Step 1: Build CPU backends with GCC (no ROCm preset)
# Pre-set CMAKE_HIP_COMPILER="" to prevent check_language(HIP) from
# finding a HIP compiler (it searches /opt/rocm even without PATH).
# Remove /opt/rocm from PATH to prevent find_program from finding hipcc.
RUN mkdir -p build-cpu && \
PATH=/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \
cmake -B build-cpu -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_HIP_COMPILER="" \
-DCMAKE_INSTALL_PREFIX=/build/dist && \
cmake --build build-cpu --target ggml-cpu -- -l $(nproc) && \
cmake --install build-cpu --component CPU --strip && \
echo "=== CPU install ===" && \
(find /build/dist/lib/ollama -type f -o -type l 2>&1 | head -20 || echo "empty")
# Step 2: Build HIP backend with ROCm preset + gfx906 target only
# The ROCm 6 preset enables HIP language detection (enable_language(HIP))
# which ensures GPU kernels are properly compiled for gfx906.
# OLLAMA_RUNNER_DIR=rocm from the preset, so HIP goes to lib/ollama/rocm/
# Need CMAKE_PREFIX_PATH so find_package(hip) finds hip-config.cmake
# at /opt/rocm/lib/cmake/hip/hip-config.cmake.
RUN mkdir -p build-hip && \
cmake -B build-hip \
--preset 'ROCm 6' \
-DAMDGPU_TARGETS="gfx906:xnack-" \
-DCMAKE_BUILD_TYPE=Release \
-DCMAKE_PREFIX_PATH="/opt/rocm" && \
cmake --build build-hip --target ggml-hip -- -l $(nproc) && \
cmake --install build-hip --component HIP --strip && \
echo "=== HIP install ===" && \
find /build/dist/lib/ollama -type f -o -type l | head -20
# Step 3: Build Go binary (GCC for CGo linking)
ENV CGO_ENABLED=1
RUN go build -trimpath -ldflags="-X=github.com/ollama/ollama/version.Version=${OLLAMA_VERSION}" -o /build/dist/ollama .
# ---------- Runtime image ----------
FROM ubuntu:24.04
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \
ca-certificates curl libstdc++6 libgomp1 libvulkan1 libopenblas0 \
&& rm -rf /var/lib/apt/lists/*
# Copy ROCm 6.1 runtime libraries
# These are needed at runtime by ggml-hip via LD_LIBRARY_PATH
COPY --from=builder /opt/rocm/lib/ /opt/rocm/lib/
COPY --from=builder /opt/rocm/share/ /opt/rocm/share/
# Copy ollama binary + all backends (CPU + HIP)
# CPU install: /build/dist/lib/ollama/libggml-*.so
# HIP install: /build/dist/lib/ollama/rocm/libggml-hip.so
COPY --from=builder /build/dist/ollama /usr/bin/ollama
COPY --from=builder /build/dist/lib/ollama/ /usr/lib/ollama/
RUN ldconfig
ENV LD_LIBRARY_PATH=/opt/rocm/lib:/usr/lib/ollama/rocm:/usr/lib/ollama
ENV HSA_OVERRIDE_GFX_VERSION=9.0.6
ENV HCC_AMDGPU_TARGET=gfx906
ENV HSA_ENABLE_SDMA=0
EXPOSE 11434
ENTRYPOINT ["/bin/ollama"]
CMD ["serve"]

View File

@@ -1,15 +1,15 @@
version: "3.9"
services:
# nomadnet:
# image: ghcr.io/markqvist/nomadnet:master
# container_name: nomadnet
# restart: always
# volumes:
# - /mnt/HoardingCow_docker_data/Nomadnet:/root/.nomadnetwork
# - /mnt/HoardingCow_docker_data/Reticulum:/root/.reticulum
# # Reticulum transport must be reachable directly (NOT through Traefik)
# ports:
# - "4242:4242"
nomadnet:
image: ghcr.io/markqvist/nomadnet:master
container_name: nomadnet
restart: always
volumes:
- /mnt/HoardingCow_docker_data/Nomadnet:/root/.nomadnetwork
- /mnt/HoardingCow_docker_data/Reticulum:/root/.reticulum
# Reticulum transport must be reachable directly (NOT through Traefik)
ports:
- "4242:4242"
synapse:
image: ghcr.io/element-hq/synapse:latest

View File

@@ -13,20 +13,17 @@ services:
- "--certificatesresolvers.njalla.acme.storage=/letsencrypt/acme.json"
- "--certificatesresolvers.njalla.acme.httpchallenge.entrypoint=web"
- "--log.level=INFO"
- "--log.filepath=/var/log/traefik/traefik.log"
- "--accesslog.filepath=/var/log/traefik/access.log"
- "--log.level=DEBUG"
- "--providers.docker=true"
- "--providers.docker.exposedByDefault=false"
ports:
- "80:80"
- "443:443"
environment:
- NJALLA_TOKEN=***
- NJALLA_TOKEN=${NJALLA_TOKEN}
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- /mnt/HoardingCow_docker_data/Traefik:/letsencrypt
- /var/log/traefik:/var/log/traefik
restart: unless-stopped
networks:
- traefik_backend

View File

@@ -7,11 +7,8 @@ services:
- USER_UID=1000
- USER_GID=1000
- GITEA__server__ROOT_URL=https://code.lazyworkhorse.net
- GITEA__actions__ENABLED=true
- SSH_PORT=2222
- SSH_LISTEN_PORT=2222
# Enable Gitea Actions (act_runner required on host)
- GITEA__actions__ENABLED=true
volumes:
- /mnt/HoardingCow_docker_data/Gitea:/data
networks:
@@ -43,22 +40,6 @@ services:
# Internal Routing
- "traefik.http.services.gitea.loadbalancer.server.port=3000"
act_runner:
image: gitea/act_runner:latest
container_name: act_runner
environment:
- GITEA_INSTANCE_URL=https://code.lazyworkhorse.net
- GITEA_RUNNER_REGISTRATION_TOKEN=${GITEA_RUNNER_TOKEN}
- GITEA_RUNNER_NAME=ai-host-runner
- GITEA_RUNNER_LABELS=ubuntu-latest:docker://catthehacker/ubuntu:full-22.04,nixos-builder:docker://nixos/nix
volumes:
- /var/run/docker.sock:/var/run/docker.sock
networks:
- vc_net
restart: always
depends_on:
- gitea
networks:
vc_net:
external: true

View File

@@ -1,9 +0,0 @@
# Custom wg-easy with iptables-nft (nftables-backed iptables)
# Fixes crash-loop when host kernel lacks legacy iptable_nat module.
FROM ghcr.io/wg-easy/wg-easy:latest
# The upstream image registers only iptables-legacy with update-alternatives.
# iptables-nft binary exists but isn't registered as an alternative key.
# Override the alternatives-managed symlinks directly.
RUN ln -sf /usr/sbin/iptables-nft /usr/sbin/iptables && \
ln -sf /usr/sbin/ip6tables-nft /usr/sbin/ip6tables

View File

@@ -1,38 +0,0 @@
version: "3.8"
services:
wireguard:
build:
context: .
dockerfile: Dockerfile
image: wg-easy-iptables-nft:latest
container_name: wireguard
cap_add:
- NET_ADMIN
- SYS_MODULE
environment:
- WG_HOST=vpn.lazyworkhorse.net
- PASSWORD=${WG_PASSWORD}
- WG_PORT=51820
- WG_DEFAULT_ADDRESS=10.8.0.x
- WG_DEFAULT_DNS=1.1.1.1,8.8.8.8
- WG_ALLOWED_IPS=0.0.0.0/0, ::/0
- WG_PERSISTENT_KEEPALIVE=25
- UI_TRAFFIC_STATS=true
- UI_CHART_TYPE=0
ports:
- "51820:51820/udp"
- "51821:51821/tcp"
volumes:
- /mnt/HoardingCow_docker_data/WireGuard:/etc/wireguard:rw
sysctls:
- net.ipv4.conf.all.src_valid_mark=1
- net.ipv4.ip_forward=1
restart: unless-stopped
networks:
- vpn_net
networks:
vpn_net:
external: true
name: vpn_net