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.
11
fork

Configure Feed

Select the types of activity you want to include in your feed.

docs(readme): simplify install paths and Twitter integration

Jack e98f3dee 1110d9b2

+87 -528
+87 -528
README.md
··· 1 1 # tweets-2-bsky 2 - This repo can also be found on Tangled at [j4ck.xyz/tweets2bsky](https://tangled.org/j4ck.xyz/tweets2bsky) 2 + 3 + Cross-post from Twitter/X to Bluesky with thread support, media handling, account mapping, and a web dashboard. 3 4 4 - Crosspost posts from Twitter/X to Bluesky with thread support, media handling, account mapping, and a web dashboard. 5 + This repo is also mirrored on Tangled: [j4ck.xyz/tweets2bsky](https://tangled.org/j4ck.xyz/tweets2bsky) 6 + 7 + ## How It Works (Simple) 8 + 9 + 1. You connect one or more Twitter/X source accounts to a Bluesky account. 10 + 2. The app reads tweets from X using `@the-convocation/twitter-scraper` with your cookies (`auth_token` + `ct0`). 11 + 3. It posts to Bluesky using the official AT Protocol client (`@atproto/api`). 12 + 4. It tracks what was already posted in SQLite so it does not repost duplicates. 13 + 5. A scheduler runs automatically, and you can also trigger `Run now` from the dashboard or CLI. 14 + 15 + ## Installation (Pick One Path) 16 + 17 + Use either: 18 + 19 + - Docker (recommended) 20 + - Source install (PM2 or manual runtime) 5 21 6 - ## Quick Start (Recommended: Docker) 22 + Do not do both on the same machine unless you intentionally want two separate deployments. 7 23 8 - Most people should use Docker. It is the simplest setup and includes full feature parity (backend API + scheduler + frontend dashboard + Chromium). 24 + ### Option A: Docker (Recommended) 9 25 10 - Prerequisite: Docker Desktop (Windows/macOS) or Docker Engine (Linux). On Windows, use Docker Desktop in Linux container mode. 26 + Prerequisite: Docker Desktop (macOS/Windows) or Docker Engine (Linux). 11 27 12 - Prefer using the included compose file so the named volume is always attached: 28 + Start with the included compose file: 13 29 14 30 ```bash 15 31 docker compose up -d 16 32 ``` 17 33 18 - ### 1) Run the latest image 34 + Open `http://localhost:3000`. 19 35 20 - macOS/Linux (bash): 36 + If you prefer `docker run`: 21 37 22 38 ```bash 23 39 docker run -d \ ··· 28 44 j4ckxyz/tweets-2-bsky:latest 29 45 ``` 30 46 31 - Windows (PowerShell): 47 + Important: keep a persistent volume (`-v tweets2bsky_data:/app/data`) so mappings/history survive container recreation. 32 48 33 - ```powershell 34 - docker run -d --name tweets-2-bsky -p 3000:3000 -v tweets2bsky_data:/app/data --restart unless-stopped j4ckxyz/tweets-2-bsky:latest 35 - ``` 36 - 37 - Open `http://localhost:3000`. 38 - 39 - Important: keep `-v tweets2bsky_data:/app/data` (or an equivalent bind mount). Without a persistent volume, mappings and history are lost when the container is recreated. 40 - 41 - If port `3000` is already in use, change only the first port (example: `-p 3001:3000`). 42 - 43 - ### 2) Complete first-time setup 44 - 45 - 1. Register the first user (this user becomes admin). 46 - 2. Add Twitter cookies in Settings. 47 - 3. Add at least one mapping via the guided "Add account" flow (Twitter sources -> Bluesky account -> credential validation -> verify email + create). 48 - 4. Click `Run now`. 49 - 50 - ### 3) Useful Docker commands 49 + Useful Docker commands: 51 50 52 51 ```bash 53 52 docker logs -f tweets-2-bsky ··· 56 55 docker start tweets-2-bsky 57 56 ``` 58 57 59 - ### 4) Update to newest release 58 + Update Docker deployment: 60 59 61 60 ```bash 62 61 docker pull j4ckxyz/tweets-2-bsky:latest ··· 70 69 j4ckxyz/tweets-2-bsky:latest 71 70 ``` 72 71 73 - Alternative image registry: `ghcr.io/j4ckxyz/tweets-2-bsky:latest`. 72 + Alternative image: `ghcr.io/j4ckxyz/tweets-2-bsky:latest`. 74 73 75 - ## Source Install Quick Start (No Docker) 74 + ### Option B: Source Install (PM2 or Manual) 76 75 77 - If you prefer running from source and do not want to manage PM2 manually, use the installer script. 76 + Prerequisites: 78 77 79 - ### 1) Clone the repo 78 + - `git` 79 + - Bun 1.x+ (the installer auto-installs/upgrades Bun when needed) 80 + - PM2 (optional, but recommended for background runtime) 81 + 82 + Clone and install: 80 83 81 84 ```bash 82 85 git clone https://github.com/j4ckxyz/tweets-2-bsky 83 86 cd tweets-2-bsky 84 - ``` 85 - 86 - ### 2) Run install + background start 87 - 88 - ```bash 89 87 chmod +x install.sh 90 88 ./install.sh 91 89 ``` 92 90 93 - What this does by default: 91 + `install.sh` does install/build/start and uses: 94 92 95 - - auto-installs Bun (latest stable for your platform) when missing 96 - - auto-upgrades Bun to latest stable before install/build 97 - - installs dependencies 98 - - builds server + web dashboard 99 - - creates/updates `.env` with sensible defaults (`PORT=3000`, generated `JWT_SECRET` if missing) 100 - - starts in the background 101 - - uses PM2 if installed 102 - - otherwise uses `nohup` 103 - - prints your local web URL (for example `http://localhost:3000`) 93 + - PM2 when PM2 is available 94 + - `nohup` when PM2 is not installed 104 95 105 - ### Useful installer commands 96 + Useful installer commands: 106 97 107 98 ```bash 108 - ./install.sh --no-start 99 + ./install.sh --status 100 + ./install.sh --stop 109 101 ./install.sh --start-only 110 - ./install.sh --stop 111 - ./install.sh --status 102 + ./install.sh --no-start 112 103 ./install.sh --port 3100 113 - ./install.sh --host 127.0.0.1 114 - ./install.sh --skip-native-rebuild 115 104 ``` 116 105 117 - If you prefer full manual source setup details, skip to [Manual Setup](#manual-setup-technical). 118 - 119 - After source install starts, open `http://localhost:3000` and follow the first-time setup steps in [Quick Start](#quick-start-recommended-docker). 120 - 121 - ## Docker (Single-Container, Backend + Frontend + Scheduler) 122 - 123 - This repo now includes a single `Dockerfile` that runs: 124 - 125 - - the backend API 126 - - the scheduler/worker loop 127 - - the built frontend dashboard 128 - - Chromium (for quote-tweet screenshot fallback support) 129 - 130 - The container aims for feature parity with normal installs while giving one-command startup. 131 - 132 - Tip: the repository includes `docker-compose.yml` with a named `tweets2bsky_data` volume, so `docker compose up -d` is the safest default. 133 - 134 - ### 1) Pull and run (recommended) 135 - 136 - After publishing an image (see [Publishing](#publishing-multi-platform-images-linuxamd64--linuxarm64)), run: 137 - 138 - ```bash 139 - docker run -d \ 140 - --name tweets-2-bsky \ 141 - -p 3000:3000 \ 142 - -v tweets2bsky_data:/app/data \ 143 - --restart unless-stopped \ 144 - j4ckxyz/tweets-2-bsky:latest 145 - ``` 146 - 147 - Open `http://localhost:3000`. 148 - 149 - PowerShell equivalent: 150 - 151 - ```powershell 152 - docker run -d --name tweets-2-bsky -p 3000:3000 -v tweets2bsky_data:/app/data --restart unless-stopped j4ckxyz/tweets-2-bsky:latest 153 - ``` 154 - 155 - Alternative registry mirror: `ghcr.io/j4ckxyz/tweets-2-bsky:latest`. 156 - 157 - ### 2) Build locally (if you do not want to pull) 158 - 159 - ```bash 160 - docker build -t tweets-2-bsky:local . 161 - 162 - docker run -d \ 163 - --name tweets-2-bsky \ 164 - -p 3000:3000 \ 165 - -v tweets2bsky_data:/app/data \ 166 - --restart unless-stopped \ 167 - tweets-2-bsky:local 168 - ``` 169 - 170 - ### 3) Environment variables 171 - 172 - Pass environment values with `-e` or `--env-file` (same values as normal install): 173 - 174 - ```bash 175 - docker run -d \ 176 - --name tweets-2-bsky \ 177 - -p 3000:3000 \ 178 - -v tweets2bsky_data:/app/data \ 179 - --env-file .env \ 180 - j4ckxyz/tweets-2-bsky:latest 181 - ``` 182 - 183 - Common variables: 184 - 185 - - `PORT` (default `3000`) 186 - - `JWT_SECRET` (recommended to set explicitly) 187 - - `JWT_EXPIRES_IN` 188 - - `CORS_ALLOWED_ORIGINS` 189 - - `BSKY_APPVIEW_URL` (optional override) 190 - - `SCHEDULED_ACCOUNT_TIMEOUT_MS` (default `480000` / 8 minutes, forces a skip when one source account hangs during scheduled checks) 191 - - `TWEETS2BSKY_DATA_DIR` (default `/app/data` in Docker; keep aligned with your mounted data volume path) 192 - 193 - ### 4) Persistent data inside Docker 194 - 195 - Store all app state in `/app/data` (mounted via volume): 196 - 197 - - `/app/data/config.json` (mappings, users, credentials) 198 - - `/app/data/database.sqlite` 199 - - `/app/data/.jwt-secret` 200 - 201 - The app reads persistent files from `TWEETS2BSKY_DATA_DIR` (defaults to `/app/data` in Docker), so a single mounted volume preserves everything important. 202 - 203 - ### 5) CLI usage in container 204 - 205 - You can run CLI commands without leaving Docker: 106 + #### PM2 Manual Runtime (if you want direct PM2 control) 206 107 207 108 ```bash 208 - docker exec -it tweets-2-bsky bun dist/cli.js status 209 - docker exec -it tweets-2-bsky bun dist/cli.js run-now 210 - docker exec -it tweets-2-bsky bun dist/cli.js list 211 - ``` 212 - 213 - ### 6) Updating Docker deployments 214 - 215 - For Docker installs, update by pulling a newer image and recreating the container with the same volume: 216 - 217 - ```bash 218 - docker pull j4ckxyz/tweets-2-bsky:latest 219 - docker stop tweets-2-bsky 220 - docker rm tweets-2-bsky 221 - docker run -d \ 222 - --name tweets-2-bsky \ 223 - -p 3000:3000 \ 224 - -v tweets2bsky_data:/app/data \ 225 - --restart unless-stopped \ 226 - j4ckxyz/tweets-2-bsky:latest 227 - ``` 228 - 229 - ### 7) Debug logs (especially useful on Raspberry Pi) 230 - 231 - If runs appear stuck, stream logs live: 232 - 233 - ```bash 234 - docker logs -f tweets-2-bsky 235 - ``` 236 - 237 - For source installs, use whichever runtime you started with: 238 - 239 - ```bash 109 + bun install 110 + bun run build 111 + pm2 start "$HOME/.bun/bin/bun" --name tweets-2-bsky --cwd "$PWD" -- dist/index.js 240 112 pm2 logs tweets-2-bsky 241 - # or 242 - tail -f data/runtime/nohup.out 243 - ``` 244 - 245 - If an account hangs during a scheduled cycle, the scheduler now times out that account and moves on automatically. You can tune this with `SCHEDULED_ACCOUNT_TIMEOUT_MS`. 246 - 247 - ### 8) Platform support 248 - 249 - The Docker build is designed for multi-platform images: 250 - 251 - - `linux/amd64` (typical Linux servers, many Windows machines) 252 - - `linux/arm64` (Apple Silicon Macs, ARM Linux servers) 253 - 254 - This means the same image tag can be pulled on Docker Desktop (Windows/macOS) and Linux hosts. 255 - On Windows, use Docker Desktop in **Linux container** mode. 256 - 257 - ### Publishing (multi-platform images: linux/amd64 + linux/arm64) 258 - 259 - Automatic publishing is included via GitHub Actions: 260 - 261 - - `.github/workflows/docker-publish.yml` for GHCR 262 - - `.github/workflows/docker-publish-dockerhub.yml` for Docker Hub (only runs when Docker Hub secrets are set) 263 - 264 - - pushes to `master` or `main` publish fresh multi-arch images and update `:latest` 265 - - tags like `v2.0.0` publish versioned tags (`:2.0.0`, `:2.0`) 266 - - manual publish is available with **Actions -> Publish Docker Image -> Run workflow** 267 - - after first publish, set GHCR package visibility to **Public** so anyone can pull 268 - 269 - To enable automatic Docker Hub publishing with GitHub CLI: 270 - 271 - ```bash 272 - gh secret set DOCKERHUB_USERNAME --body "<dockerhub-username>" 273 - gh secret set DOCKERHUB_TOKEN --body "<dockerhub-access-token>" 113 + pm2 save 274 114 ``` 275 115 276 - Users can always pull the newest build with: 116 + #### Manual Foreground Runtime (no PM2) 277 117 278 118 ```bash 279 - docker pull j4ckxyz/tweets-2-bsky:latest 280 - ``` 281 - 282 - #### Option A: GitHub Container Registry (GHCR) 283 - 284 - ```bash 285 - docker login ghcr.io -u <github-username> 286 - docker buildx create --name t2b-builder --use 287 - docker buildx inspect --bootstrap 288 - 289 - docker buildx build \ 290 - --platform linux/amd64,linux/arm64 \ 291 - -t ghcr.io/j4ckxyz/tweets-2-bsky:latest \ 292 - -t ghcr.io/j4ckxyz/tweets-2-bsky:2.0.0 \ 293 - --push . 294 - ``` 295 - 296 - Then set the GHCR package visibility to **Public** in GitHub package settings. 297 - 298 - #### Option B: Docker Hub 299 - 300 - ```bash 301 - docker login 302 - docker buildx create --name t2b-builder --use 303 - docker buildx inspect --bootstrap 304 - 305 - docker buildx build \ 306 - --platform linux/amd64,linux/arm64 \ 307 - -t <dockerhub-user>/tweets-2-bsky:latest \ 308 - -t <dockerhub-user>/tweets-2-bsky:2.0.0 \ 309 - --push . 310 - ``` 311 - 312 - Once published, users only need `docker pull` + `docker run`. 313 - 314 - ## Linux VPS Without Domain (Secure HTTPS via Tailscale) 315 - 316 - If you host on a public VPS (Linux) and do not own a domain, use the server installer: 317 - 318 - ```bash 319 - chmod +x install-server.sh 320 - ./install-server.sh 321 - ``` 322 - 323 - What this does: 324 - 325 - - runs the normal app install/build/start flow 326 - - auto-selects a free local app port if your chosen/default port is already in use 327 - - forces the app to bind locally only (`HOST=127.0.0.1`) 328 - - installs and starts Tailscale if needed 329 - - configures `tailscale serve` on a free HTTPS port so your dashboard is reachable over Tailnet HTTPS 330 - - prints the final Tailnet URL to open from any device authenticated on your Tailscale account 331 - 332 - Optional non-interactive login: 333 - 334 - ```bash 335 - ./install-server.sh --auth-key <TS_AUTHKEY> 336 - ``` 337 - 338 - Optional fixed Tailscale HTTPS port: 339 - 340 - ```bash 341 - ./install-server.sh --https-port 443 342 - ``` 343 - 344 - Optional public exposure (internet) with Funnel: 345 - 346 - ```bash 347 - ./install-server.sh --funnel 348 - ``` 349 - 350 - Notes: 351 - 352 - - this does **not** replace or delete `install.sh`; it wraps server-hardening around it 353 - - normal updates still use `./update.sh` and keep your local `.env` values 354 - - if you already installed manually, this is still safe to run later 355 - 356 - ## What This Project Does 357 - 358 - - crossposts tweets and threads to Bluesky 359 - - handles images, videos, GIFs, quote tweets, and link cards 360 - - stores processed history in SQLite to avoid reposting 361 - - supports multiple Twitter source usernames per Bluesky target 362 - - provides both: 363 - - web dashboard workflows 364 - - CLI workflows (including cron-friendly mode) 365 - 366 - ## Requirements 367 - 368 - Recommended runtime: 369 - 370 - - Docker Desktop / Docker Engine 371 - 372 - If running from source instead of Docker: 373 - 374 - - Bun 1.x+ (auto-installed/upgraded by `install.sh` and `update.sh`) 375 - - git 376 - 377 - Optional but recommended for source installs: 378 - 379 - - PM2 (for managed background runtime) 380 - - Chrome/Chromium (used for some quote-tweet screenshot fallbacks) 381 - - build tools for native modules (`better-sqlite3`) if your platform needs source compilation 382 - 383 - ## Manual Setup (Technical) 384 - 385 - ### Standard run (foreground) 386 - 387 - ```bash 388 - git clone https://github.com/j4ckxyz/tweets-2-bsky 389 - cd tweets-2-bsky 390 119 bun install 391 120 bun run build 392 121 bun run start 393 122 ``` 394 123 395 - Open: [http://localhost:3000](http://localhost:3000) 396 - 397 - ### Set environment values explicitly 124 + #### Manual Nohup Runtime (no PM2) 398 125 399 126 ```bash 400 - cat > .env <<'EOF' 401 - PORT=3000 402 - JWT_SECRET=replace-with-a-strong-random-secret 403 - # Optional: auth token lifetime (jsonwebtoken format), default is 30d. 404 - # JWT_EXPIRES_IN=30d 405 - # Optional: comma-separated browser origins allowed to call the API. 406 - # Leave unset to allow all origins (default/backward-compatible). 407 - # CORS_ALLOWED_ORIGINS=https://your-tailnet-host.ts.net,https://localhost:3000 408 - EOF 127 + mkdir -p data/runtime 128 + nohup bun run start > data/runtime/tweets-2-bsky.log 2>&1 & 129 + echo $! > data/runtime/tweets-2-bsky.pid 409 130 ``` 410 131 411 - ### Rebuild native modules 132 + Stop nohup process: 412 133 413 134 ```bash 414 - bun run rebuild:native 415 - bun run build 135 + kill "$(cat data/runtime/tweets-2-bsky.pid)" 416 136 ``` 417 137 418 - ## First-Time Setup via CLI (Alternative to Web Forms) 138 + ## First-Time Setup (After Install) 419 139 420 - ```bash 421 - bun run cli -- setup-twitter 422 - bun run cli -- add-mapping 423 - bun run cli -- run-now 424 - ``` 140 + 1. Open `http://localhost:3000`. 141 + 2. Register the first user (this account becomes admin). 142 + 3. In Settings, add Twitter cookies (`auth_token`, `ct0`; backup pair optional). 143 + 4. Add a mapping (Twitter source usernames -> Bluesky account). 144 + 5. Click `Run now`. 425 145 426 - `add-mapping` now runs a guided onboarding flow: 146 + ## Twitter/X Integration Notes 147 + 148 + - This project does not use Twitter's paid official API. 149 + - It uses `@the-convocation/twitter-scraper` and authenticated browser cookies to read account/tweet data. 150 + - Required cookies: `auth_token` and `ct0`. 151 + - If cookies expire, update them in Settings. 152 + - Keep cookies private; they are sensitive credentials. 427 153 428 - 1. enter one or more Twitter source usernames 429 - 2. create Bluesky account (or use existing) 430 - 3. enter Bluesky identifier + app password (+ optional custom PDS URL) 431 - 4. verify email, then create mapping and auto-sync profile metadata from Twitter 154 + For some quote-tweet screenshot fallbacks, Chromium is used (bundled in Docker, optional dependency for source installs). 432 155 433 - ## Recommended Command Examples 156 + ## CLI Quick Commands 434 157 435 - Always invoke CLI commands as: 158 + Always run CLI commands as: 436 159 437 160 ```bash 438 161 bun run cli -- <command> 439 162 ``` 440 163 441 - ### Status and basic operations 164 + Common commands: 442 165 443 166 ```bash 444 167 bun run cli -- status 445 168 bun run cli -- list 446 - bun run cli -- recent-activity --limit 20 447 - ``` 448 - 449 - ### Credentials and configuration 450 - 451 - ```bash 452 - bun run cli -- setup-twitter 453 - bun run cli -- setup-ai 454 - bun run cli -- set-interval 5 455 - ``` 456 - 457 - ### Mapping management 458 - 459 - ```bash 460 - bun run cli -- add-mapping 461 - bun run cli -- sync-profile <mapping-id-or-handle> --source <twitter-username> 462 - bun run cli -- edit-mapping <mapping-id-or-handle> 463 - bun run cli -- remove <mapping-id-or-handle> 464 - ``` 465 - 466 - ### Running syncs 467 - 468 - ```bash 469 169 bun run cli -- run-now 470 170 bun run cli -- run-now --dry-run 471 - bun run cli -- run-now --web 472 - ``` 473 - 474 - ### Backfill and history import 475 - 476 - ```bash 171 + bun run cli -- add-mapping 477 172 bun run cli -- backfill <mapping-id-or-handle> --limit 50 478 - bun run cli -- import-history <mapping-id-or-handle> --limit 100 479 - bun run cli -- clear-cache <mapping-id-or-handle> 480 - ``` 481 - 482 - ### Dangerous operation (admin workflow) 483 - 484 - ```bash 485 - bun run cli -- delete-all-posts <mapping-id-or-handle> 486 - ``` 487 - 488 - ### Config export/import 489 - 490 - ```bash 491 - bun run cli -- config-export ./tweets-2-bsky-config.json 492 - bun run cli -- config-import ./tweets-2-bsky-config.json 493 - ``` 494 - 495 - Mapping references accept: 496 - 497 - - mapping ID 498 - - Bluesky handle/identifier 499 - - Twitter username 500 - 501 - ## Cron / CLI-Only Operation 502 - 503 - Run every 5 minutes: 504 - 505 - ```cron 506 - */5 * * * * cd /path/to/tweets-2-bsky && /usr/local/bin/bun run cli -- run-now >> /tmp/tweets-2-bsky.log 2>&1 507 - ``` 508 - 509 - Run one backfill once: 510 - 511 - ```bash 512 - bun run cli -- backfill <mapping-id-or-handle> --limit 50 513 - ``` 514 - 515 - ## Background Runtime Options 516 - 517 - ### Option A: use `install.sh` (recommended) 518 - 519 - ```bash 520 - ./install.sh 521 - ./install.sh --status 522 - ./install.sh --stop 523 - ``` 524 - 525 - ### Option B: manage PM2 directly 526 - 527 - ```bash 528 - pm2 start "$HOME/.bun/bin/bun" --name tweets-2-bsky --cwd "$PWD" -- dist/index.js 529 - pm2 logs tweets-2-bsky 530 - pm2 restart tweets-2-bsky --update-env 531 - pm2 save 532 - ``` 533 - 534 - Do not use `--interpreter bun` with `dist/index.js` on PM2 installs that cannot `require()` async ESM modules. Use Bun as the process command instead (example above). 535 - 536 - ### PM2 migration help (older manual installs) 537 - 538 - If you manually created PM2 processes on older versions, migrate once to the Bun binary launcher: 539 - 540 - ```bash 541 - pm2 delete tweets-2-bsky || true 542 - pm2 delete twitter-mirror || true 543 - pm2 start "$HOME/.bun/bin/bun" --name tweets-2-bsky --cwd "$PWD" -- dist/index.js 544 - pm2 save 545 - ``` 546 - 547 - If your existing process must keep the legacy name: 548 - 549 - ```bash 550 - pm2 start "$HOME/.bun/bin/bun" --name twitter-mirror --cwd "$PWD" -- dist/index.js 551 - ``` 552 - 553 - ### Option C: no PM2 (nohup) 554 - 555 - ```bash 556 - mkdir -p data/runtime 557 - nohup bun run start > data/runtime/tweets-2-bsky.log 2>&1 & 558 - echo $! > data/runtime/tweets-2-bsky.pid 559 - ``` 560 - 561 - Stop nohup process: 562 - 563 - ```bash 564 - kill "$(cat data/runtime/tweets-2-bsky.pid)" 565 173 ``` 566 174 567 175 ## Updating 568 176 569 - Use: 177 + Source installs: 570 178 571 179 ```bash 572 180 ./update.sh 573 181 ``` 574 182 575 - `update.sh`: 576 - 577 - - stashes local uncommitted changes before pull and restores them after update 578 - - pulls latest code (supports non-`origin` remotes and detached-head recovery) 579 - - ensures Bun is installed and upgraded to latest stable 580 - - installs dependencies 581 - - rebuilds native modules when runtime/dependencies changed 582 - - builds server + web dashboard 583 - - restarts existing runtime for PM2 **or** nohup mode 584 - - normalizes PM2 runtime to Bun binary launcher mode (avoids Bun interpreter crash loops on some PM2 builds) 585 - - preserves local `config.json` and `.env` with backup/restore 586 - 587 - Useful update flags: 183 + Useful flags: 588 184 589 185 ```bash 590 186 ./update.sh --no-restart 591 187 ./update.sh --skip-install --skip-build 592 - ./update.sh --remote origin --branch main 593 188 ``` 594 189 595 - ## Data, Config, and Security 596 - 597 - Local files: 598 - 599 - - `config.json`: mappings, credentials, users, app settings (sensitive; do not share) 600 - - `data/database.sqlite`: processed tweet history and metadata 601 - - `data/.jwt-secret`: auto-generated local JWT signing key when `JWT_SECRET` is not set (sensitive; keep private) 602 - - `.env`: runtime environment variables (`PORT`, `JWT_SECRET`, `JWT_EXPIRES_IN`, optional overrides) 190 + ## Data and Security 603 191 604 - Security notes: 192 + Important files: 605 193 606 - - first registered dashboard user is admin 607 - - after bootstrap, only admins can create additional dashboard users 608 - - users can sign in with username or email 609 - - non-admin users only see mappings they created by default 610 - - admins can grant fine-grained permissions (view all mappings, manage groups, queue backfills, run-now, etc.) 611 - - only admins can view or edit Twitter/AI provider credentials 612 - - admin user management never exposes other users' password hashes in the UI 613 - - if `JWT_SECRET` is missing, server generates and persists a strong secret in `data/.jwt-secret` so sessions survive restarts 614 - - set `JWT_SECRET` in `.env` if you prefer explicit secret management across hosts 615 - - auth tokens default to `30d` expiry (`JWT_EXPIRES_IN`), configurable via `.env` 616 - - auth endpoints (`/api/login`, `/api/register`) are rate-limited per IP to reduce brute-force risk 617 - - prefer Bluesky app passwords (not your full account password) 194 + - `config.json` (mappings, credentials, users) 195 + - `data/database.sqlite` (processed history) 196 + - `data/.jwt-secret` (generated signing key when `JWT_SECRET` is unset) 197 + - `.env` (runtime env values) 618 198 619 - ### Multi-User Access Control 199 + Security basics: 620 200 621 - - bootstrap account: 622 - - the first account created through the web UI becomes admin 623 - - open registration is automatically disabled after this 624 - - admin capabilities: 625 - - create, edit, reset password, and delete dashboard users 626 - - assign role (`admin` or `user`) and per-user permissions 627 - - filter the Accounts page by creator to review each user's mappings 628 - - deleting a user: 629 - - disables that user's mappings so crossposting stops 630 - - leaves already-published Bluesky posts untouched 631 - - self-service security: 632 - - every user can change their own password 633 - - users can change their own email after password verification 201 + - First registered user becomes admin. 202 + - Prefer Bluesky app passwords instead of your full Bluesky password. 203 + - Set an explicit `JWT_SECRET` in `.env` for predictable secret management. 204 + - Keep `config.json`, cookie values, and `.env` private. 634 205 635 206 ## Development 636 - 637 - ### Start backend/scheduler from source 638 207 639 208 ```bash 640 209 bun run dev 641 - ``` 642 - 643 - ### Start Vite web dev server 644 - 645 - ```bash 646 210 bun run dev:web 647 - ``` 648 - 649 - ### Build and quality checks 650 - 651 - ```bash 652 211 bun run build 653 212 bun run typecheck 654 213 bun run lint ··· 656 215 657 216 ## Troubleshooting 658 217 659 - See: `TROUBLESHOOTING.md` 218 + See `TROUBLESHOOTING.md`. 660 219 661 - Common recovery when native modules fail to load: 220 + Common native module recovery: 662 221 663 222 ```bash 664 223 bun run rebuild:native