A simple tool which lets you scrape twitter accounts and crosspost them to bluesky accounts! Comes with a CLI and a webapp for managing profiles! Works with images/videos/link embeds/threads.
1# tweets-2-bsky
2
3Crosspost posts from Twitter/X to Bluesky with thread support, media handling, account mapping, and a web dashboard.
4
5## Quick Start (Easy Mode)
6
7If you are comfortable with terminal basics but do not want to manage PM2 manually, use the installer script.
8
9### 1) Clone the repo
10
11```bash
12git clone https://github.com/j4ckxyz/tweets-2-bsky
13cd tweets-2-bsky
14```
15
16### 2) Run install + background start
17
18```bash
19chmod +x install.sh
20./install.sh
21```
22
23What this does by default:
24
25- installs dependencies
26- builds server + web dashboard
27- creates/updates `.env` with sensible defaults (`PORT=3000`, generated `JWT_SECRET` if missing)
28- starts in the background
29 - uses PM2 if installed
30 - otherwise uses `nohup`
31- prints your local web URL (for example `http://localhost:3000`)
32
33### 3) Open the dashboard
34
35Open the printed URL in your browser, then:
36
371. Register the first user (this user becomes admin).
382. Add Twitter cookies in Settings.
393. Add at least one mapping.
404. Click `Run now`.
41
42### Useful installer commands
43
44```bash
45./install.sh --no-start
46./install.sh --start-only
47./install.sh --stop
48./install.sh --status
49./install.sh --port 3100
50./install.sh --host 127.0.0.1
51./install.sh --skip-native-rebuild
52```
53
54If you prefer full manual setup, skip to [Manual Setup](#manual-setup-technical).
55
56## Linux VPS Without Domain (Secure HTTPS via Tailscale)
57
58If you host on a public VPS (Linux) and do not own a domain, use the server installer:
59
60```bash
61chmod +x install-server.sh
62./install-server.sh
63```
64
65What this does:
66
67- runs the normal app install/build/start flow
68- auto-selects a free local app port if your chosen/default port is already in use
69- forces the app to bind locally only (`HOST=127.0.0.1`)
70- installs and starts Tailscale if needed
71- configures `tailscale serve` on a free HTTPS port so your dashboard is reachable over Tailnet HTTPS
72- prints the final Tailnet URL to open from any device authenticated on your Tailscale account
73
74Optional non-interactive login:
75
76```bash
77./install-server.sh --auth-key <TS_AUTHKEY>
78```
79
80Optional fixed Tailscale HTTPS port:
81
82```bash
83./install-server.sh --https-port 443
84```
85
86Optional public exposure (internet) with Funnel:
87
88```bash
89./install-server.sh --funnel
90```
91
92Notes:
93
94- this does **not** replace or delete `install.sh`; it wraps server-hardening around it
95- normal updates still use `./update.sh` and keep your local `.env` values
96- if you already installed manually, this is still safe to run later
97
98## What This Project Does
99
100- crossposts tweets and threads to Bluesky
101- handles images, videos, GIFs, quote tweets, and link cards
102- stores processed history in SQLite to avoid reposting
103- supports multiple Twitter source usernames per Bluesky target
104- provides both:
105 - web dashboard workflows
106 - CLI workflows (including cron-friendly mode)
107
108## Requirements
109
110- Node.js 22+
111- npm
112- git
113
114Optional but recommended:
115
116- PM2 (for managed background runtime)
117- Chrome/Chromium (used for some quote-tweet screenshot fallbacks)
118- build tools for native modules (`better-sqlite3`) if your platform needs source compilation
119
120## Manual Setup (Technical)
121
122### Standard run (foreground)
123
124```bash
125git clone https://github.com/j4ckxyz/tweets-2-bsky
126cd tweets-2-bsky
127npm install
128npm run build
129npm start
130```
131
132Open: [http://localhost:3000](http://localhost:3000)
133
134### Set environment values explicitly
135
136```bash
137cat > .env <<'EOF'
138PORT=3000
139JWT_SECRET=replace-with-a-strong-random-secret
140EOF
141```
142
143### Rebuild native modules after Node version changes
144
145```bash
146npm run rebuild:native
147npm run build
148```
149
150## First-Time Setup via CLI (Alternative to Web Forms)
151
152```bash
153npm run cli -- setup-twitter
154npm run cli -- add-mapping
155npm run cli -- run-now
156```
157
158## Recommended Command Examples
159
160Always invoke CLI commands as:
161
162```bash
163npm run cli -- <command>
164```
165
166### Status and basic operations
167
168```bash
169npm run cli -- status
170npm run cli -- list
171npm run cli -- recent-activity --limit 20
172```
173
174### Credentials and configuration
175
176```bash
177npm run cli -- setup-twitter
178npm run cli -- setup-ai
179npm run cli -- set-interval 5
180```
181
182### Mapping management
183
184```bash
185npm run cli -- add-mapping
186npm run cli -- edit-mapping <mapping-id-or-handle>
187npm run cli -- remove <mapping-id-or-handle>
188```
189
190### Running syncs
191
192```bash
193npm run cli -- run-now
194npm run cli -- run-now --dry-run
195npm run cli -- run-now --web
196```
197
198### Backfill and history import
199
200```bash
201npm run cli -- backfill <mapping-id-or-handle> --limit 50
202npm run cli -- import-history <mapping-id-or-handle> --limit 100
203npm run cli -- clear-cache <mapping-id-or-handle>
204```
205
206### Dangerous operation (admin workflow)
207
208```bash
209npm run cli -- delete-all-posts <mapping-id-or-handle>
210```
211
212### Config export/import
213
214```bash
215npm run cli -- config-export ./tweets-2-bsky-config.json
216npm run cli -- config-import ./tweets-2-bsky-config.json
217```
218
219Mapping references accept:
220
221- mapping ID
222- Bluesky handle/identifier
223- Twitter username
224
225## Cron / CLI-Only Operation
226
227Run every 5 minutes:
228
229```cron
230*/5 * * * * cd /path/to/tweets-2-bsky && /usr/bin/npm run cli -- run-now >> /tmp/tweets-2-bsky.log 2>&1
231```
232
233Run one backfill once:
234
235```bash
236npm run cli -- backfill <mapping-id-or-handle> --limit 50
237```
238
239## Background Runtime Options
240
241### Option A: use `install.sh` (recommended)
242
243```bash
244./install.sh
245./install.sh --status
246./install.sh --stop
247```
248
249### Option B: manage PM2 directly
250
251```bash
252pm2 start dist/index.js --name tweets-2-bsky
253pm2 logs tweets-2-bsky
254pm2 restart tweets-2-bsky --update-env
255pm2 save
256```
257
258### Option C: no PM2 (nohup)
259
260```bash
261mkdir -p data/runtime
262nohup npm start > data/runtime/tweets-2-bsky.log 2>&1 &
263echo $! > data/runtime/tweets-2-bsky.pid
264```
265
266Stop nohup process:
267
268```bash
269kill "$(cat data/runtime/tweets-2-bsky.pid)"
270```
271
272## Updating
273
274Use:
275
276```bash
277./update.sh
278```
279
280`update.sh`:
281
282- stashes local uncommitted changes before pull and restores them after update
283- pulls latest code (supports non-`origin` remotes and detached-head recovery)
284- installs dependencies
285- rebuilds native modules when Node ABI changed
286- builds server + web dashboard
287- restarts existing runtime for PM2 **or** nohup mode
288- preserves local `config.json` and `.env` with backup/restore
289
290Useful update flags:
291
292```bash
293./update.sh --no-restart
294./update.sh --skip-install --skip-build
295./update.sh --remote origin --branch main
296```
297
298## Data, Config, and Security
299
300Local files:
301
302- `config.json`: mappings, credentials, users, app settings (sensitive; do not share)
303- `data/database.sqlite`: processed tweet history and metadata
304- `.env`: runtime environment variables (`PORT`, `JWT_SECRET`, optional overrides)
305
306Security notes:
307
308- first registered dashboard user is admin
309- after bootstrap, only admins can create additional dashboard users
310- users can sign in with username or email
311- non-admin users only see mappings they created by default
312- admins can grant fine-grained permissions (view all mappings, manage groups, queue backfills, run-now, etc.)
313- only admins can view or edit Twitter/AI provider credentials
314- admin user management never exposes other users' password hashes in the UI
315- if `JWT_SECRET` is missing, server falls back to an insecure default; set your own secret in `.env`
316- prefer Bluesky app passwords (not your full account password)
317
318### Multi-User Access Control
319
320- bootstrap account:
321 - the first account created through the web UI becomes admin
322 - open registration is automatically disabled after this
323- admin capabilities:
324 - create, edit, reset password, and delete dashboard users
325 - assign role (`admin` or `user`) and per-user permissions
326 - filter the Accounts page by creator to review each user's mappings
327- deleting a user:
328 - disables that user's mappings so crossposting stops
329 - leaves already-published Bluesky posts untouched
330- self-service security:
331 - every user can change their own password
332 - users can change their own email after password verification
333
334## Development
335
336### Start backend/scheduler from source
337
338```bash
339npm run dev
340```
341
342### Start Vite web dev server
343
344```bash
345npm run dev:web
346```
347
348### Build and quality checks
349
350```bash
351npm run build
352npm run typecheck
353npm run lint
354```
355
356## Troubleshooting
357
358See: `TROUBLESHOOTING.md`
359
360Common recovery after changing Node versions:
361
362```bash
363npm run rebuild:native
364npm run build
365npm start
366```
367
368## License
369
370MIT