somratpro Claude Haiku 4.5 commited on
Commit
7c90063
Β·
1 Parent(s): 39d8167

Simplify logs to match Hugging8n style; fix Agent JWT missing

Browse files

- start.sh: remove ANSI colors, ASCII art, step numbers; add simple
box header + key-value config summary (Hugging8n style); silence
pg_isready/psql stdout; generate and persist PAPERCLIP_AGENT_JWT_SECRET
alongside BETTER_AUTH_SECRET so Paperclip startup shows "ready"
- paperclip-sync.py: suppress httpx + huggingface_hub logger noise;
remove === dividers from backup/restore log output

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>

Files changed (2) hide show
  1. paperclip-sync.py +2 -4
  2. start.sh +98 -201
paperclip-sync.py CHANGED
@@ -31,6 +31,8 @@ logging.basicConfig(
31
  format='%(asctime)s - %(levelname)s - %(message)s'
32
  )
33
  logger = logging.getLogger(__name__)
 
 
34
 
35
  # Environment variables
36
  HF_TOKEN = os.environ.get('HF_TOKEN')
@@ -389,9 +391,7 @@ def sync_from_hf() -> bool:
389
 
390
  def sync_to_backup() -> bool:
391
  """Full backup operation: dump DB β†’ create tarball β†’ upload to HF"""
392
- logger.info('=' * 60)
393
  logger.info('Starting backup operation')
394
- logger.info('=' * 60)
395
 
396
  status = read_status()
397
 
@@ -439,9 +439,7 @@ def sync_to_backup() -> bool:
439
 
440
  def sync_from_backup() -> bool:
441
  """Full restore operation: download from HF β†’ extract β†’ restore DB"""
442
- logger.info('=' * 60)
443
  logger.info('Starting restore operation')
444
- logger.info('=' * 60)
445
 
446
  status = read_status()
447
 
 
31
  format='%(asctime)s - %(levelname)s - %(message)s'
32
  )
33
  logger = logging.getLogger(__name__)
34
+ logging.getLogger("httpx").setLevel(logging.WARNING)
35
+ logging.getLogger("huggingface_hub").setLevel(logging.WARNING)
36
 
37
  # Environment variables
38
  HF_TOKEN = os.environ.get('HF_TOKEN')
 
391
 
392
  def sync_to_backup() -> bool:
393
  """Full backup operation: dump DB β†’ create tarball β†’ upload to HF"""
 
394
  logger.info('Starting backup operation')
 
395
 
396
  status = read_status()
397
 
 
439
 
440
  def sync_from_backup() -> bool:
441
  """Full restore operation: download from HF β†’ extract β†’ restore DB"""
 
442
  logger.info('Starting restore operation')
 
443
 
444
  status = read_status()
445
 
start.sh CHANGED
@@ -1,50 +1,9 @@
1
  #!/bin/bash
2
- set -e
3
 
4
  umask 0077
5
 
6
- # Colors for output
7
- RED='\033[0;31m'
8
- GREEN='\033[0;32m'
9
- YELLOW='\033[1;33m'
10
- BLUE='\033[0;34m'
11
- NC='\033[0m' # No Color
12
-
13
- # Banner
14
- echo -e "${BLUE}"
15
- cat << 'EOF'
16
- ___ ___ _____ _ _
17
- / _ \/ _ \___ __________/ ___/| (_)____
18
- / ___/ ___/ _ `/ ___/ ___/\__ \ | | / __ \
19
- / / / / / /_/ / / / /__/__/ / | | / /_/ /
20
- \_/ \_/ \__,_/_/ \___/____/ |_|_/ .___/
21
- /_/
22
- EOF
23
- echo -e "${NC}${GREEN}Starting HuggingClip (Paperclip on HF Spaces)${NC}\n"
24
-
25
- # ============================================================================
26
- # 1. Validate Environment Variables
27
- # ============================================================================
28
- echo -e "${BLUE}[1/8] Validating environment variables...${NC}"
29
-
30
- REQUIRED_VARS=("HF_TOKEN")
31
- MISSING_VARS=()
32
-
33
- for var in "${REQUIRED_VARS[@]}"; do
34
- if [ -z "${!var}" ]; then
35
- MISSING_VARS+=("$var")
36
- fi
37
- done
38
-
39
- if [ ${#MISSING_VARS[@]} -gt 0 ]; then
40
- echo -e "${YELLOW}Warning: Missing env vars: ${MISSING_VARS[*]}${NC}"
41
- echo -e "${YELLOW}Backup to HF Dataset will be disabled${NC}"
42
- SYNC_DISABLED=true
43
- else
44
- SYNC_DISABLED=false
45
- fi
46
-
47
- # Default values
48
  export DATABASE_URL="${DATABASE_URL:-postgres://postgres:paperclip@localhost:5432/paperclip}"
49
  export PORT="${PORT:-3100}"
50
  export SERVE_UI="${SERVE_UI:-true}"
@@ -52,118 +11,119 @@ export NODE_ENV="${NODE_ENV:-production}"
52
  export HOST="${HOST:-0.0.0.0}"
53
  export PAPERCLIP_HOME="${PAPERCLIP_HOME:-/paperclip}"
54
  export PAPERCLIP_DEPLOYMENT_MODE="${PAPERCLIP_DEPLOYMENT_MODE:-authenticated}"
 
 
 
 
 
 
55
  export SYNC_INTERVAL="${SYNC_INTERVAL:-180}"
56
  export SYNC_MAX_FILE_BYTES="${SYNC_MAX_FILE_BYTES:-52428800}"
57
  export BACKUP_DATASET_NAME="${BACKUP_DATASET_NAME:-paperclip-backup}"
58
- export PAPERCLIP_TELEMETRY_DISABLED="${PAPERCLIP_TELEMETRY_DISABLED:-1}"
59
- export DO_NOT_TRACK="${DO_NOT_TRACK:-1}"
60
 
61
- # Derive public URL from HF Space host (auto-set by HF Spaces runtime)
62
- if [ -z "${PAPERCLIP_PUBLIC_URL}" ] && [ -n "${SPACE_HOST}" ]; then
63
  export PAPERCLIP_PUBLIC_URL="https://${SPACE_HOST}"
64
  fi
65
 
66
- # Allow hostnames via env var (no CLI needed, comma-separated)
67
- # Includes localhost, 0.0.0.0, and the HF Space public hostname
68
  _ALLOWED="localhost,127.0.0.1,0.0.0.0"
69
- if [ -n "${SPACE_HOST}" ]; then
70
  _ALLOWED="${_ALLOWED},${SPACE_HOST}"
71
  fi
72
  export PAPERCLIP_ALLOWED_HOSTNAMES="${PAPERCLIP_ALLOWED_HOSTNAMES:-${_ALLOWED}}"
73
 
74
- # Auto-generate BETTER_AUTH_SECRET if not provided
75
- # User-set secret (HF Space secret) always takes precedence
76
- AUTH_SECRET_FILE="${PAPERCLIP_HOME}/.auth-secret"
 
 
77
  mkdir -p "${PAPERCLIP_HOME}"
78
- if [ -z "${BETTER_AUTH_SECRET}" ]; then
 
 
 
79
  if [ -f "${AUTH_SECRET_FILE}" ]; then
80
- # Reuse previously generated secret (persists across restarts)
81
  export BETTER_AUTH_SECRET=$(cat "${AUTH_SECRET_FILE}")
82
- echo -e "${YELLOW}Using persisted auth secret from ${AUTH_SECRET_FILE}${NC}"
83
  else
84
- # First boot β€” generate and save
85
  export BETTER_AUTH_SECRET=$(openssl rand -base64 32)
86
  echo "${BETTER_AUTH_SECRET}" > "${AUTH_SECRET_FILE}"
87
  chmod 600 "${AUTH_SECRET_FILE}"
88
- echo -e "${YELLOW}Generated new auth secret (saved to ${AUTH_SECRET_FILE})${NC}"
89
  fi
90
- else
91
- echo -e "${GREEN}Using BETTER_AUTH_SECRET from environment${NC}"
92
  fi
93
 
94
- echo -e "${GREEN}βœ“ Environment validated${NC}\n"
95
-
96
- # ============================================================================
97
- # 2. Initialize PostgreSQL
98
- # ============================================================================
99
- echo -e "${BLUE}[2/8] Setting up PostgreSQL database...${NC}"
 
 
 
 
100
 
101
- # Detect installed PostgreSQL version
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  PG_VERSION=$(ls /usr/lib/postgresql/ 2>/dev/null | sort -V | tail -1)
103
  if [ -z "$PG_VERSION" ]; then
104
- echo -e "${RED}ERROR: PostgreSQL not found${NC}"
105
  exit 1
106
  fi
107
  PG_DATA="/var/lib/postgresql/${PG_VERSION}/main"
108
- echo "PostgreSQL version: ${PG_VERSION}, data dir: ${PG_DATA}"
109
 
110
- # Initialize cluster if it doesn't exist yet
111
  if [ ! -f "${PG_DATA}/PG_VERSION" ]; then
112
  echo "Initializing PostgreSQL cluster..."
113
- pg_createcluster "${PG_VERSION}" main --locale=C.UTF-8
114
  fi
115
 
116
- # Start cluster if not running
117
  if ! pg_ctlcluster "${PG_VERSION}" main status 2>/dev/null | grep -q "online"; then
118
- echo "Starting PostgreSQL cluster..."
119
- pg_ctlcluster "${PG_VERSION}" main start
120
  fi
121
 
122
- # Wait until ready
123
- until pg_isready -h localhost -U postgres 2>/dev/null; do
124
  sleep 1
125
  done
126
 
127
- # Set postgres password and create paperclip DB (must run as postgres OS user β€” peer auth)
128
- su - postgres -c "psql -c \"ALTER USER postgres WITH PASSWORD 'paperclip';\"" 2>/dev/null || true
129
- su - postgres -c "psql -tc \"SELECT 1 FROM pg_database WHERE datname = 'paperclip'\" | grep -q 1 || \
130
- psql -c \"CREATE DATABASE paperclip OWNER postgres;\"" 2>/dev/null || true
131
-
132
- # Export correct DATABASE_URL with detected version credentials
133
- export DATABASE_URL="${DATABASE_URL:-postgres://postgres:paperclip@localhost:5432/paperclip}"
134
-
135
- echo -e "${GREEN}βœ“ PostgreSQL ready${NC}\n"
136
 
137
- # ============================================================================
138
- # 3. Restore from HF Dataset Backup
139
- # ============================================================================
140
- echo -e "${BLUE}[3/8] Restoring database from HF Dataset backup...${NC}"
141
 
142
- if [ "$SYNC_DISABLED" = false ]; then
 
 
143
  python3 /app/paperclip-sync.py restore 2>&1 || true
144
- echo -e "${GREEN}βœ“ Restore attempt completed${NC}\n"
145
  else
146
- echo -e "${YELLOW}Skipping restore (no HF_TOKEN)${NC}\n"
147
  fi
148
 
149
- # ============================================================================
150
- # 4. Setup Cloudflare Proxy (if token provided)
151
- # ============================================================================
152
- if [ -n "$CLOUDFLARE_WORKERS_TOKEN" ] && [ -n "$CLOUDFLARE_ACCOUNT_ID" ]; then
153
- echo -e "${BLUE}[4/8] Setting up Cloudflare proxy...${NC}"
154
- python3 /app/cloudflare-proxy-setup.py 2>&1 || echo -e "${YELLOW}Cloudflare setup failed, continuing without proxy${NC}"
155
- echo ""
156
- else
157
- echo -e "${BLUE}[4/8] Cloudflare proxy (skipped - no credentials)${NC}\n"
158
  fi
159
 
160
- # ============================================================================
161
- # 5. Start Background Sync Loop
162
- # ============================================================================
163
- echo -e "${BLUE}[5/8] Starting database sync loop...${NC}"
164
 
165
- if [ "$SYNC_DISABLED" = false ]; then
166
- # Start sync in background
167
  (
168
  while true; do
169
  sleep "$SYNC_INTERVAL"
@@ -171,67 +131,25 @@ if [ "$SYNC_DISABLED" = false ]; then
171
  done
172
  ) &
173
  SYNC_PID=$!
174
- echo -e "${GREEN}βœ“ Sync loop started (PID: $SYNC_PID)${NC}\n"
175
  else
176
- echo -e "${YELLOW}Sync disabled (no HF_TOKEN)${NC}\n"
177
- fi
178
-
179
- # ============================================================================
180
- # 6. Start Health Server
181
- # ============================================================================
182
- echo -e "${BLUE}[6/8] Starting health server on port 7861...${NC}"
183
-
184
- # Load Cloudflare proxy if available
185
- if [ -f /app/cloudflare-proxy.js ]; then
186
- export NODE_OPTIONS="--require /app/cloudflare-proxy.js"
187
  fi
188
 
 
189
  node /app/health-server.js &
190
  HEALTH_PID=$!
191
- echo -e "${GREEN}βœ“ Health server started (PID: $HEALTH_PID)${NC}\n"
192
-
193
- # Wait for health server to start
194
  sleep 2
195
 
196
- # ============================================================================
197
- # 7. Launch Paperclip
198
- # ============================================================================
199
- echo -e "${BLUE}[7/8] Launching Paperclip application...${NC}"
200
-
201
  cd /app/paperclip
202
 
203
- # Install Paperclip dependencies if needed
204
  if [ ! -d "node_modules" ]; then
205
  echo "Installing Paperclip dependencies..."
206
  pnpm install 2>&1 | tail -5 || npm install 2>&1 | tail -5
207
  fi
208
 
209
- # Run Paperclip
210
- export DATABASE_URL
211
- export PORT
212
- export SERVE_UI
213
- export NODE_ENV
214
- export HOST
215
- export PAPERCLIP_HOME
216
- export PAPERCLIP_DEPLOYMENT_MODE
217
- export PAPERCLIP_TELEMETRY_DISABLED
218
- export DO_NOT_TRACK
219
- export PAPERCLIP_DEPLOYMENT_EXPOSURE="${PAPERCLIP_DEPLOYMENT_EXPOSURE:-private}"
220
- export PAPERCLIP_INSTANCE_ID="${PAPERCLIP_INSTANCE_ID:-default}"
221
- export PAPERCLIP_CONFIG="${PAPERCLIP_CONFIG:-${PAPERCLIP_HOME}/instances/default/config.json}"
222
- export OPENCODE_ALLOW_ALL_MODELS="${OPENCODE_ALLOW_ALL_MODELS:-true}"
223
- export PAPERCLIP_ALLOWED_HOSTNAMES
224
- export PAPERCLIP_PUBLIC_URL
225
- # Pass LLM API keys through to Paperclip adapters and sub-processes
226
- export GEMINI_API_KEY="${GEMINI_API_KEY:-}"
227
- export ANTHROPIC_API_KEY="${ANTHROPIC_API_KEY:-${CLAUDE_API_KEY:-}}"
228
- export OPENAI_API_KEY="${OPENAI_API_KEY:-}"
229
-
230
- # Create Paperclip instance config.json if it doesn't exist.
231
- # Required by bootstrap-ceo CLI to find DB and generate the admin invite URL.
232
- # Skipped when a config was already restored from HF Dataset backup.
233
  if [ ! -f "${PAPERCLIP_CONFIG}" ]; then
234
- echo "Creating Paperclip instance config (first boot)..."
235
  mkdir -p "$(dirname "${PAPERCLIP_CONFIG}")"
236
  python3 <<'PYEOF'
237
  import json, os
@@ -279,84 +197,63 @@ with open(config_path, "w") as f:
279
  json.dump(config, f, indent=2)
280
  print(f" Config written to {config_path}")
281
  PYEOF
282
- echo -e "${GREEN}βœ“ Instance config created${NC}"
283
- else
284
- echo -e "${GREEN}βœ“ Instance config found at ${PAPERCLIP_CONFIG}${NC}"
285
  fi
286
 
287
- echo -e "${GREEN}βœ“ All systems ready${NC}"
288
- echo -e "${GREEN}═══════════════════════════════════════════${NC}"
289
- echo -e " Health Dashboard: http://localhost:7861/"
290
- echo -e " Paperclip UI: http://localhost:7861/app/"
291
- echo -e " API Endpoint: http://localhost:7861/api/*"
292
- echo -e "${GREEN}═══════════════════════════════════════════${NC}\n"
293
-
294
- # ============================================================================
295
- # 8. Graceful Shutdown Handler
296
- # ============================================================================
297
  cleanup() {
298
- echo -e "\n${YELLOW}[SHUTDOWN] Received termination signal...${NC}"
299
- echo "Syncing data to HF Dataset..."
300
-
301
- if [ "$SYNC_DISABLED" = false ]; then
302
  python3 /app/paperclip-sync.py sync 2>&1 || true
303
  fi
304
-
305
- echo "Stopping services..."
306
- [ -n "$HEALTH_PID" ] && kill $HEALTH_PID 2>/dev/null || true
307
- [ -n "$SYNC_PID" ] && kill $SYNC_PID 2>/dev/null || true
308
- [ -n "$PAPERCLIP_PID" ] && kill $PAPERCLIP_PID 2>/dev/null || true
309
-
310
- echo -e "${GREEN}Shutdown complete${NC}"
311
  exit 0
312
  }
313
-
314
  trap cleanup SIGTERM SIGINT
315
 
316
- # ============================================================================
317
- # 8. Start Paperclip, wait for init, then bootstrap admin
318
- # ============================================================================
319
- echo -e "${BLUE}[8/8] Starting Paperclip server...${NC}"
320
-
321
- # Start Paperclip in background so we can run post-init steps
322
  node --import ./server/node_modules/tsx/dist/loader.mjs server/dist/index.js &
323
  PAPERCLIP_PID=$!
324
 
325
- # Wait for Paperclip API to be ready β€” use 127.0.0.1 to avoid IPv6 issues (max 90s)
326
- echo "Waiting for Paperclip to initialize..."
327
  PAPERCLIP_READY=false
328
  for i in $(seq 1 45); do
329
  if curl -sf http://127.0.0.1:3100/api/health >/dev/null 2>&1; then
330
- echo -e "${GREEN}βœ“ Paperclip ready (${i}s)${NC}"
331
  PAPERCLIP_READY=true
332
  break
333
  fi
334
  sleep 2
335
  done
336
 
337
- # Bootstrap first admin β€” generates invite URL if no admin exists yet
338
  if [ "$PAPERCLIP_READY" = true ]; then
339
- echo -e "${BLUE}Bootstrapping admin account...${NC}"
340
  BOOTSTRAP_OUTPUT=$(pnpm paperclipai auth bootstrap-ceo 2>&1 || true)
341
- # Extract URL from the specific "Invite URL: ..." line, stripping ANSI color codes
342
  INVITE_URL=$(echo "$BOOTSTRAP_OUTPUT" | grep "Invite URL:" | sed 's/\x1B\[[0-9;]*[a-zA-Z]//g' | grep -o 'https\?://[^ ]*' | head -1)
343
  if [ -n "$INVITE_URL" ]; then
344
- # Save invite URL for health dashboard to display
345
  echo "$INVITE_URL" > /tmp/invite-url.txt
346
- echo -e "${GREEN}╔══════════════════════════════════════════════════════════╗${NC}"
347
- echo -e "${GREEN}β•‘ ADMIN SETUP β€” open this URL in your browser: β•‘${NC}"
348
- echo -e "${GREEN}β•‘ β•‘${NC}"
349
- echo -e " ${INVITE_URL}"
350
- echo -e "${GREEN}β•‘ β•‘${NC}"
351
- echo -e "${GREEN}β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•${NC}"
 
 
352
  else
353
- # Clear any stale invite URL file
354
  rm -f /tmp/invite-url.txt
355
- echo -e "${GREEN}βœ“ Admin account already configured${NC}"
356
  fi
357
  else
358
- echo -e "${YELLOW}Warning: Paperclip did not become ready in 90s${NC}"
359
  fi
360
 
361
- # Keep container alive β€” wait for Paperclip process to exit
 
 
 
 
 
 
362
  wait $PAPERCLIP_PID
 
1
  #!/bin/bash
2
+ set -euo pipefail
3
 
4
  umask 0077
5
 
6
+ # ── Config ────────────────────────────────────────────────────────────────────
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  export DATABASE_URL="${DATABASE_URL:-postgres://postgres:paperclip@localhost:5432/paperclip}"
8
  export PORT="${PORT:-3100}"
9
  export SERVE_UI="${SERVE_UI:-true}"
 
11
  export HOST="${HOST:-0.0.0.0}"
12
  export PAPERCLIP_HOME="${PAPERCLIP_HOME:-/paperclip}"
13
  export PAPERCLIP_DEPLOYMENT_MODE="${PAPERCLIP_DEPLOYMENT_MODE:-authenticated}"
14
+ export PAPERCLIP_DEPLOYMENT_EXPOSURE="${PAPERCLIP_DEPLOYMENT_EXPOSURE:-private}"
15
+ export PAPERCLIP_INSTANCE_ID="${PAPERCLIP_INSTANCE_ID:-default}"
16
+ export PAPERCLIP_CONFIG="${PAPERCLIP_CONFIG:-${PAPERCLIP_HOME}/instances/default/config.json}"
17
+ export PAPERCLIP_TELEMETRY_DISABLED="${PAPERCLIP_TELEMETRY_DISABLED:-1}"
18
+ export DO_NOT_TRACK="${DO_NOT_TRACK:-1}"
19
+ export OPENCODE_ALLOW_ALL_MODELS="${OPENCODE_ALLOW_ALL_MODELS:-true}"
20
  export SYNC_INTERVAL="${SYNC_INTERVAL:-180}"
21
  export SYNC_MAX_FILE_BYTES="${SYNC_MAX_FILE_BYTES:-52428800}"
22
  export BACKUP_DATASET_NAME="${BACKUP_DATASET_NAME:-paperclip-backup}"
 
 
23
 
24
+ # Derive public URL from HF Space host
25
+ if [ -z "${PAPERCLIP_PUBLIC_URL:-}" ] && [ -n "${SPACE_HOST:-}" ]; then
26
  export PAPERCLIP_PUBLIC_URL="https://${SPACE_HOST}"
27
  fi
28
 
29
+ # Allowed hostnames
 
30
  _ALLOWED="localhost,127.0.0.1,0.0.0.0"
31
+ if [ -n "${SPACE_HOST:-}" ]; then
32
  _ALLOWED="${_ALLOWED},${SPACE_HOST}"
33
  fi
34
  export PAPERCLIP_ALLOWED_HOSTNAMES="${PAPERCLIP_ALLOWED_HOSTNAMES:-${_ALLOWED}}"
35
 
36
+ # LLM API keys
37
+ export GEMINI_API_KEY="${GEMINI_API_KEY:-}"
38
+ export ANTHROPIC_API_KEY="${ANTHROPIC_API_KEY:-${CLAUDE_API_KEY:-}}"
39
+ export OPENAI_API_KEY="${OPENAI_API_KEY:-}"
40
+
41
  mkdir -p "${PAPERCLIP_HOME}"
42
+
43
+ # Auth secrets (generate + persist so they survive restarts)
44
+ AUTH_SECRET_FILE="${PAPERCLIP_HOME}/.auth-secret"
45
+ if [ -z "${BETTER_AUTH_SECRET:-}" ]; then
46
  if [ -f "${AUTH_SECRET_FILE}" ]; then
 
47
  export BETTER_AUTH_SECRET=$(cat "${AUTH_SECRET_FILE}")
 
48
  else
 
49
  export BETTER_AUTH_SECRET=$(openssl rand -base64 32)
50
  echo "${BETTER_AUTH_SECRET}" > "${AUTH_SECRET_FILE}"
51
  chmod 600 "${AUTH_SECRET_FILE}"
 
52
  fi
 
 
53
  fi
54
 
55
+ JWT_SECRET_FILE="${PAPERCLIP_HOME}/.jwt-secret"
56
+ if [ -z "${PAPERCLIP_AGENT_JWT_SECRET:-}" ]; then
57
+ if [ -f "${JWT_SECRET_FILE}" ]; then
58
+ export PAPERCLIP_AGENT_JWT_SECRET=$(cat "${JWT_SECRET_FILE}")
59
+ else
60
+ export PAPERCLIP_AGENT_JWT_SECRET=$(openssl rand -base64 32)
61
+ echo "${PAPERCLIP_AGENT_JWT_SECRET}" > "${JWT_SECRET_FILE}"
62
+ chmod 600 "${JWT_SECRET_FILE}"
63
+ fi
64
+ fi
65
 
66
+ # ── Banner ────────────────────────────────────────────────────────────────────
67
+ echo ""
68
+ echo " ╔════════════════════════════════════╗"
69
+ echo " β•‘ HuggingClip β•‘"
70
+ echo " β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•"
71
+ echo ""
72
+ echo "Public host : ${SPACE_HOST:-not detected}"
73
+ echo "Public URL : ${PAPERCLIP_PUBLIC_URL:-http://localhost:${PORT}}"
74
+ echo "App port : ${PORT}"
75
+ echo "Deploy mode : ${PAPERCLIP_DEPLOYMENT_MODE}"
76
+ echo "Sync every : ${SYNC_INTERVAL}s"
77
+ echo ""
78
+
79
+ # ── PostgreSQL ────────────────────────────────────────────────────────────────
80
  PG_VERSION=$(ls /usr/lib/postgresql/ 2>/dev/null | sort -V | tail -1)
81
  if [ -z "$PG_VERSION" ]; then
82
+ echo "ERROR: PostgreSQL not found"
83
  exit 1
84
  fi
85
  PG_DATA="/var/lib/postgresql/${PG_VERSION}/main"
 
86
 
 
87
  if [ ! -f "${PG_DATA}/PG_VERSION" ]; then
88
  echo "Initializing PostgreSQL cluster..."
89
+ pg_createcluster "${PG_VERSION}" main --locale=C.UTF-8 >/dev/null 2>&1
90
  fi
91
 
 
92
  if ! pg_ctlcluster "${PG_VERSION}" main status 2>/dev/null | grep -q "online"; then
93
+ echo "Starting PostgreSQL..."
94
+ pg_ctlcluster "${PG_VERSION}" main start >/dev/null 2>&1
95
  fi
96
 
97
+ until pg_isready -h localhost -U postgres >/dev/null 2>&1; do
 
98
  sleep 1
99
  done
100
 
101
+ su - postgres -c "psql -c \"ALTER USER postgres WITH PASSWORD 'paperclip';\"" >/dev/null 2>&1 || true
102
+ su - postgres -c "psql -tc \"SELECT 1 FROM pg_database WHERE datname = 'paperclip'\" | grep -q 1 || psql -c \"CREATE DATABASE paperclip OWNER postgres;\"" >/dev/null 2>&1 || true
 
 
 
 
 
 
 
103
 
104
+ echo "PostgreSQL ready (v${PG_VERSION})"
 
 
 
105
 
106
+ # ── Restore from HF Dataset ───────────────────────────────────────────────────
107
+ if [ -n "${HF_TOKEN:-}" ]; then
108
+ echo "Restoring persisted data from HF Dataset..."
109
  python3 /app/paperclip-sync.py restore 2>&1 || true
 
110
  else
111
+ echo "HF_TOKEN not set β€” running without backup persistence"
112
  fi
113
 
114
+ # ── Cloudflare Proxy ──────────────────────────────────────────────────────────
115
+ if [ -n "${CLOUDFLARE_WORKERS_TOKEN:-}" ] && [ -n "${CLOUDFLARE_ACCOUNT_ID:-}" ]; then
116
+ echo "Setting up Cloudflare proxy..."
117
+ python3 /app/cloudflare-proxy-setup.py 2>&1 || echo "Cloudflare setup failed, continuing without proxy"
 
 
 
 
 
118
  fi
119
 
120
+ # ── Load Cloudflare module if present ─────────────────────────────────────────
121
+ if [ -f /app/cloudflare-proxy.js ]; then
122
+ export NODE_OPTIONS="--require /app/cloudflare-proxy.js"
123
+ fi
124
 
125
+ # ── Background sync loop ──────────────────────────────────────────────────────
126
+ if [ -n "${HF_TOKEN:-}" ]; then
127
  (
128
  while true; do
129
  sleep "$SYNC_INTERVAL"
 
131
  done
132
  ) &
133
  SYNC_PID=$!
 
134
  else
135
+ SYNC_PID=""
 
 
 
 
 
 
 
 
 
 
136
  fi
137
 
138
+ # ── Health server ─────────────────────────────────────────────────────────────
139
  node /app/health-server.js &
140
  HEALTH_PID=$!
 
 
 
141
  sleep 2
142
 
143
+ # ── Paperclip instance config ─────────────────────────────────────────────────
 
 
 
 
144
  cd /app/paperclip
145
 
 
146
  if [ ! -d "node_modules" ]; then
147
  echo "Installing Paperclip dependencies..."
148
  pnpm install 2>&1 | tail -5 || npm install 2>&1 | tail -5
149
  fi
150
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
  if [ ! -f "${PAPERCLIP_CONFIG}" ]; then
152
+ echo "Creating instance config (first boot)..."
153
  mkdir -p "$(dirname "${PAPERCLIP_CONFIG}")"
154
  python3 <<'PYEOF'
155
  import json, os
 
197
  json.dump(config, f, indent=2)
198
  print(f" Config written to {config_path}")
199
  PYEOF
 
 
 
200
  fi
201
 
202
+ # ── Graceful shutdown ─────────────────────────────────────────────────────────
 
 
 
 
 
 
 
 
 
203
  cleanup() {
204
+ echo "Shutting down β€” syncing data..."
205
+ if [ -n "${HF_TOKEN:-}" ]; then
 
 
206
  python3 /app/paperclip-sync.py sync 2>&1 || true
207
  fi
208
+ [ -n "${HEALTH_PID:-}" ] && kill "$HEALTH_PID" 2>/dev/null || true
209
+ [ -n "${SYNC_PID:-}" ] && kill "$SYNC_PID" 2>/dev/null || true
210
+ [ -n "${PAPERCLIP_PID:-}" ] && kill "$PAPERCLIP_PID" 2>/dev/null || true
 
 
 
 
211
  exit 0
212
  }
 
213
  trap cleanup SIGTERM SIGINT
214
 
215
+ # ── Launch Paperclip ──────────────────────────────────────────────────────────
216
+ echo "Starting Paperclip..."
 
 
 
 
217
  node --import ./server/node_modules/tsx/dist/loader.mjs server/dist/index.js &
218
  PAPERCLIP_PID=$!
219
 
220
+ # Wait for API ready (max 90s)
 
221
  PAPERCLIP_READY=false
222
  for i in $(seq 1 45); do
223
  if curl -sf http://127.0.0.1:3100/api/health >/dev/null 2>&1; then
224
+ echo "Paperclip ready (${i}s)"
225
  PAPERCLIP_READY=true
226
  break
227
  fi
228
  sleep 2
229
  done
230
 
 
231
  if [ "$PAPERCLIP_READY" = true ]; then
 
232
  BOOTSTRAP_OUTPUT=$(pnpm paperclipai auth bootstrap-ceo 2>&1 || true)
 
233
  INVITE_URL=$(echo "$BOOTSTRAP_OUTPUT" | grep "Invite URL:" | sed 's/\x1B\[[0-9;]*[a-zA-Z]//g' | grep -o 'https\?://[^ ]*' | head -1)
234
  if [ -n "$INVITE_URL" ]; then
 
235
  echo "$INVITE_URL" > /tmp/invite-url.txt
236
+ echo ""
237
+ echo " β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”"
238
+ echo " β”‚ ADMIN SETUP β€” open this URL in your browser: β”‚"
239
+ echo " β”‚ β”‚"
240
+ echo " β”‚ ${INVITE_URL}"
241
+ echo " β”‚ β”‚"
242
+ echo " β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜"
243
+ echo ""
244
  else
 
245
  rm -f /tmp/invite-url.txt
246
+ echo "Admin account already configured"
247
  fi
248
  else
249
+ echo "Warning: Paperclip did not become ready in 90s"
250
  fi
251
 
252
+ echo "HuggingClip is ready!"
253
+ echo ""
254
+ echo " Health dashboard : http://localhost:7861/"
255
+ echo " Paperclip UI : http://localhost:7861/app/"
256
+ echo " API : http://localhost:7861/api/"
257
+ echo ""
258
+
259
  wait $PAPERCLIP_PID