Download src/backup.ts from ghuser1/pl: direct link, hf CLI and curl.
- Browser
- Download file 2.84 kB
-
https://huggingface.co/spaces/ghuser1/pl/resolve/main/src/backup.ts
- Command line
-
hf download hf://spaces/ghuser1/pl/src/backup.ts
-
curl -L -o backup.ts https://huggingface.co/spaces/ghuser1/pl/resolve/main/src/backup.ts
2.84 kB
| import { spawn } from 'node:child_process'; | |
| import fs from 'node:fs/promises'; | |
| import os from 'node:os'; | |
| import path from 'node:path'; | |
| import { sqlite } from './db'; | |
| type BackupDisabled = { | |
| enabled: false; | |
| reason: string; | |
| }; | |
| type BackupSuccess = { | |
| enabled: true; | |
| ok: true; | |
| file: string; | |
| remote: string; | |
| }; | |
| type BackupFailure = { | |
| enabled: true; | |
| ok: false; | |
| error: string; | |
| }; | |
| export type BackupResult = BackupDisabled | BackupSuccess | BackupFailure; | |
| const REMOTE = process.env.RCLONE_BACKUP_REMOTE || ''; | |
| const TIMEOUT_MS = Number(process.env.RCLONE_BACKUP_TIMEOUT_MS || 120_000); | |
| const BACKUP_FILE = 'promptlib-latest.sqlite'; | |
| function joinRcloneTarget(remote: string, fileName: string): string { | |
| if (remote.endsWith(':') || remote.endsWith('/')) return `${remote}${fileName}`; | |
| return `${remote}/${fileName}`; | |
| } | |
| function truncateOutput(value: string): string { | |
| return value.length > 4000 ? `${value.slice(0, 4000)}...` : value; | |
| } | |
| function runRclone(args: string[], timeoutMs: number): Promise<void> { | |
| return new Promise((resolve, reject) => { | |
| const child = spawn('rclone', args, { | |
| env: process.env, | |
| stdio: ['ignore', 'pipe', 'pipe'], | |
| }); | |
| let stdout = ''; | |
| let stderr = ''; | |
| const timer = setTimeout(() => { | |
| child.kill('SIGTERM'); | |
| reject(new Error(`rclone timed out after ${timeoutMs}ms`)); | |
| }, timeoutMs); | |
| child.stdout.on('data', (chunk) => { | |
| stdout += String(chunk); | |
| }); | |
| child.stderr.on('data', (chunk) => { | |
| stderr += String(chunk); | |
| }); | |
| child.on('error', (err) => { | |
| clearTimeout(timer); | |
| reject(err); | |
| }); | |
| child.on('close', (code) => { | |
| clearTimeout(timer); | |
| if (code === 0) { | |
| resolve(); | |
| return; | |
| } | |
| const output = truncateOutput([stdout.trim(), stderr.trim()].filter(Boolean).join('\n')); | |
| reject(new Error(`rclone exited with code ${code}${output ? `: ${output}` : ''}`)); | |
| }); | |
| }); | |
| } | |
| export async function backupDatabaseToRclone(): Promise<BackupResult> { | |
| if (!REMOTE) { | |
| return { enabled: false, reason: 'RCLONE_BACKUP_REMOTE is not set' }; | |
| } | |
| const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'promptlib-backup-')); | |
| const fileName = BACKUP_FILE; | |
| const snapshot = path.join(tmpDir, fileName); | |
| const target = joinRcloneTarget(REMOTE, fileName); | |
| try { | |
| await sqlite.backup(snapshot); | |
| await runRclone(['copyto', snapshot, target], TIMEOUT_MS); | |
| console.log('[backup] uploaded', target); | |
| return { enabled: true, ok: true, file: fileName, remote: target }; | |
| } catch (err) { | |
| const message = err instanceof Error ? err.message : String(err); | |
| console.error('[backup] failed', message); | |
| return { enabled: true, ok: false, error: message }; | |
| } finally { | |
| await fs.rm(tmpDir, { recursive: true, force: true }); | |
| } | |
| } | |