From 563ccc5632407c296de5ccda522bd280e22d8a40 Mon Sep 17 00:00:00 2001 From: Hermes Date: Mon, 18 May 2026 18:17:15 -0400 Subject: [PATCH 1/4] feat: add Paperclip agent orchestrator to AI compose stack 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. --- ai/compose.yml | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/ai/compose.yml b/ai/compose.yml index aca3347..004a5c9 100644 --- a/ai/compose.yml +++ b/ai/compose.yml @@ -96,6 +96,61 @@ services: - "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: + image: ghcr.io/paperclipai/paperclip:v2026.517.0 + 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: ai_net: external: true -- 2.49.1 From 37bf43c3ea1fddfa4db93591eba8050eea25333f Mon Sep 17 00:00:00 2001 From: Hermes Date: Mon, 18 May 2026 18:37:31 -0400 Subject: [PATCH 2/4] feat: add custom Dockerfile with Hermes adapter baked in MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- ai/compose.yml | 3 +- ai/paperclip/Dockerfile | 47 +++++++++++++++++++++++++++++++ ai/paperclip/docker-entrypoint.sh | 35 +++++++++++++++++++++++ 3 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 ai/paperclip/Dockerfile create mode 100644 ai/paperclip/docker-entrypoint.sh diff --git a/ai/compose.yml b/ai/compose.yml index 004a5c9..003b3d4 100644 --- a/ai/compose.yml +++ b/ai/compose.yml @@ -115,7 +115,8 @@ services: - ai_backend paperclip: - image: ghcr.io/paperclipai/paperclip:v2026.517.0 + build: + context: ./paperclip container_name: paperclip restart: always ports: diff --git a/ai/paperclip/Dockerfile b/ai/paperclip/Dockerfile new file mode 100644 index 0000000..a40c91a --- /dev/null +++ b/ai/paperclip/Dockerfile @@ -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"] diff --git a/ai/paperclip/docker-entrypoint.sh b/ai/paperclip/docker-entrypoint.sh new file mode 100644 index 0000000..28f561a --- /dev/null +++ b/ai/paperclip/docker-entrypoint.sh @@ -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 "$@" -- 2.49.1 From bce403232a1cfe63caa3c90b4b12ce0b6b8d262f Mon Sep 17 00:00:00 2001 From: Hermes Date: Mon, 18 May 2026 18:38:17 -0400 Subject: [PATCH 3/4] Revert custom Dockerfile - not needed, adapter installs on persistent volume The Hermes adapter can be installed once via Paperclip's adapter management API and persists on the Docker volume across restarts. No custom Dockerfile or build step required. --- ai/compose.yml | 3 +- ai/paperclip/Dockerfile | 47 ------------------------------- ai/paperclip/docker-entrypoint.sh | 35 ----------------------- 3 files changed, 1 insertion(+), 84 deletions(-) delete mode 100644 ai/paperclip/Dockerfile delete mode 100644 ai/paperclip/docker-entrypoint.sh diff --git a/ai/compose.yml b/ai/compose.yml index 003b3d4..004a5c9 100644 --- a/ai/compose.yml +++ b/ai/compose.yml @@ -115,8 +115,7 @@ services: - ai_backend paperclip: - build: - context: ./paperclip + image: ghcr.io/paperclipai/paperclip:v2026.517.0 container_name: paperclip restart: always ports: diff --git a/ai/paperclip/Dockerfile b/ai/paperclip/Dockerfile deleted file mode 100644 index a40c91a..0000000 --- a/ai/paperclip/Dockerfile +++ /dev/null @@ -1,47 +0,0 @@ -# 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"] diff --git a/ai/paperclip/docker-entrypoint.sh b/ai/paperclip/docker-entrypoint.sh deleted file mode 100644 index 28f561a..0000000 --- a/ai/paperclip/docker-entrypoint.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/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 "$@" -- 2.49.1 From 1eacc3cd8eae7fe12feec62c2dab894f3dd6a956 Mon Sep 17 00:00:00 2001 From: Hermes Date: Mon, 18 May 2026 22:13:59 -0400 Subject: [PATCH 4/4] fix(paperclip): force Traefik to use ai_net network for routing --- ai/compose.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/ai/compose.yml b/ai/compose.yml index 004a5c9..6d51f6e 100644 --- a/ai/compose.yml +++ b/ai/compose.yml @@ -139,6 +139,7 @@ services: - ai_backend labels: - "traefik.enable=true" + - "traefik.docker.network=ai_net" - "traefik.http.routers.paperclip-http.rule=Host(`paperclip.lazyworkhorse.net`)" - "traefik.http.routers.paperclip-http.entrypoints=web" -- 2.49.1