Skip to content

Instantly share code, notes, and snippets.

@smartynov
Last active April 25, 2026 21:48
Show Gist options
  • Select an option

  • Save smartynov/8908a5fefefe2c93bfe72054e5f9aeb8 to your computer and use it in GitHub Desktop.

Select an option

Save smartynov/8908a5fefefe2c93bfe72054e5f9aeb8 to your computer and use it in GitHub Desktop.
Paperclip auto install
#!/usr/bin/env bash
# setup-paperclip.sh — Install Paperclip AI with Caddy reverse proxy, basic auth, and systemd
#
# Usage:
# sudo bash setup-paperclip.sh <domain> <auth_user> <auth_password> <run_as_user>
#
# Example:
# sudo bash setup-paperclip.sh paperclip.example.com admin 's3cureP@ss!' deploy
#
# What this does:
# 1. Installs Node.js 22 LTS + pnpm (via corepack)
# 2. Installs Caddy (auto-HTTPS via Let's Encrypt)
# 3. Clones Paperclip into ~<run_as_user>/paperclip/
# 4. Runs onboard to create config + embedded PostgreSQL
# 5. Creates a systemd service (User=<run_as_user>, auto-restart, starts on boot)
# 6. Configures Caddy as a reverse proxy with HTTP basic auth
# 7. Disables Paperclip telemetry
#
# Paperclip runs in local_trusted mode (localhost only).
# Caddy handles HTTPS termination and basic auth in front of it.
#
# The service runs as <run_as_user> so that agent CLIs (Claude Code, Codex)
# share the same user, HOME, PATH, and auth tokens. After install:
#
# 1. SSH as <run_as_user> and install an agent runtime:
# Claude Code: curl -fsSL https://claude.ai/install.sh | bash && claude login
# Codex: npm i -g @openai/codex && codex
# 2. Create a workspace directory, e.g. ~/workspaces/my-project/
# 3. Open https://<domain>, enter basic auth, complete the onboarding wizard
# 4. When creating an agent, set Working Directory to ~/workspaces/my-project/
#
# Prerequisites:
# - Ubuntu 22.04+ or Debian 12+
# - DNS A record for <domain> pointing to this server
# - Ports 80 and 443 open (Let's Encrypt + HTTPS)
# - <run_as_user> must already exist on the system
# - Run this script as root
#
# Management:
# systemctl status paperclip
# systemctl restart paperclip
# journalctl -u paperclip -f
#
# Update:
# cd ~/paperclip && git pull && pnpm install && sudo systemctl restart paperclip
set -euo pipefail
# ----- Arguments -----
DOMAIN="${1:?Usage: sudo bash setup-paperclip.sh <domain> <auth_user> <auth_password> <run_as_user>}"
AUTH_USER="${2:?Missing argument: auth_user}"
AUTH_PASS="${3:?Missing argument: auth_password}"
RUN_USER="${4:?Missing argument: run_as_user (e.g. deploy, ubuntu, sm)}"
PAPERCLIP_PORT=3100
if [ "$(id -u)" -ne 0 ]; then
echo "Error: this script must be run as root."
exit 1
fi
# Resolve user's home directory
if ! id "$RUN_USER" &>/dev/null; then
echo "Error: user '${RUN_USER}' does not exist."
exit 1
fi
USER_HOME=$(getent passwd "$RUN_USER" | cut -d: -f6)
PAPERCLIP_DIR="${USER_HOME}/paperclip"
# Suppress corepack download prompts
export COREPACK_ENABLE_DOWNLOAD_PROMPT=0
echo "============================================"
echo " Paperclip setup"
echo " Domain: ${DOMAIN}"
echo " Auth: ${AUTH_USER}"
echo " Run as: ${RUN_USER} (${USER_HOME})"
echo " Install: ${PAPERCLIP_DIR}"
echo "============================================"
echo ""
# ----- 1. Node.js 22 LTS -----
echo "--- [1/7] Node.js ---"
if command -v node &>/dev/null && node -v | grep -qE "^v2[2-9]"; then
echo "OK: Node.js $(node -v) already installed"
else
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
apt-get install -y nodejs
echo "OK: Node.js $(node -v) installed"
fi
corepack enable
corepack prepare pnpm@latest --activate 2>/dev/null || true
echo "OK: pnpm $(pnpm -v)"
echo ""
# ----- 2. Caddy -----
echo "--- [2/7] Caddy ---"
if command -v caddy &>/dev/null; then
echo "OK: Caddy already installed"
else
apt-get install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \
| gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \
| tee /etc/apt/sources.list.d/caddy-stable.list
apt-get update
apt-get install -y caddy
echo "OK: Caddy installed"
fi
echo ""
# ----- 3. Clone + install -----
echo "--- [3/7] Paperclip ---"
if [ -d "${PAPERCLIP_DIR}/.git" ]; then
echo "Updating existing installation..."
cd "${PAPERCLIP_DIR}"
sudo -u "$RUN_USER" git pull
else
echo "Cloning repository..."
sudo -u "$RUN_USER" git clone https://github.com/paperclipai/paperclip.git "${PAPERCLIP_DIR}"
cd "${PAPERCLIP_DIR}"
fi
echo "Installing dependencies (this may take a few minutes)..."
sudo -u "$RUN_USER" \
COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \
pnpm install
echo "OK: Paperclip ready"
echo ""
# ----- 4. Workspaces directory -----
echo "--- [4/7] Workspaces ---"
WORKSPACES="${USER_HOME}/workspaces"
if [ ! -d "$WORKSPACES" ]; then
sudo -u "$RUN_USER" mkdir -p "$WORKSPACES"
fi
echo "OK: ${WORKSPACES}"
echo ""
# ----- 5. Onboard + allowed hostname -----
echo "--- [5/7] Onboard ---"
CONFIG_FILE="${USER_HOME}/.paperclip/instances/default/config.json"
if [ -f "$CONFIG_FILE" ]; then
echo "OK: config already exists, skipping onboard"
else
echo "Running onboard (creates config + embedded PostgreSQL)..."
# onboard --yes creates config then starts the server (blocking).
# Run in background, wait for health check, then stop it.
sudo -u "$RUN_USER" bash -c "
cd ${PAPERCLIP_DIR} && \
COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \
PAPERCLIP_TELEMETRY_DISABLED=1 \
DO_NOT_TRACK=1 \
HOME=${USER_HOME} \
pnpm paperclipai onboard --yes
" &
ONBOARD_PID=$!
echo "Waiting for server to start..."
for i in $(seq 1 45); do
if curl -sf http://127.0.0.1:${PAPERCLIP_PORT}/api/health >/dev/null 2>&1; then
echo "OK: server is running"
break
fi
if ! kill -0 "$ONBOARD_PID" 2>/dev/null; then
echo "ERROR: onboard process exited unexpectedly."
echo " Try manually: sudo -u ${RUN_USER} bash -c 'cd ${PAPERCLIP_DIR} && pnpm paperclipai onboard --yes'"
exit 1
fi
if [ "$i" -eq 45 ]; then
echo "ERROR: server didn't start after 90s."
kill "$ONBOARD_PID" 2>/dev/null || true
exit 1
fi
sleep 2
done
# Stop the onboard server (systemd will take over)
kill "$ONBOARD_PID" 2>/dev/null || true
wait "$ONBOARD_PID" 2>/dev/null || true
sleep 2
echo "OK: onboard complete, server stopped"
fi
# Add domain as allowed hostname (local_trusted mode blocks unknown hosts)
echo "Adding '${DOMAIN}' as allowed hostname..."
sudo -u "$RUN_USER" bash -c "
cd ${PAPERCLIP_DIR} && \
COREPACK_ENABLE_DOWNLOAD_PROMPT=0 \
HOME=${USER_HOME} \
pnpm paperclipai allowed-hostname ${DOMAIN}
" || echo "WARNING: could not add hostname (add manually: pnpm paperclipai allowed-hostname ${DOMAIN})"
echo ""
# ----- 6. Systemd service -----
echo "--- [6/7] Systemd service ---"
cat > /etc/systemd/system/paperclip.service << EOF
[Unit]
Description=Paperclip AI Agent Orchestration
After=network.target
[Service]
Type=simple
User=${RUN_USER}
WorkingDirectory=${PAPERCLIP_DIR}
Environment=HOME=${USER_HOME}
Environment=COREPACK_ENABLE_DOWNLOAD_PROMPT=0
Environment=PAPERCLIP_TELEMETRY_DISABLED=1
Environment=DO_NOT_TRACK=1
Environment=NODE_ENV=production
Environment=PATH=${USER_HOME}/.local/bin:/usr/local/bin:/usr/bin:/bin
ExecStart=/usr/bin/pnpm paperclipai run
Restart=always
RestartSec=5
LimitNOFILE=65536
TimeoutStopSec=30
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable paperclip
echo "OK: systemd service created and enabled"
echo ""
# ----- 7. Caddy config -----
echo "--- [7/7] Caddy reverse proxy ---"
HASHED_PASS=$(caddy hash-password --plaintext "$AUTH_PASS")
cat > /etc/caddy/Caddyfile << EOF
${DOMAIN} {
basicauth * {
${AUTH_USER} ${HASHED_PASS}
}
reverse_proxy localhost:${PAPERCLIP_PORT}
encode gzip
log {
output file /var/log/caddy/${DOMAIN}.log {
roll_size 10mb
roll_keep 5
}
}
}
EOF
mkdir -p /var/log/caddy
systemctl restart caddy
echo "OK: Caddy configured for https://${DOMAIN}"
echo ""
# ----- Start -----
echo "--- Starting Paperclip ---"
systemctl start paperclip
for i in $(seq 1 30); do
if curl -sf http://127.0.0.1:${PAPERCLIP_PORT}/api/health >/dev/null 2>&1; then
echo "OK: Paperclip is running (health check passed)"
break
fi
if [ "$i" -eq 30 ]; then
echo "WARNING: health check didn't pass after 60s."
echo " Check logs: journalctl -u paperclip -f"
fi
sleep 2
done
echo ""
echo "============================================"
echo " Installation complete!"
echo "============================================"
echo ""
echo " URL: https://${DOMAIN}"
echo " User: ${AUTH_USER}"
echo " Pass: (as provided)"
echo ""
echo " Data: ${USER_HOME}/.paperclip/instances/default/"
echo " Repo: ${PAPERCLIP_DIR}"
echo " Work: ${WORKSPACES}/"
echo ""
echo " Telemetry: DISABLED"
echo ""
echo "--------------------------------------------"
echo " NEXT STEPS (as ${RUN_USER}, not root):"
echo ""
echo " 1. Install an agent runtime:"
echo ""
echo " Claude Code:"
echo " curl -fsSL https://claude.ai/install.sh | bash"
echo " claude login"
echo ""
echo " Codex (OpenAI):"
echo " npm i -g @openai/codex"
echo " codex"
echo ""
echo " 2. Create a workspace:"
echo " mkdir -p ~/workspaces/my-project"
echo ""
echo " 3. Open https://${DOMAIN} and complete"
echo " the onboarding wizard. When creating"
echo " an agent, set Working Directory to:"
echo " ${WORKSPACES}/my-project"
echo "--------------------------------------------"
echo ""
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment