{"id":30600588,"url":"https://github.com/titansage02/pairqr","last_synced_at":"2026-05-20T07:03:03.956Z","repository":{"id":310726992,"uuid":"1038566528","full_name":"TitanSage02/PairQR","owner":"TitanSage02","description":"PairQR is an instant secure texts sharing app that uses QR code pairing for direct peer-to-peer transfers between devices with end-to-end encryption and no registration required.","archived":false,"fork":false,"pushed_at":"2025-08-19T20:53:54.000Z","size":548,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-08-19T22:26:31.413Z","etag":null,"topics":["instant-share","privacy","webrtc"],"latest_commit_sha":null,"homepage":"https://pairqr.app","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/TitanSage02.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-08-15T12:48:16.000Z","updated_at":"2025-08-19T20:53:57.000Z","dependencies_parsed_at":"2025-08-19T22:26:43.281Z","dependency_job_id":null,"html_url":"https://github.com/TitanSage02/PairQR","commit_stats":null,"previous_names":["titansage02/pairqr"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/TitanSage02/PairQR","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TitanSage02%2FPairQR","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TitanSage02%2FPairQR/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TitanSage02%2FPairQR/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TitanSage02%2FPairQR/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/TitanSage02","download_url":"https://codeload.github.com/TitanSage02/PairQR/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TitanSage02%2FPairQR/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":272782943,"owners_count":24992348,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-29T02:00:10.610Z","response_time":87,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["instant-share","privacy","webrtc"],"created_at":"2025-08-29T23:35:34.360Z","updated_at":"2026-05-20T07:03:03.860Z","avatar_url":"https://github.com/TitanSage02.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# PairQR — Instant, Encrypted Sharing\n\n\u003e 🔒 Share text and files peer-to-peer with end-to-end encryption and QR code pairing.\n\n**PairQR** is a privacy-first platform that spins up ephemeral sessions to exchange text and files with ease. Pair via **QR code**, connect peers over **WebRTC**, and keep data **encrypted client-side**.\n\n---\n\n## ✨ Features\n\n* **End-to-end encryption** — data is encrypted in the browser; the server can’t read it\n* **QR code pairing** — create a session with a simple scan\n* **Real-time** — secure P2P messaging and file transfer with WebRTC\n* **Ephemeral sessions** — no persistence \n* **Admin dashboard** — basic management \u0026 metrics\n* **Cross-platform** — works on modern browsers\n\n---\n\n## 🧱 Monorepo Architecture\n\n```\nPairQR/\n├─ client/              # React + Vite (TypeScript)\n│  └─ ...\n├─ server/              # Node/Express (TypeScript)\n│  ├─ src/*.ts\n│  ├─ docker-compose.yml    # prod stack: caddy, api, redis, postgres, coturn\n│  ├─ Caddyfile             # reverse proxy + TLS\n│  └─ ...\n├─ shared/              # Shared types \u0026 schemas (TS/Zod)\n└─ scripts/             # Scripts (e.g., start.sh)\n```\n\n**Client**: React 18, TypeScript, Vite, Tailwind, shadcn/ui, TanStack Query, QR (jsqr)\n**Server**: Node.js + Express, Drizzle ORM + PostgreSQL, WebSocket (ws), JWT (admin), Helmet, CORS, rate-limit\n**P2P**: WebRTC; **STUN/TURN** via **coturn** for NAT traversal\n**Prod**: Docker Compose (Caddy TLS proxy, API, Redis, Postgres, coturn), GitHub Actions (CI/CD), DNS (Cloudflare/Vercel/Registrar)\n\n---\n\n## 🚀 Local Development\n\n\u003e Requirements: **Node.js 18+**, **npm** (or pnpm/yarn), and **PostgreSQL** if you use DB features.\n\n```bash\n# Install deps at the repo root (or per package)\nnpm install\n\n# Frontend\ncd client\nnpm run dev\n\n# Backend\ncd ../server\n# configure server/.env (see below)\nnpm run dev\n```\n\n---\n\n## 🔧 Configuration\n\nCreate **`server/.env`** (minimal example):\n\n```env\n# Runtime\nNODE_ENV=production\nPORT=3000\n\n# Secrets (use strong values)\nADMIN_PASSWORD=change_me\nHMAC_SECRET=change_me\nJWT_SECRET=change_me\nTURN_STATIC_SECRET=change_me\n\n# CORS (comma-separated; ideally no spaces)\nCORS_ORIGIN=https://pairqr.vercel.app,https://pairqr.app\n\n# Internal services\nREDIS_URL=redis://redis:6379\nREDIS_TTL_SECONDS=300\nDATABASE_URL=postgres://postgres:postgres@postgres:5432/pairqr\nTURN_REALM=pairqr\nTURN_URIS=turn:coturn:3478?transport=udp,turn:coturn:3478?transport=tcp\n```\n\n\u003e **Heads-up**: if any secret contains **`$`**, escape it as **`$$`** in `.env` (Docker Compose treats `$VAR` as interpolation).\n\n---\n\n## 🐳 Production (Docker Compose)\n\nThe prod stack (in `server/docker-compose.yml`) runs:\n\n* **caddy** — reverse proxy + Let’s Encrypt TLS (ports **80/443**) → proxies to `pairqr:3000`\n* **pairqr** — your Node/Express API (**3000** internal only)\n* **redis** — cache / sessions\n* **postgres** — database\n* **coturn** — STUN/TURN (**3478** UDP/TCP)\n\n**Caddyfile** (example):\n\n```caddyfile\n# HTTPS\napi.pairqr.app {\n  encode gzip\n  reverse_proxy pairqr:3000\n}\n\n# HTTP: /health =\u003e 200, everything else redirects to HTTPS (optional)\nhttp://api.pairqr.app {\n  route /health {\n    respond \"ok\" 200\n  }\n  redir https://api.pairqr.app{uri}\n}\n```\n\n---\n\n\n## 🤖 CI/CD (Recommended)\n\nTypical GitHub Actions pipeline:\n\n1. **Build** the server Docker image (multi-stage, `npm ci`) and push to **GHCR**.\n2. **SSH** into the server, write `server/.env` from GitHub Secrets, set `SERVER_IMAGE`.\n3. `docker compose pull \u0026\u0026 up -d`, then perform an **HTTPS healthcheck** (expect **200**).\n\n**GitHub Secrets** (Settings → Secrets and variables → Actions):\n\n* SSH/Droplet: `DO_HOST`, `DO_SSH_USER`, `DO_SSH_KEY` (optionally `DO_SSH_PORT`, `DO_SSH_PASSPHRASE`), `PROJECT_DIR`\n* App: `ADMIN_PASSWORD`, `HMAC_SECRET`, `JWT_SECRET`, `CORS_ORIGIN`, `TURN_STATIC_SECRET`\n* Optional: `DATABASE_URL`, `REDIS_URL`, `TURN_REALM`, `TURN_URIS`, `RATE_LIMIT_*`, `SESSION_TTL_MINUTES`, etc.\n\n\u003e In the script that writes `.env`, **escape `$`** as `$$` to avoid interpolation warnings and corrupted values.\n\n---\n\n## 🔒 Security\n\n* **E2EE**: encrypted client-side; server relays but can’t read content\n* **Ephemeral**: messages are not persisted by default\n* **CORS**: strict allowlist (production frontends only)\n* **Rate limiting**: basic API abuse protection\n* **Headers**: Helmet on the app + security headers on Caddy\n* **Validation**: Zod on critical inputs\n\n---\n\n## 🩺 Health \u0026 Logs\n\n* **Health**: `GET /health` → **200** (via `https://api.pairqr.app/health`)\n* **Logs**:\n\n  * backend: `docker logs -f pairqr-app`\n\n---\n\n## 🛠 Troubleshooting (Quick FAQ)\n\n* **Healthcheck returns 308**: you are probing HTTP while Caddy redirects to HTTPS. Probe **HTTPS** instead (`curl -L https://api…/health`), or keep the HTTP block that serves `/health` as 200.\n* **`The \"U\" variable is not set`**: a `$` in `.env` wasn’t escaped → replace `$` with `$$`.\n* **Node build OOM**: use `npm ci`, keep `package-lock.json`, multi-stage Docker, and/or build the image in CI (not on the VPS).\n* **TURN behind Cloudflare**: don’t proxy UDP; keep **DNS only** for the TURN/API subdomain if you rely on UDP and ACME HTTP-01.\n\n---\n\n## 🤝 Contributing\n\n1. Fork\n2. Branch: `feat/my-feature`\n3. Dev: `npm run dev` (client/server)\n4. Checks: `npm run check`, tests if present\n5. Open a PR with a clear description and docs updates if needed\n\n---\n\n## 📄 License\n\nSee **LICENSE**.\n\n---\n\n## 🔗 Links\n\n* **Demo**: [https://pairqr.app](https://pairqr.vercel.app)\n* **Repo**: [https://github.com/TitanSage02/PairQR](https://github.com/TitanSage02/PairQR)\n* **Issues**: [https://github.com/TitanSage02/PairQR/issues](https://github.com/TitanSage02/PairQR/issues)\n\n---\n\n*Built with ❤️ for simple, private, and secure communication.*\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftitansage02%2Fpairqr","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftitansage02%2Fpairqr","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftitansage02%2Fpairqr/lists"}