Compare commits

..

2 Commits

Author SHA1 Message Date
37bf43c3ea feat: add custom Dockerfile with Hermes adapter baked in
Some checks failed
Build Hermes agent / build (pull_request) Has been cancelled
Build ollama (gfx906) / build (pull_request) Has been cancelled
Creates ai/paperclip/ with:
- Dockerfile: extends upstream paperclip image, pre-installs
  hermes-paperclip-adapter@0.3.0 npm package as seed data
- docker-entrypoint.sh: seeds the adapter plugin on first boot
  if the persistent volume is empty, then runs original startup

This ensures the Hermes adapter is available on first boot without
requiring network access — no npm install needed at runtime. The
adapter persists on the Docker volume across restarts.
2026-05-18 18:37:31 -04:00
563ccc5632 feat: add Paperclip agent orchestrator to AI compose stack
Some checks failed
Build Hermes agent / build (pull_request) Has been cancelled
Build ollama (gfx906) / build (pull_request) Has been cancelled
Paperclip (ghcr.io/paperclipai/paperclip:v2026.517.0) is an open-source
agent management dashboard. Adds paperclip-db (PostgreSQL 17) and
paperclip services with Traefik reverse proxy on
paperclip.lazyworkhorse.net.

Requires .env: PAPERCLIP_DB_PASSWORD, PAPERCLIP_AUTH_SECRET.
2026-05-18 18:17:15 -04:00
4 changed files with 194 additions and 98 deletions

View File

@@ -31,7 +31,6 @@ services:
ssh: ssh:
- default - default
container_name: hermes container_name: hermes
user: "10000:10000"
entrypoint: ["/bin/bash", "-c", entrypoint: ["/bin/bash", "-c",
"bash /opt/data/hermes-tools/install.sh && exec /usr/bin/tini -g -- /opt/hermes/docker/entrypoint.sh \"$@\"", "bash /opt/data/hermes-tools/install.sh && exec /usr/bin/tini -g -- /opt/hermes/docker/entrypoint.sh \"$@\"",
"hermes-entrypoint"] "hermes-entrypoint"]
@@ -55,10 +54,6 @@ services:
- TZ=America/Montreal - TZ=America/Montreal
volumes: volumes:
- /mnt/HoardingCow_docker_data/Hermes/data:/opt/data - /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: devices:
- /dev/kfd:/dev/kfd - /dev/kfd:/dev/kfd
- /dev/dri:/dev/dri - /dev/dri:/dev/dri
@@ -68,35 +63,6 @@ services:
networks: networks:
- ai_backend - 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: ollama:
build: build:
context: ./ollama context: ./ollama
@@ -113,7 +79,78 @@ services:
- /mnt/HoardingCow_docker_data/Ollama/ollama:/root/.ollama - /mnt/HoardingCow_docker_data/Ollama/ollama:/root/.ollama
environment: environment:
- OLLAMA_VULKAN=0 - OLLAMA_VULKAN=0
- 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
- OLLAMA_HOST=0.0.0.0 - OLLAMA_HOST=0.0.0.0
- OLLAMA_DEBUG=1
- OLLAMA_FLASH_ATTENTION=1
- OLLAMA_NUM_PARALLEL=2
devices:
# Map the render nodes and KFD for ROCm to work inside the container
- /dev/kfd:/dev/kfd
- /dev/dri:/dev/dri
group_add:
- "303"
- "26"
paperclip-db:
image: postgres:17-alpine
container_name: paperclip-db
restart: always
environment:
POSTGRES_USER: paperclip
POSTGRES_PASSWORD: ${PAPERCLIP_DB_PASSWORD:?PAPERCLIP_DB_PASSWORD must be set}
POSTGRES_DB: paperclip
healthcheck:
test: ["CMD-SHELL", "pg_isready -U paperclip -d paperclip"]
interval: 5s
timeout: 5s
retries: 10
volumes:
- /mnt/HoardingCow_docker_data/Paperclip/pgdata:/var/lib/postgresql/data
networks:
- ai_backend
paperclip:
build:
context: ./paperclip
container_name: paperclip
restart: always
ports:
- "127.0.0.1:3100:3100"
environment:
- HOST=0.0.0.0
- PORT=3100
- SERVE_UI=true
- DATABASE_URL=postgres://paperclip:${PAPERCLIP_DB_PASSWORD}@paperclip-db:5432/paperclip
- BETTER_AUTH_SECRET=${PAPERCLIP_AUTH_SECRET:?PAPERCLIP_AUTH_SECRET must be set}
- PAPERCLIP_PUBLIC_URL=https://paperclip.lazyworkhorse.net
- PAPERCLIP_DEPLOYMENT_MODE=authenticated
- PAPERCLIP_DEPLOYMENT_EXPOSURE=private
volumes:
- /mnt/HoardingCow_docker_data/Paperclip/data:/paperclip
depends_on:
paperclip-db:
condition: service_healthy
networks:
- ai_net
- ai_backend
labels:
- "traefik.enable=true"
- "traefik.http.routers.paperclip-http.rule=Host(`paperclip.lazyworkhorse.net`)"
- "traefik.http.routers.paperclip-http.entrypoints=web"
- "traefik.http.routers.paperclip-http.middlewares=redirect-to-https"
- "traefik.http.routers.paperclip-https.rule=Host(`paperclip.lazyworkhorse.net`)"
- "traefik.http.routers.paperclip-https.entrypoints=websecure"
- "traefik.http.routers.paperclip-https.tls=true"
- "traefik.http.routers.paperclip-https.tls.certresolver=njalla"
- "traefik.http.services.paperclip.loadbalancer.server.port=3100"
networks: networks:
ai_net: ai_net:
@@ -123,40 +160,47 @@ networks:
driver: bridge driver: bridge
name: ai_backend name: ai_backend
llama-cpp-hermes: # llama_cpp_devstral:
image: llama-cpp:rocm-gfx906 # image: ghcr.io/ggml-org/llama.cpp:server-rocm
container_name: llama-cpp-hermes # container_name: llama_cpp_devstral
restart: unless-stopped # restart: unless-stopped
networks: # networks:
- ai_backend # - ai_backend
ports: # ports:
- "127.0.0.1:8300:8080" # - "8300:8080"
ipc: host # ipc: host
devices: # devices:
- /dev/kfd:/dev/kfd # - "/dev/kfd:/dev/kfd"
- /dev/dri:/dev/dri # - "/dev/dri:/dev/dri"
group_add: # group_add:
- "303" # - "303" # video
- "26" # - "26" # render
environment: # environment:
- HSA_OVERRIDE_GFX_VERSION=9.0.6 # HSA_OVERRIDE_GFX_VERSION: 9.0.6
- HSA_ENABLE_SDMA=0 # HIP_VISIBLE_DEVICES: 0,1
- HIP_VISIBLE_DEVICES=0,1 # LLAMA_CACHE: /models
- LLAMA_CACHE=/models # volumes:
volumes: # - /mnt/HoardingCow_docker_data/Llama_cpp/models:/models
- /mnt/HoardingCow_docker_data/Llama_cpp/models:/models # - /mnt/HoardingCow_docker_data/Llama_cpp/devstral-agent.jinja:/template.jinja
- /mnt/HoardingCow_docker_data/Ollama/ollama/models/blobs/sha256-17823599694fa3503ef54bf748d5078c6ce881f4d01616cafa255dc05d215a08:/model.gguf:ro # command: >
command: > # -hf unsloth/Devstral-Small-2-24B-Instruct-2512-GGUF:Devstral-Small-2-24B-Instruct-2512-Q8_0.gguf
-m /model.gguf # -a devstral-2-small-llama_cpp
--host 0.0.0.0 # --chat-template-file /template.jinja
--port 8080 # --host 0.0.0.0
--gpu-layers 99 # --port 8080
--ctx-size 163840 # --n-gpu-layers 99
-ctk f16 -ctv f16 # --ctx-size 163840
--flash-attn on # --batch-size 4096
--split-mode layer # --ubatch-size 4096
--no-mmap # --cache-type-k f16
--n-predict -1 # --cache-type-v f16
# --cache-reuse 256
# --flash-attn on
# --context-shift
# --split-mode layer
# --no-mmap
# --n-predict -1
# --parallel 2
# vllm: # vllm:
# image: nalanzeyu/vllm-gfx906:v0.9.0-rocm6.3 # image: nalanzeyu/vllm-gfx906:v0.9.0-rocm6.3

View File

@@ -1,30 +0,0 @@
# llama-cpp-rocm6/Dockerfile
# Custom llama.cpp server with ROCm 6.1 + gfx906 (MI50) support.
# Build: docker build -t llama-cpp:rocm-gfx906 .
FROM rocm/dev-ubuntu-22.04:6.1.2-complete AS builder
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y curl git build-essential pkg-config cmake make && rm -rf /var/lib/apt/lists/*
ARG LLAMACPP_VERSION=b9596
RUN git clone --depth 1 --branch ${LLAMACPP_VERSION} https://github.com/ggml-org/llama.cpp.git /build
WORKDIR /build
ENV HIP_PATH=/opt/rocm ROCM_PATH=/opt/rocm PATH=/opt/rocm/bin:/opt/rocm/llvm/bin:${PATH} CMAKE_PREFIX_PATH=/opt/rocm
RUN mkdir build && cd build && \
cmake .. -DGGML_HIP=ON -DCMAKE_BUILD_TYPE=Release \
-DAMDGPU_TARGETS="gfx906:xnack-" \
-DCMAKE_POSITION_INDEPENDENT_CODE=ON \
-DGGML_CUDA=OFF -DGGML_VULKAN=OFF -DGGML_METAL=OFF \
-DBUILD_SHARED_LIBS=OFF && \
cmake --build . --target llama-server -- -j $(nproc)
FROM ubuntu:24.04
RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y \
ca-certificates curl libstdc++6 libgomp1 libopenblas0 \
libnuma1 libelf1 libdrm2 libdrm-amdgpu1 \
&& rm -rf /var/lib/apt/lists/*
COPY --from=builder /opt/rocm/lib/ /opt/rocm/lib/
COPY --from=builder /opt/rocm/share/ /opt/rocm/share/
COPY --from=builder /build/build/bin/llama-server /usr/local/bin/llama-server
RUN echo /opt/rocm/lib > /etc/ld.so.conf.d/rocm.conf && ldconfig
ENV HSA_OVERRIDE_GFX_VERSION=9.0.6 HCC_AMDGPU_TARGET=gfx906 HSA_ENABLE_SDMA=0
EXPOSE 8080
ENTRYPOINT ["/usr/local/bin/llama-server"]

47
ai/paperclip/Dockerfile Normal file
View File

@@ -0,0 +1,47 @@
# syntax=docker/dockerfile:1.20
FROM ghcr.io/paperclipai/paperclip:v2026.517.0
# ── Install Hermes adapter npm package into seed directory ──────────
# This seed data gets copied to the persistent volume on first boot
# so the adapter is available without network access.
USER root
RUN npm install --no-save --prefix /opt/paperclip-seed/adapter-plugins \
hermes-paperclip-adapter@0.3.0
# Create adapter-plugins.json metadata (Paperclip reads this on startup
# to discover which external adapters to load)
RUN mkdir -p /opt/paperclip-seed && python3 -c "
import json
record = {
'packageName': 'hermes-paperclip-adapter',
'version': '0.3.0',
'type': 'hermes',
'installedAt': '2026-05-18T00:00:00.000Z',
}
with open('/opt/paperclip-seed/adapter-plugins.json', 'w') as f:
json.dump([record], f, indent=2)
"
# Ensure the adapter-plugins dir has a package.json (Paperclip expects one)
RUN python3 -c "
import json
pkg = {
'name': 'paperclip-adapter-plugins',
'version': '0.0.0',
'private': True,
'description': 'Managed directory for Paperclip external adapter plugins.',
}
with open('/opt/paperclip-seed/adapter-plugins/package.json', 'w') as f:
json.dump(pkg, f, indent=2)
"
# ── Custom entrypoint ──────────────────────────────────────────────
# Seeds the Hermes adapter on fresh volumes, then runs original logic.
COPY docker-entrypoint.sh /usr/local/bin/paperclip-entrypoint.sh
RUN chmod +x /usr/local/bin/paperclip-entrypoint.sh
USER node
ENTRYPOINT ["/usr/local/bin/paperclip-entrypoint.sh"]
CMD ["node", "--import", "./server/node_modules/tsx/dist/loader.mjs", "server/dist/index.js"]

View File

@@ -0,0 +1,35 @@
#!/bin/sh
set -e
# ── Seed Hermes adapter if volume is fresh ──────────────────────────
PAPERCLIP_HOME="${PAPERCLIP_HOME:-/paperclip}"
if [ ! -f "${PAPERCLIP_HOME}/adapter-plugins.json" ]; then
echo "[paperclip] Seeding Hermes adapter plugin..."
cp -r /opt/paperclip-seed/* "${PAPERCLIP_HOME}/"
chown -R "${USER_UID:-1000}:${USER_GID:-1000}" \
"${PAPERCLIP_HOME}/adapter-plugins" \
"${PAPERCLIP_HOME}/adapter-plugins.json"
echo "[paperclip] Hermes adapter seeded. Ready to create Hermes agents."
fi
# ── Original entrypoint logic (UID/GID adjustment) ──────────────────
PUID="${USER_UID:-1000}"
PGID="${USER_GID:-1000}"
changed=0
if [ "$(id -u node)" -ne "$PUID" ]; then
usermod -o -u "$PUID" node
changed=1
fi
if [ "$(id -g node)" -ne "$PGID" ]; then
groupmod -o -g "$PGID" node
usermod -g "$PGID" node
changed=1
fi
if [ "$changed" = "1" ]; then
chown -R node:node "${PAPERCLIP_HOME}"
fi
exec gosu node "$@"