feat: Add paperclip-docker Ansible role for RTX 3090

Deploy Paperclip AI with PostgreSQL, nginx reverse proxy, and Cloudflare
SSL/TLS on illuminations.wdw-labs.cloud. Configured for local_trusted
mode (LAN-only, no auth) with host networking and auto-generated secrets.

Stack: PostgreSQL 16 + Paperclip + nginx + certbot
Hostname: muppet-vision.wdw-labs.cloud
vLLM endpoint: localhost:8000 (DeepSeek-R1-32B Q4)
This commit is contained in:
rblundon committed 2026-09-27 16:47:04 -05:00
1 parent 8778a3bbb0
commit f701ad823f
13 files changed
+1093 -124

No files matched your search

@@ -0,0 +1,65 @@
#!/bin/sh
set -e
DOMAIN="${PAPERCLIP_DOMAIN}"
CERT_PATH="/etc/letsencrypt/live/${DOMAIN}/fullchain.pem"
EMAIL="${CERTBOT_EMAIL}"
TOKEN="${CLOUDFLARE_API_TOKEN}"
# Verify environment variables
if [ -z "$DOMAIN" ]; then
echo "ERROR: PAPERCLIP_DOMAIN not set"
exit 1
fi
if [ -z "$EMAIL" ]; then
echo "ERROR: CERTBOT_EMAIL not set"
exit 1
fi
if [ -z "$TOKEN" ]; then
echo "ERROR: CLOUDFLARE_API_TOKEN not set"
exit 1
fi
# Check if certificate already exists
if [ -f "$CERT_PATH" ]; then
echo "Certificate for $DOMAIN already exists at $CERT_PATH"
exit 0
fi
echo "Requesting certificate for $DOMAIN via Cloudflare DNS challenge..."
# Create a temporary credentials file (more reliable than env var with certbot)
CREDS_FILE="/tmp/cloudflare.ini"
cat > "$CREDS_FILE" << EOF
dns_cloudflare_api_token = ${TOKEN}
EOF
chmod 600 "$CREDS_FILE"
# Request certificate with Cloudflare DNS plugin
if certbot certonly \
--dns-cloudflare \
--dns-cloudflare-credentials "$CREDS_FILE" \
--agree-tos \
--no-eff-email \
--email "$EMAIL" \
--non-interactive \
-d "$DOMAIN"; then
echo "Certificate obtained successfully for $DOMAIN"
else
echo "ERROR: Failed to obtain certificate for $DOMAIN"
rm -f "$CREDS_FILE"
exit 1
fi
# Clean up credentials file
rm -f "$CREDS_FILE"
# Verify certificate
if [ -f "$CERT_PATH" ]; then
echo "Certificate verified at $CERT_PATH"
else
echo "ERROR: Certificate not found at $CERT_PATH after successful certbot run"
exit 1
fi
@@ -0,0 +1,90 @@
services:
db:
image: postgres:16-alpine
container_name: paperclip-db
network_mode: host
environment:
POSTGRES_USER: paperclip
POSTGRES_PASSWORD: paperclip
POSTGRES_DB: paperclip
volumes:
- {{ paperclip_base_path }}/pgdata:/var/lib/postgresql/data
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -U paperclip -d paperclip"]
interval: 10s
timeout: 5s
retries: 5
paperclip:
image: reeoss/paperclipai-paperclip:latest
container_name: paperclip
network_mode: host
env_file:
- {{ paperclip_base_path }}/.env
environment:
HOST: "127.0.0.1"
PAPERCLIP_HOME: /paperclip
PAPERCLIP_PORT: {{ paperclip_port }}
PAPERCLIP_PUBLIC_URL: "https://{{ paperclip_domain }}"
PAPERCLIP_DEPLOYMENT_MODE: "{{ paperclip_deployment_mode }}"
PAPERCLIP_DEPLOYMENT_EXPOSURE: "private"
PAPERCLIP_ALLOWED_HOSTNAMES: "{{ paperclip_allowed_hostnames }}"
DATABASE_URL: "postgresql://paperclip:paperclip@localhost:5432/paperclip"
BETTER_AUTH_SECRET: ${BETTER_AUTH_SECRET}
PAPERCLIP_AGENT_JWT_SECRET: ${PAPERCLIP_AGENT_JWT_SECRET}
VLLM_ENDPOINT: ${VLLM_ENDPOINT}
OPENAI_API_KEY: "${OPENAI_API_KEY:-}"
ANTHROPIC_API_KEY: "${ANTHROPIC_API_KEY:-}"
volumes:
- {{ paperclip_data_dir }}:/paperclip
depends_on:
db:
condition: service_healthy
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:{{ paperclip_port }}/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 60s
certbot:
image: certbot/dns-cloudflare:latest
container_name: paperclip-certbot
volumes:
- {{ paperclip_certs_dir }}:/etc/letsencrypt
- {{ paperclip_base_path }}/certbot-init.sh:/certbot-init.sh:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
environment:
CLOUDFLARE_API_TOKEN: ${CLOUDFLARE_API_TOKEN}
CERTBOT_EMAIL: ${CERTBOT_EMAIL}
PAPERCLIP_DOMAIN: ${PAPERCLIP_DOMAIN}
entrypoint: /bin/sh
command:
- -c
- |
/certbot-init.sh && while true; do certbot renew --quiet; sleep 12h; done
restart: unless-stopped
nginx:
image: nginx:latest
container_name: paperclip-nginx
network_mode: host
volumes:
- {{ paperclip_base_path }}/nginx.conf:/etc/nginx/nginx.conf:ro
- {{ paperclip_certs_dir }}:/etc/nginx/certs:ro
depends_on:
- certbot
restart: unless-stopped
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:80/"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
volumes:
paperclip_data:
driver: local
@@ -0,0 +1,86 @@
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
client_max_body_size 100M;
# Redirect HTTP to HTTPS
server {
listen 80;
server_name {{ paperclip_domain }};
return 301 https://$server_name$request_uri;
}
# HTTPS server
server {
listen 443 ssl http2;
server_name {{ paperclip_domain }};
# SSL certificates from certbot
ssl_certificate /etc/nginx/certs/live/{{ paperclip_domain }}/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/live/{{ paperclip_domain }}/privkey.pem;
# SSL configuration
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
location / {
proxy_pass http://127.0.0.1:{{ paperclip_port }};
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $server_name;
proxy_set_header X-Forwarded-Port $server_port;
# WebSocket support
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# Timeouts
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# Buffering
proxy_buffering off;
proxy_request_buffering off;
}
# Health check endpoint (no auth)
location /health {
proxy_pass http://127.0.0.1:{{ paperclip_port }}/health;
access_log off;
}
}
}