Migrate files from Slack (originally served via Vercel CDN) to Hetzner Object Storage, preserving the original Vercel URL structure.
┌─────────────┐ ┌───────────┐ ┌─────────────────┐
│ Slack API │ ───► │ SQLite │ ───► │ Hetzner S3 │
│ (Ruby) │ │ state.db │ │ (Go workers) │
└─────────────┘ └───────────┘ └─────────────────┘
scrape.rb ▲ main.go
│ │ │
Search for Resumable Download from
Vercel URLs, state Slack, upload
map to Slack to Hetzner
files
- Copy
.env.exampleto.envand fill in your credentials:
cp .env.example .env- Install Ruby dependencies:
bundle install- Build the Go migrator:
go build -o migrate .# Test first
ruby scrape.rb --limit 100 --dry-run
# Full scrape
ruby scrape.rbThis will:
- Search Slack for messages containing
*-hack-club-bot.vercel.appURLs - Find the parent message with the original Slack files
- Map each Vercel URL to its corresponding Slack file (by index)
- Store in
state.dbwith statuspending
./migrateThis will:
- Read pending URLs from
state.db - Download files from Slack (with auth, concurrent workers)
- Upload to Hetzner S3 (streaming, memory-efficient)
- Preserve Vercel URL structure in S3 keys (e.g.,
cloud-abc123/0filename.png) - Update status in
state.db - Export
url_mapping.csvwhen complete
Just run ./migrate again. It will:
- Reset any stuck
processingfiles topending - Skip already
completefiles - Retry
errorfiles (change status topendingin DB if needed)
sqlite3 state.db "SELECT status, COUNT(*) FROM files GROUP BY status;"sqlite3 state.db "UPDATE files SET status = 'pending' WHERE status = 'error';"
./migratestate.db- SQLite database with all stateurl_mapping.csv- CSV with columns:vercel_url,hetzner_url,status
| Variable | Description |
|---|---|
SLACK_BOT_TOKEN |
Slack user token (xoxp) with search:read, channels:history, files:read scopes |
SLACK_CHANNEL_ID |
Channel ID to scrape (default: C016DEDUL87) |
HETZNER_ENDPOINT |
S3 endpoint (e.g., https://fsn1.your-objectstorage.com) |
HETZNER_ACCESS_KEY |
Access key |
HETZNER_SECRET_KEY |
Secret key |
HETZNER_BUCKET_NAME |
Bucket name |
HETZNER_PUBLIC_URL |
Public URL prefix for the bucket |
DOWNLOAD_WORKERS |
Number of concurrent download/upload workers (default: 10) |