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:
1 parent
8778a3bbb0
commit
f701ad823f
13 files changed
+1093
-124
No files matched your search
@@ -0,0 +1,191 @@
|
||||
# Installation Guide - Paperclip Docker Role
|
||||
|
||||
## File Placement
|
||||
|
||||
Copy the files from this directory to your ansible repo as follows:
|
||||
|
||||
### Role Files
|
||||
|
||||
Create the role directory structure:
|
||||
|
||||
```bash
|
||||
mkdir -p roles/paperclip-docker/{defaults,handlers,tasks,templates}
|
||||
```
|
||||
|
||||
Then copy:
|
||||
|
||||
| From | To |
|
||||
|------|-----|
|
||||
| `defaults_main.yml` | `roles/paperclip-docker/defaults/main.yml` |
|
||||
| `handlers_main.yml` | `roles/paperclip-docker/handlers/main.yml` |
|
||||
| `tasks_main.yml` | `roles/paperclip-docker/tasks/main.yml` |
|
||||
| `docker-compose.yml.j2` | `roles/paperclip-docker/templates/docker-compose.yml.j2` |
|
||||
| `nginx.conf.j2` | `roles/paperclip-docker/templates/nginx.conf.j2` |
|
||||
| `certbot-init.sh.j2` | `roles/paperclip-docker/templates/certbot-init.sh.j2` |
|
||||
| `README.md` | `roles/paperclip-docker/README.md` |
|
||||
|
||||
### Host Variables
|
||||
|
||||
Create the host directory:
|
||||
|
||||
```bash
|
||||
mkdir -p host_vars/illuminations.wdw-labs.cloud
|
||||
```
|
||||
|
||||
Then copy:
|
||||
|
||||
| From | To |
|
||||
|------|-----|
|
||||
| `host_vars_illuminations.yml` | `host_vars/illuminations.wdw-labs.cloud/vars.yml` |
|
||||
|
||||
### Playbook
|
||||
|
||||
Copy:
|
||||
|
||||
| From | To |
|
||||
|------|-----|
|
||||
| `deploy_paperclip_stack.yml` | `playbooks/deploy_paperclip_stack.yml` |
|
||||
|
||||
## Complete Installation Command
|
||||
|
||||
```bash
|
||||
# From your ansible repo root
|
||||
|
||||
# Create directories
|
||||
mkdir -p roles/paperclip-docker/{defaults,handlers,tasks,templates}
|
||||
mkdir -p host_vars/illuminations.wdw-labs.cloud
|
||||
|
||||
# Copy files (adjust path to where you downloaded them)
|
||||
cp /path/to/defaults_main.yml roles/paperclip-docker/defaults/main.yml
|
||||
cp /path/to/handlers_main.yml roles/paperclip-docker/handlers/main.yml
|
||||
cp /path/to/tasks_main.yml roles/paperclip-docker/tasks/main.yml
|
||||
cp /path/to/docker-compose.yml.j2 roles/paperclip-docker/templates/
|
||||
cp /path/to/nginx.conf.j2 roles/paperclip-docker/templates/
|
||||
cp /path/to/certbot-init.sh.j2 roles/paperclip-docker/templates/
|
||||
cp /path/to/README.md roles/paperclip-docker/
|
||||
cp /path/to/host_vars_illuminations.yml host_vars/illuminations.wdw-labs.cloud/vars.yml
|
||||
cp /path/to/deploy_paperclip_stack.yml playbooks/
|
||||
|
||||
# Verify structure
|
||||
tree roles/paperclip-docker
|
||||
tree host_vars/illuminations.wdw-labs.cloud
|
||||
ls -la playbooks/deploy_paperclip_stack.yml
|
||||
```
|
||||
|
||||
## Final Directory Structure
|
||||
|
||||
After installation, your ansible repo should have:
|
||||
|
||||
```
|
||||
.
|
||||
├── roles/
|
||||
│ └── paperclip-docker/
|
||||
│ ├── README.md
|
||||
│ ├── defaults/
|
||||
│ │ └── main.yml
|
||||
│ ├── handlers/
|
||||
│ │ └── main.yml
|
||||
│ ├── tasks/
|
||||
│ │ └── main.yml
|
||||
│ └── templates/
|
||||
│ ├── certbot-init.sh.j2
|
||||
│ ├── docker-compose.yml.j2
|
||||
│ └── nginx.conf.j2
|
||||
├── playbooks/
|
||||
│ ├── deploy_paperclip_stack.yml
|
||||
│ └── (other existing playbooks)
|
||||
├── host_vars/
|
||||
│ ├── illuminations.wdw-labs.cloud/
|
||||
│ │ └── vars.yml
|
||||
│ └── (other existing host vars)
|
||||
├── group_vars/
|
||||
│ └── (existing group vars)
|
||||
├── inventory.yml
|
||||
├── ansible.cfg
|
||||
└── (other existing files)
|
||||
```
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Copy the files** as described above
|
||||
2. **Review variables** in `host_vars/illuminations.wdw-labs.cloud/vars.yml` - update if needed
|
||||
3. **Verify vault** contains `vault_cloudflare_api_token`
|
||||
4. **Test connectivity**:
|
||||
```bash
|
||||
ansible -i inventory.yml illuminations.wdw-labs.cloud -m ping
|
||||
```
|
||||
5. **Run the playbook**:
|
||||
```bash
|
||||
ansible-playbook playbooks/deploy_paperclip_stack.yml -i inventory.yml -K
|
||||
```
|
||||
(The `-K` prompts for become password)
|
||||
|
||||
**Note:** The playbook automatically calls the `docker-host` role first to ensure Docker is installed. No manual Docker setup needed.
|
||||
|
||||
## Verification After Installation
|
||||
|
||||
```bash
|
||||
# Verify the role exists
|
||||
ls -la roles/paperclip-docker/
|
||||
|
||||
# Verify host vars exist
|
||||
cat host_vars/illuminations.wdw-labs.cloud/vars.yml
|
||||
|
||||
# Verify playbook exists
|
||||
cat playbooks/deploy_paperclip_stack.yml
|
||||
|
||||
# Test syntax
|
||||
ansible-playbook playbooks/deploy_paperclip_stack.yml --syntax-check
|
||||
|
||||
# Dry run
|
||||
ansible-playbook playbooks/deploy_paperclip_stack.yml -i inventory.yml --check
|
||||
```
|
||||
|
||||
## Troubleshooting Installation
|
||||
|
||||
### Role not found error
|
||||
```
|
||||
ERROR! the role 'paperclip-docker' was not found
|
||||
```
|
||||
|
||||
**Solution:** Ensure `roles/paperclip-docker/` exists with `tasks/main.yml` inside.
|
||||
|
||||
### Template not found error
|
||||
```
|
||||
ERROR! Unable to find template paperclip-docker/docker-compose.yml.j2
|
||||
```
|
||||
|
||||
**Solution:** Ensure templates are in `roles/paperclip-docker/templates/` with `.j2` extension.
|
||||
|
||||
### Vault variable not found
|
||||
```
|
||||
fatal: [illuminations]: FAILED! => error: Undefined variable: vault_cloudflare_api_token
|
||||
```
|
||||
|
||||
**Solution:** Ensure `vault_cloudflare_api_token` is defined in your vault file:
|
||||
```bash
|
||||
ansible-vault view group_vars/all/vault
|
||||
```
|
||||
|
||||
### Host not in inventory
|
||||
```
|
||||
ERROR! Unable to parse illuminations.wdw-labs.cloud as an inventory address
|
||||
```
|
||||
|
||||
**Solution:** Ensure your `inventory.yml` contains the host:
|
||||
```yaml
|
||||
all:
|
||||
hosts:
|
||||
illuminations.wdw-labs.cloud:
|
||||
ansible_host: <ip_address>
|
||||
```
|
||||
|
||||
## Files Reference
|
||||
|
||||
All files are self-contained and ready to use. No modifications needed unless:
|
||||
|
||||
- You want to change the domain (edit `host_vars/illuminations.wdw-labs.cloud/vars.yml`)
|
||||
- You want to change ports (edit `roles/paperclip-docker/defaults/main.yml`)
|
||||
- You want to change the certbot email (edit host_vars)
|
||||
|
||||
For complete documentation, see `roles/paperclip-docker/README.md` after installation.
|
||||
@@ -0,0 +1,136 @@
|
||||
---
|
||||
- name: Install Docker SDK for Python
|
||||
apt:
|
||||
name: python3-docker
|
||||
state: present
|
||||
become: yes
|
||||
|
||||
- name: Download latest docker-compose from GitHub
|
||||
shell: |
|
||||
curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||
chmod +x /usr/local/bin/docker-compose
|
||||
become: yes
|
||||
|
||||
- name: Verify docker-compose installation
|
||||
shell: /usr/local/bin/docker-compose --version
|
||||
register: compose_version
|
||||
become: yes
|
||||
|
||||
- name: Show docker-compose version
|
||||
debug:
|
||||
msg: "{{ compose_version.stdout }}"
|
||||
|
||||
- name: Create paperclip directories
|
||||
file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
mode: '0755'
|
||||
owner: root
|
||||
group: root
|
||||
loop:
|
||||
- "{{ paperclip_base_path }}"
|
||||
- "{{ paperclip_data_dir }}"
|
||||
- "{{ paperclip_certs_dir }}"
|
||||
- "{{ paperclip_base_path }}/pgdata"
|
||||
become: yes
|
||||
|
||||
- name: Generate BETTER_AUTH_SECRET
|
||||
shell: openssl rand -hex 24
|
||||
register: better_auth_secret
|
||||
changed_when: false
|
||||
no_log: yes
|
||||
|
||||
- name: Generate PAPERCLIP_AGENT_JWT_SECRET
|
||||
shell: openssl rand -hex 24
|
||||
register: paperclip_jwt_secret
|
||||
changed_when: false
|
||||
no_log: yes
|
||||
|
||||
- name: Write secrets and configuration to .env
|
||||
copy:
|
||||
content: |
|
||||
# Authentication secrets (auto-generated during deployment)
|
||||
BETTER_AUTH_SECRET={{ better_auth_secret.stdout }}
|
||||
PAPERCLIP_AGENT_JWT_SECRET={{ paperclip_jwt_secret.stdout }}
|
||||
|
||||
# Infrastructure configuration
|
||||
CLOUDFLARE_API_TOKEN={{ vault_cloudflare_api_token }}
|
||||
CERTBOT_EMAIL={{ certbot_email }}
|
||||
PAPERCLIP_DOMAIN={{ paperclip_domain }}
|
||||
VLLM_ENDPOINT={{ vllm_endpoint }}
|
||||
dest: "{{ paperclip_base_path }}/.env"
|
||||
mode: '0600'
|
||||
owner: root
|
||||
group: root
|
||||
become: yes
|
||||
no_log: yes
|
||||
|
||||
- name: Template docker-compose.yml
|
||||
template:
|
||||
src: docker-compose.yml.j2
|
||||
dest: "{{ paperclip_base_path }}/docker-compose.yml"
|
||||
mode: '0644'
|
||||
owner: root
|
||||
group: root
|
||||
become: yes
|
||||
notify: Restart paperclip stack
|
||||
|
||||
- name: Template nginx config
|
||||
template:
|
||||
src: nginx.conf.j2
|
||||
dest: "{{ paperclip_base_path }}/nginx.conf"
|
||||
mode: '0644'
|
||||
owner: root
|
||||
group: root
|
||||
become: yes
|
||||
notify: Reload nginx
|
||||
|
||||
- name: Template certbot init script
|
||||
template:
|
||||
src: certbot-init.sh.j2
|
||||
dest: "{{ paperclip_base_path }}/certbot-init.sh"
|
||||
mode: '0755'
|
||||
owner: root
|
||||
group: root
|
||||
become: yes
|
||||
|
||||
- name: Stop and remove old containers
|
||||
shell: |
|
||||
cd {{ paperclip_base_path }}
|
||||
/usr/local/bin/docker-compose down -v --remove-orphans 2>/dev/null || true
|
||||
become: yes
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Deploy docker compose stack
|
||||
shell: |
|
||||
cd {{ paperclip_base_path }}
|
||||
/usr/local/bin/docker-compose up -d
|
||||
become: yes
|
||||
register: compose_result
|
||||
|
||||
- name: Wait for Paperclip to be ready
|
||||
uri:
|
||||
url: "http://localhost:{{ paperclip_port }}/health"
|
||||
status_code: 200
|
||||
register: result
|
||||
until: result.status == 200
|
||||
retries: 30
|
||||
delay: 5
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Wait for nginx to be ready
|
||||
uri:
|
||||
url: "https://{{ paperclip_domain }}/"
|
||||
validate_certs: no
|
||||
status_code: [200, 301, 302]
|
||||
register: result
|
||||
until: result.status in [200, 301, 302]
|
||||
retries: 30
|
||||
delay: 5
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Verify Paperclip is accessible via https
|
||||
debug:
|
||||
msg: "Paperclip is accessible at https://{{ paperclip_domain }}"
|
||||
when: result.status is defined and result.status in [200, 301, 302]
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
---
|
||||
# Paperclip deployment paths and configuration
|
||||
paperclip_base_path: /opt/paperclip
|
||||
paperclip_data_dir: "{{ paperclip_base_path }}/data"
|
||||
paperclip_certs_dir: "{{ paperclip_base_path }}/certs"
|
||||
paperclip_port: 3100
|
||||
paperclip_domain: muppet-vision.wdw-labs.cloud
|
||||
paperclip_host: 127.0.0.1
|
||||
|
||||
# Paperclip allowed hostnames (space or comma-separated)
|
||||
# Includes: domain name, localhost (for internal/local access), and client IP
|
||||
paperclip_allowed_hostnames: "{{ paperclip_domain }} localhost 192.168.10.50"
|
||||
|
||||
# Deployment mode: authenticated (requires login) or local_trusted (no auth, trusted network)
|
||||
paperclip_deployment_mode: "local_trusted"
|
||||
|
||||
# Reverse proxy configuration
|
||||
nginx_port: 443
|
||||
nginx_http_port: 80
|
||||
|
||||
# HTTPS/SSL configuration
|
||||
certbot_email: admin@wdw-labs.cloud
|
||||
certbot_provider: cloudflare
|
||||
|
||||
# Inference endpoint (vLLM or remote OpenAI-compatible)
|
||||
vllm_endpoint: "http://127.0.0.1:8000"
|
||||
|
||||
# NOTE: Authentication secrets (BETTER_AUTH_SECRET, PAPERCLIP_AGENT_JWT_SECRET)
|
||||
# are auto-generated during deployment and stored in .env (mode 0600)
|
||||
# No need to specify them here — they're created with: openssl rand -hex 24
|
||||
@@ -0,0 +1,19 @@
|
||||
---
|
||||
- name: Restart paperclip stack
|
||||
shell: |
|
||||
cd {{ paperclip_base_path }}
|
||||
/usr/local/bin/docker-compose restart
|
||||
become: yes
|
||||
|
||||
- name: Reload nginx
|
||||
shell: |
|
||||
docker exec paperclip-nginx nginx -s reload
|
||||
become: yes
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Reload certbot
|
||||
shell: |
|
||||
docker restart paperclip-certbot
|
||||
become: yes
|
||||
ignore_errors: yes
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
---
|
||||
- name: Install Docker SDK for Python
|
||||
apt:
|
||||
name: python3-docker
|
||||
state: present
|
||||
become: yes
|
||||
|
||||
- name: Download latest docker-compose from GitHub
|
||||
shell: |
|
||||
curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
|
||||
chmod +x /usr/local/bin/docker-compose
|
||||
become: yes
|
||||
|
||||
- name: Verify docker-compose installation
|
||||
shell: /usr/local/bin/docker-compose --version
|
||||
register: compose_version
|
||||
become: yes
|
||||
|
||||
- name: Show docker-compose version
|
||||
debug:
|
||||
msg: "{{ compose_version.stdout }}"
|
||||
|
||||
- name: Create paperclip directories
|
||||
file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
mode: '0755'
|
||||
owner: root
|
||||
group: root
|
||||
loop:
|
||||
- "{{ paperclip_base_path }}"
|
||||
- "{{ paperclip_data_dir }}"
|
||||
- "{{ paperclip_certs_dir }}"
|
||||
- "{{ paperclip_base_path }}/pgdata"
|
||||
become: yes
|
||||
|
||||
- name: Fix directory ownership for container user (UID 1000)
|
||||
shell: |
|
||||
chown -R 1000:1000 {{ paperclip_data_dir }}
|
||||
chown -R 1000:1000 {{ paperclip_base_path }}/pgdata
|
||||
become: yes
|
||||
|
||||
- name: Generate BETTER_AUTH_SECRET
|
||||
shell: openssl rand -hex 24
|
||||
register: better_auth_secret
|
||||
changed_when: false
|
||||
no_log: yes
|
||||
|
||||
- name: Generate PAPERCLIP_AGENT_JWT_SECRET
|
||||
shell: openssl rand -hex 24
|
||||
register: paperclip_jwt_secret
|
||||
changed_when: false
|
||||
no_log: yes
|
||||
|
||||
- name: Write secrets and configuration to .env
|
||||
copy:
|
||||
content: |
|
||||
# Authentication secrets (auto-generated during deployment)
|
||||
BETTER_AUTH_SECRET={{ better_auth_secret.stdout }}
|
||||
PAPERCLIP_AGENT_JWT_SECRET={{ paperclip_jwt_secret.stdout }}
|
||||
|
||||
# Infrastructure configuration
|
||||
CLOUDFLARE_API_TOKEN={{ vault_cloudflare_api_token }}
|
||||
CERTBOT_EMAIL={{ certbot_email }}
|
||||
PAPERCLIP_DOMAIN={{ paperclip_domain }}
|
||||
VLLM_ENDPOINT={{ vllm_endpoint }}
|
||||
dest: "{{ paperclip_base_path }}/.env"
|
||||
mode: '0600'
|
||||
owner: root
|
||||
group: root
|
||||
become: yes
|
||||
no_log: yes
|
||||
|
||||
- name: Template docker-compose.yml
|
||||
template:
|
||||
src: docker-compose.yml.j2
|
||||
dest: "{{ paperclip_base_path }}/docker-compose.yml"
|
||||
mode: '0644'
|
||||
owner: root
|
||||
group: root
|
||||
become: yes
|
||||
notify: Restart paperclip stack
|
||||
|
||||
- name: Template nginx config
|
||||
template:
|
||||
src: nginx.conf.j2
|
||||
dest: "{{ paperclip_base_path }}/nginx.conf"
|
||||
mode: '0644'
|
||||
owner: root
|
||||
group: root
|
||||
become: yes
|
||||
notify: Reload nginx
|
||||
|
||||
- name: Template certbot init script
|
||||
template:
|
||||
src: certbot-init.sh.j2
|
||||
dest: "{{ paperclip_base_path }}/certbot-init.sh"
|
||||
mode: '0755'
|
||||
owner: root
|
||||
group: root
|
||||
become: yes
|
||||
|
||||
- name: Stop and remove old containers
|
||||
shell: |
|
||||
cd {{ paperclip_base_path }}
|
||||
/usr/local/bin/docker-compose down --remove-orphans 2>/dev/null || true
|
||||
become: yes
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Deploy docker compose stack
|
||||
shell: |
|
||||
cd {{ paperclip_base_path }}
|
||||
/usr/local/bin/docker-compose up -d
|
||||
become: yes
|
||||
register: compose_result
|
||||
|
||||
- name: Wait for Paperclip to be ready
|
||||
uri:
|
||||
url: "http://localhost:{{ paperclip_port }}/health"
|
||||
status_code: 200
|
||||
register: result
|
||||
until: result.status == 200
|
||||
retries: 30
|
||||
delay: 5
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Wait for nginx to be ready
|
||||
uri:
|
||||
url: "https://{{ paperclip_domain }}/"
|
||||
validate_certs: no
|
||||
status_code: [200, 301, 302]
|
||||
register: result
|
||||
until: result.status in [200, 301, 302]
|
||||
retries: 30
|
||||
delay: 5
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Verify Paperclip is accessible via https
|
||||
debug:
|
||||
msg: "Paperclip is accessible at https://{{ paperclip_domain }}"
|
||||
when: result.status is defined and result.status in [200, 301, 302]
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user