Files
PaperPhone/zeabur.yaml
2026-04-04 13:38:53 +08:00

475 lines
19 KiB
YAML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
apiVersion: zeabur.com/v1
kind: Template
metadata:
name: PaperPhone
spec:
description: >-
A WeChat-style, end-to-end encrypted IM app with stateless ECDH E2EE,
WebRTC video/voice calls, Cloudflare R2 file storage, and iOS PWA
support — fully self-hostable.
icon: https://raw.githubusercontent.com/619dev/PaperPhone/main/client/public/icons/icon-192.png
tags:
- Chat
- Security
- Communication
readme: |-
# PaperPhone IM
A **WeChat-style**, end-to-end encrypted instant messaging application.
## Security
- **Stateless ECDH + XSalsa20-Poly1305** — per-message ephemeral keys, forward secrecy without session state
- **Zero-knowledge server** — only ciphertext is stored; private keys never leave the device
- **Four-tier key persistence** — memory → localStorage → sessionStorage → IndexedDB, survives WebView restarts
## Features
- 📹 **Video & Voice Calls** — WebRTC P2P (1:1) + Mesh (group up to 6)
- 👥 **Group Chat** — up to 2000 members, plain-text messaging, Do Not Disturb mode, member management
- 🌐 **Moments** — social feed with text + up to 9 photos or 1 video (≤ 10 min), likes, comments, tag-based visibility
- 👤 **User Profile** — contact profile page with Moments feed, bidirectional privacy controls (hide their / hide mine)
- 📰 **Timeline** — Xiaohongshu-style public feed with masonry layout, up to 50 images/videos + 2000-char text, anonymous posting, likes & comments
- ⏱️ **Auto-Delete Messages** — 5 tiers: never / 1 day / 3 days / 1 week / 1 month
- 🔔 **Push Notifications** — Web Push (VAPID) + OneSignal dual-channel
- 🌐 **Multi-language** — Chinese, English, Japanese, Korean, French, German, Russian, Spanish
- 📱 **iOS PWA** — "Add to Home Screen" via Safari, no enterprise cert needed
- 💬 Rich messaging: text, images, video, document files (PDF/DOCX/XLSX etc. with type icons), voice, 200+ emoji (8 categories), Telegram sticker packs, delivery receipts, typing indicators
- 🗂️ **Cloudflare R2** file storage (images, voice & video)
- 🏷️ **Friend Tags** — tag-based contact filtering & Moments visibility control
- 🎭 **Telegram Sticker Packs** — dynamic sticker packs with configurable pack list, unlimited quantity
- 🔑 **Two-Factor Auth (2FA)** — Google Authenticator TOTP, 8 recovery codes
- 📷 **QR Code Scan & Share** — scan QR to add friends or join groups; group QR codes with configurable expiry (1 week / 1 month / 3 months)
## After Deployment
1. Open the domain assigned to the **client** service.
2. Register an account — cryptographic keys are generated locally on your device.
3. (Optional) Configure Cloudflare R2 for image/voice/video uploads — set `R2_*` variables on the **server** service.
4. (Optional) Configure Cloudflare TURN for cross-network video calls — set `CF_CALLS_APP_ID` and `CF_CALLS_APP_SECRET`.
5. (Optional) Enable push notifications — set `VAPID_*` variables for Web Push.
6. (Optional) Enable Telegram sticker packs — set `TELEGRAM_BOT_TOKEN` and optionally `STICKER_PACKS`.
## Docker Hub Images
- `facilisvelox/paperphone-client:latest`
- `facilisvelox/paperphone-server:latest`
variables:
- key: PUBLIC_DOMAIN
type: DOMAIN
name: Client Domain
description: The public domain for the PaperPhone web client (e.g. paperphone.example.com).
- key: JWT_SECRET
type: STRING
name: JWT Secret
description: A long random string used to sign authentication tokens. Change this before going to production.
- key: R2_ACCOUNT_ID
type: STRING
name: Cloudflare R2 Account ID
description: Your Cloudflare account ID (found in Cloudflare Dashboard → right sidebar).
- key: R2_ACCESS_KEY_ID
type: STRING
name: R2 Access Key ID
description: R2 API token access key (R2 → Manage API tokens → Create token).
- key: R2_SECRET_ACCESS_KEY
type: STRING
name: R2 Secret Access Key
description: R2 API token secret key paired with the access key above.
- key: R2_BUCKET
type: STRING
name: R2 Bucket Name
description: Name of the R2 bucket to store uploaded files (e.g. paperphone).
- key: R2_PUBLIC_URL
type: STRING
name: R2 Public URL (optional)
description: >-
Public base URL for the bucket, e.g. https://pub-xxx.r2.dev or your
custom domain. If set, files are served directly from R2/CDN.
Leave blank to proxy files through the server.
- key: MYSQL_ROOT_PASSWORD
type: STRING
name: MySQL Root Password
description: Password for the MySQL root user. Set this yourself — it will NOT be auto-generated.
- key: CF_CALLS_APP_ID
type: STRING
name: Cloudflare Calls App ID (optional)
description: >-
Required only for cross-network WebRTC video/voice calls. Obtain from
Cloudflare Dashboard → Calls → your App. Leave blank to use STUN only
(LAN calls work without this).
- key: CF_CALLS_APP_SECRET
type: STRING
name: Cloudflare Calls App Secret (optional)
description: >-
The Bearer token / App Secret paired with CF_CALLS_APP_ID above.
Leave blank if you are not using Cloudflare TURN.
- key: VAPID_PUBLIC_KEY
type: STRING
name: VAPID Public Key (optional)
description: >-
Web Push VAPID public key for push notifications. Generate with:
npx web-push generate-vapid-keys. Leave blank to disable push.
- key: VAPID_PRIVATE_KEY
type: STRING
name: VAPID Private Key (optional)
description: >-
Web Push VAPID private key paired with the public key above.
- key: VAPID_SUBJECT
type: STRING
name: VAPID Subject (optional)
description: >-
Contact email for VAPID, e.g. mailto:admin@yoursite.com.
- key: ONESIGNAL_APP_ID
type: STRING
name: OneSignal App ID (optional)
description: >-
For native Android/iOS push via Median.co. Get from OneSignal
Dashboard → Settings → Keys & IDs.
- key: ONESIGNAL_REST_KEY
type: STRING
name: OneSignal REST API Key (optional)
description: >-
OneSignal REST API key paired with the App ID above.
- key: TELEGRAM_BOT_TOKEN
type: STRING
name: Telegram Bot Token (optional)
description: >-
Token from @BotFather for proxying Telegram sticker packs.
Leave blank to disable sticker packs.
- key: STICKER_PACKS
type: STRING
name: Sticker Packs (optional)
description: >-
Comma-separated list of Telegram sticker pack names with labels,
in the format name:label (e.g. pack_name:My Pack,other_pack:Other Pack).
No limit on quantity. Leave blank to use the 8 built-in defaults.
services:
# ── MySQL 8 ───────────────────────────────────────────────────────
- name: MySQL
icon: https://raw.githubusercontent.com/zeabur/service-icons/main/marketplace/mysql.svg
template: PREBUILT
spec:
source:
image: mysql:8.0
ports:
- id: database
port: 3306
type: TCP
volumes:
- id: data
dir: /var/lib/mysql
env:
MYSQL_ROOT_PASSWORD:
default: ${MYSQL_ROOT_PASSWORD}
expose: true
MYSQL_ROOT_HOST:
default: "%"
MYSQL_DATABASE:
default: paperphone
expose: true
readonly: true
MYSQL_USER:
default: paperphone
expose: true
readonly: true
MYSQL_PASSWORD:
default: ${MYSQL_ROOT_PASSWORD}
expose: true
MYSQL_HOST:
default: ${CONTAINER_HOSTNAME}
expose: true
readonly: true
MYSQL_PORT:
default: "3306"
expose: true
readonly: true
instructions:
- type: TEXT
title: MySQL Host
content: ${PORT_FORWARDED_HOSTNAME}
category: Hostname & Port
- type: TEXT
title: MySQL Port
content: ${DATABASE_PORT_FORWARDED_PORT}
category: Hostname & Port
- type: TEXT
title: MySQL User
content: ${MYSQL_USER}
category: Credentials
- type: PASSWORD
title: MySQL Password
content: ${MYSQL_PASSWORD}
category: Credentials
- type: TEXT
title: MySQL Database
content: ${MYSQL_DATABASE}
category: Credentials
# ── Redis 7 ───────────────────────────────────────────────────────
- name: Redis
icon: https://raw.githubusercontent.com/zeabur/service-icons/main/marketplace/redis.svg
template: PREBUILT
spec:
source:
image: redis:7-alpine
command:
- redis-server
- --save
- "60"
- "1"
ports:
- id: database
port: 6379
type: TCP
volumes:
- id: data
dir: /data
env:
REDIS_PASSWORD:
default: ${PASSWORD}
expose: true
REDIS_HOST:
default: ${CONTAINER_HOSTNAME}
expose: true
readonly: true
REDIS_PORT:
default: "6379"
expose: true
readonly: true
REDIS_URI:
default: redis://${REDIS_HOST}:${REDIS_PORT}
expose: true
readonly: true
instructions:
- type: PASSWORD
title: Redis Password
content: ${REDIS_PASSWORD}
category: Credentials
- type: TEXT
title: Redis Connection String
content: redis://:${REDIS_PASSWORD}@${PORT_FORWARDED_HOSTNAME}:${DATABASE_PORT_FORWARDED_PORT}
# ── PaperPhone Server (Node.js) ────────────────────────────────
- name: server
icon: https://raw.githubusercontent.com/619dev/PaperPhone/main/client/public/icons/icon-192.png
template: PREBUILT
dependencies:
- MySQL
- Redis
spec:
source:
image: facilisvelox/paperphone-server:latest
ports:
- id: web
port: 3000
type: HTTP
volumes:
- id: uploads
dir: /app/uploads
env:
PORT:
default: "3000"
NODE_ENV:
default: production
UPLOAD_DIR:
default: /app/uploads
JWT_SECRET:
default: ${JWT_SECRET}
JWT_EXPIRES_IN:
default: 7d
DB_HOST:
default: ${MYSQL_HOST}
DB_PORT:
default: ${MYSQL_PORT}
DB_USER:
default: paperphone
DB_PASS:
default: ${MYSQL_PASSWORD}
DB_NAME:
default: ${MYSQL_DATABASE}
REDIS_HOST:
default: ${REDIS_HOST}
REDIS_PORT:
default: ${REDIS_PORT}
R2_ACCOUNT_ID:
default: ${R2_ACCOUNT_ID}
R2_ACCESS_KEY_ID:
default: ${R2_ACCESS_KEY_ID}
R2_SECRET_ACCESS_KEY:
default: ${R2_SECRET_ACCESS_KEY}
R2_BUCKET:
default: ${R2_BUCKET}
R2_PUBLIC_URL:
default: ${R2_PUBLIC_URL}
CF_CALLS_APP_ID:
default: ${CF_CALLS_APP_ID}
CF_CALLS_APP_SECRET:
default: ${CF_CALLS_APP_SECRET}
VAPID_PUBLIC_KEY:
default: ${VAPID_PUBLIC_KEY}
VAPID_PRIVATE_KEY:
default: ${VAPID_PRIVATE_KEY}
VAPID_SUBJECT:
default: ${VAPID_SUBJECT}
ONESIGNAL_APP_ID:
default: ${ONESIGNAL_APP_ID}
ONESIGNAL_REST_KEY:
default: ${ONESIGNAL_REST_KEY}
TELEGRAM_BOT_TOKEN:
default: ${TELEGRAM_BOT_TOKEN}
STICKER_PACKS:
default: ${STICKER_PACKS}
SERVER_URL:
default: ${ZEABUR_WEB_URL}
expose: true
readonly: true
# ── PaperPhone Client (Nginx / PWA) ────────────────────────────
- name: client
icon: https://raw.githubusercontent.com/619dev/PaperPhone/main/client/public/icons/icon-192.png
template: PREBUILT
domainKey: PUBLIC_DOMAIN
dependencies:
- server
spec:
source:
image: facilisvelox/paperphone-client:latest
ports:
- id: web
port: 80
type: HTTP
env:
SERVER_URL:
default: ${SERVER_URL}
localization:
zh-CN:
description: >-
微信风格的端对端加密即时通讯 App支持无状态 ECDH 加密、WebRTC 视频/语音通话、
Cloudflare R2 文件存储、多语言与 iOS PWA 部署,完全可自建。
variables:
- key: PUBLIC_DOMAIN
type: DOMAIN
name: 客户端域名
description: PaperPhone 前端的公开域名(例如 paperphone.example.com
- key: JWT_SECRET
type: STRING
name: JWT 密钥
description: 用于签发认证令牌的长随机字符串,生产环境请务必修改。
- key: MYSQL_ROOT_PASSWORD
type: STRING
name: MySQL 密码
description: MySQL root 用户的密码,请自行设置,不会自动生成。
- key: R2_ACCOUNT_ID
type: STRING
name: Cloudflare 账号 ID
description: Cloudflare 账号 ID位于 Dashboard 右侧边栏)。
- key: R2_ACCESS_KEY_ID
type: STRING
name: R2 Access Key ID
description: R2 API Token 的 Access KeyR2 → 管理 API 令牌 → 创建令牌)。
- key: R2_SECRET_ACCESS_KEY
type: STRING
name: R2 Secret Access Key
description: 与上方 Access Key 配对的 Secret Key。
- key: R2_BUCKET
type: STRING
name: R2 Bucket 名称
description: 存储上传文件的 R2 Bucket 名称(如 paperphone
- key: R2_PUBLIC_URL
type: STRING
name: R2 公开 URL可选
description: >-
Bucket 的公开访问 URL如 https://pub-xxx.r2.dev 或自定义域名。
设置后文件直接走 R2 CDN无需经过服务器代理。留空则由服务器代理。
- key: CF_CALLS_APP_ID
type: STRING
name: Cloudflare Calls App ID可选
description: 跨网络 WebRTC 视频通话所需,从 Cloudflare Dashboard → Calls 获取。局域网内无需填写。
- key: CF_CALLS_APP_SECRET
type: STRING
name: Cloudflare Calls App Secret可选
description: 与上方 CF_CALLS_APP_ID 配对使用的 Bearer Token。不使用 Cloudflare TURN 时留空即可。
- key: TELEGRAM_BOT_TOKEN
type: STRING
name: Telegram Bot Token可选
description: 通过 @BotFather 获取的 Bot Token用于代理 Telegram 贴纸包。留空则禁用贴纸功能。
- key: STICKER_PACKS
type: STRING
name: 贴纸包列表(可选)
description: >-
逗号分隔的 Telegram 贴纸包配置,格式为 包名:显示名称
(如 pack_name:我的贴纸,other_pack:另一个包)。数量无上限。
留空则使用内置的 8 个默认贴纸包。
- key: VAPID_PUBLIC_KEY
type: STRING
name: VAPID 公钥(可选)
description: >-
Web Push 消息推送所需的 VAPID 公钥。通过 npx web-push generate-vapid-keys 生成。
留空则禁用浏览器推送功能。
- key: VAPID_PRIVATE_KEY
type: STRING
name: VAPID 私钥(可选)
description: 与上方 VAPID 公钥配对使用的私钥。
- key: VAPID_SUBJECT
type: STRING
name: VAPID 联系邮箱(可选)
description: VAPID 联系邮箱,如 mailto:admin@yoursite.com。
- key: ONESIGNAL_APP_ID
type: STRING
name: OneSignal App ID可选
description: 通过 Median.co 原生 App 推送所需。从 OneSignal Dashboard → Settings → Keys & IDs 获取。
- key: ONESIGNAL_REST_KEY
type: STRING
name: OneSignal REST API Key可选
description: 与上方 OneSignal App ID 配对使用的 REST API Key。
readme: |-
# PaperPhone IM
**微信风格**的端对端加密即时通讯应用。
## 安全模型
- **无状态 ECDH + XSalsa20-Poly1305** — 逐消息临时密钥,前向保密无需会话状态同步
- **零知识服务器** — 仅存储密文,私钥仅在设备本地,永不上传
- **四层密钥持久化** — 内存 → localStorage → sessionStorage → IndexedDBAndroid WebView 重启后仍可恢复
## 功能亮点
- 📹 **视频/语音通话** — WebRTC P2P1:1+ Mesh 多人≤6 人)
- 👥 **群聊** — 最多 2000 人群组,纯文本消息,免打扰模式,成员管理
- 🌐 **朋友圈** — 文字 + 最多 9 张图或 1 个视频≤10 分钟),点赞、评论、标签可见性控制
- 👤 **个人资料** — 联系人资料页(朋友圈动态 +「不看此人朋友圈」与「不让他看我的朋友圈」双向隐私控制)
- 📰 **时间线** — 小红书风格公开发帖区双列瀑布流布局最多50个图片/视频+2000字文字支持匿名发帖、点赞、评论
- ⏱️ **消息自动删除** — 5 档可选(永不/1天/3天/1周/1月
- 🔔 **消息推送** — Web Push (VAPID) + OneSignal 双通道
- 🌐 **多语言** — 中文、英文、日语、韩语、法语、德语、俄语、西班牙语
- 📱 **iOS 永久免签** — Safari「添加到主屏幕」无需企业证书
- 💬 富文本消息、图片、视频、文档文件PDF/DOCX/XLSX 等带类型图标、语音、Emoji200+8 分类、Telegram 贴纸包、送达回执、打字状态
- 🗂️ **Cloudflare R2** 对象存储(图片、语音与视频)
- 🏷️ **好友标签** — 标签分类筛选通讯录 + 朋友圈可见性控制
- 🎭 **Telegram 贴纸包** — 动态贴纸包管理,可自定义包列表,数量无上限
- 🔑 **两步验证 (2FA)** — Google Authenticator TOTP8 个恢复码
- 📷 **扫码加好友/入群** — 扫一扫二维码添加好友、加入群聊群二维码可设置有效期1周/1月/3月
## 部署后操作
1. 打开分配给 **client** 服务的域名。
2. 注册账号 — 密钥在本地设备生成。
3. (可选)在 **server** 服务设置 `R2_*` 变量,启用图片/语音/视频上传功能。
4. (可选)配置 Cloudflare TURN 以支持跨公网视频通话。
5. (可选)设置 `VAPID_*` 变量启用浏览器推送通知。
6. (可选)设置 `TELEGRAM_BOT_TOKEN` 和 `STICKER_PACKS` 启用 Telegram 贴纸包功能。