玩游戏时常需要与朋友分享整合包,但每次都要从云盘下载后再通过其他工具(即时通讯软件)中转,颇为繁琐。这让我回想起之前参与远程桌面前端开发时接触过的 WebRTC 技术,于是决定基于它搭建一个简易的 P2P 文件传输系统,项目根据自己的需求结合codex搭建的,该文章主要记录个人在部署过程中的主要步骤,欢迎大家多多指点一二。

        webRTC p2p核心步骤:信令交换(交换SDP)-> ICE候选收集(获取本地 IP、公网 IP 及 TURN 中继地址) -> 连通性检查(通过 STUN 进行 NAT 穿透;若直连失败,则启用 TURN 中继转发,此时不再严格属于 P2P)-> DTLS握手(建立安全传输通道) -> 数据/媒体传输。

项目地址:https://gitee.com/treeshavings/p2p-transfer.git

在线访问地址:Q-Uploader - P2P File Transfer

一、项目和服务器准备

        前端使用 Vite + Vue3 + TypeScript,后端信令服务器基于 Node.js。服务器选用的是阿里云 ECS(ecs.e-c1m1.large,Ubuntu 24.04,3Mbps 带宽),该规格目前有优惠活动(年费 99 元)。阿里云控制台内置的智能助手支持通过自然语言快速安装 Docker、Coturn 等组件,方便后续部署。

二、使用docker部署

        1、服务器登录凭证

        选的登录凭证是密钥对。后面生成一键部署脚本也会用到。

创建好自行保存。

        2、前后端生成Dockerfile配置文件以及docker compose统一管理

        前端采用多阶段构建,先利用 Node.js 镜像编译 Vite 项目,再将产物复制到 Nginx 镜像中,以减小最终镜像体积。Dockerfile 如下(ssl生成后面第3点有介绍):

# ============ Build Stage ============
FROM docker.m.daocloud.io/library/node:20-alpine AS builder
WORKDIR /app

# 分离依赖安装层,最大化利用缓存
COPY package.json package-lock.json ./
RUN npm ci

# 复制源码与构建配置
COPY tsconfig.json tsconfig.app.json tsconfig.node.json vite.config.ts index.html ./
COPY public/ public/
COPY src/ src/

RUN npm run build

# ============ Runtime Stage ============
FROM docker.m.daocloud.io/library/nginx:alpine

COPY --from=builder /app/dist /usr/share/nginx/html

# nginx 配置 + SSL 证书
COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY ssl/ /etc/nginx/ssl/

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD wget --no-verbose --tries=1 --spider https://localhost/ --no-check-certificate || exit 1

EXPOSE 80 443

        后端信令服务器同样使用多阶段构建,仅保留生产依赖,并采用非 root 用户运行,提高安全性:

# Stage 1: Install dependencies
FROM docker.m.daocloud.io/library/node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev

# Stage 2: Production runtime
FROM docker.m.daocloud.io/library/node:20-alpine
RUN addgroup -g 1001 -S nodejs && adduser -S nodejs -u 1001
WORKDIR /app
COPY --from=deps --chown=nodejs:nodejs /app/node_modules ./node_modules
COPY --chown=nodejs:nodejs server.js ./
USER nodejs
EXPOSE 3080
CMD ["node", "server.js"]

        最后通过 docker-compose.yml 统一编排前后端服务,并配置网络、健康检查及日志策略:

services:
  # ==================== Frontend (Nginx + Vite SPA) ====================
  app:
    build:
      context: ./p2p-uploader-front
      dockerfile: Dockerfile
    container_name: q-uploader-web
    ports:
      - "80:80"
      - "443:443"
    restart: unless-stopped
    depends_on:
      backend:
        condition: service_healthy
    networks:
      app-network:
        aliases:
          - frontend
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

  # ==================== Backend (Node.js WebRTC Signaling) ====================
  backend:
    build:
      context: ./p2p-signal-server
      dockerfile: Dockerfile
    container_name: q-uploader-api
    ports:
      - "3080:3080"
    restart: unless-stopped
    env_file:
      - ./p2p-signal-server/.env.production
    environment:
      - PORT=3080
      - AUTH_TOKEN=${AUTH_TOKEN:-}
      - ICE_SERVERS=${ICE_SERVERS:-}
    networks:
      app-network:
        aliases:
          - backend
          - api
    healthcheck:
      test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:3080/server-status"]
      interval: 30s
      timeout: 5s
      retries: 3
      start_period: 10s
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

networks:
  app-network:
    driver: bridge
    name: q-uploader-net




        3、ssl证书(启用https)

         下载ssl证书,ssl证书阿里云有免费的,有效期3个月,ssl下载的服务器类型是Nginx,

        4、一键部署脚本 

        为了方便重复部署,用codex编写了一个一键部署脚本 deploy.sh。它会将项目打包(排除 node_modules 等)、上传到服务器、自动备份现有环境变量、构建并启动 Docker 容器。脚本内已配置好服务器 IP、SSH 密钥路径等参数,在 Git Bash 中执行 ./deploy.sh 即可完成全流程。​​​​​​​

#!/usr/bin/env bash
set -euo pipefail

# ============================================================
#  Q-Uploader One-Click Deploy Script
#  Usage: ./deploy.sh
#  Prereq: Docker + Docker Compose installed on server
# ============================================================

# ---- Config ----
SERVER_IP="120.27.226.32"
SERVER_PORT="22"
SERVER_USER="root"
REMOTE_DIR="/opt/q-uploder"
PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)"

# SSH key path (Git Bash: /d/aliyun-key/个人开发.pem)
SSH_KEY="/d/aliyun-key/个人开发.pem"

RED='\033[0;31m'
GREEN='\033[0;32m'
CYAN='\033[0;36m'
NC='\033[0m'

step()  { echo -e "${CYAN}[*]${NC} $1"; }
ok()    { echo -e "${GREEN}[+]${NC} $1"; }
die()   { echo -e "${RED}[!]${NC} $1"; exit 1; }

SSH_OPTS="-i ${SSH_KEY} -o StrictHostKeyChecking=no -o ConnectTimeout=10"
SCP_OPTS="-i ${SSH_KEY} -o StrictHostKeyChecking=no -o ConnectTimeout=10"

# ---- Pre-flight checks ----
if [ ! -f "${SSH_KEY}" ]; then
    die "SSH key not found: ${SSH_KEY}. Please update SSH_KEY in the script."
fi
chmod 600 "${SSH_KEY}" 2>/dev/null || true

echo ""
echo "========================================"
echo "  Q-Uploader Deploy"
echo "  Target: ${SERVER_USER}@${SERVER_IP}:${SERVER_PORT}${REMOTE_DIR}"
echo "  Key:    ${SSH_KEY}"
echo "========================================"
echo ""

# ---- 0. Connectivity test ----
step "Testing SSH connectivity..."
if ssh ${SSH_OPTS} -p ${SERVER_PORT} ${SERVER_USER}@${SERVER_IP} "echo ok" 2>/dev/null; then
    ok "SSH connection successful"
else
    die "SSH connection failed: ${SERVER_USER}@${SERVER_IP}:${SERVER_PORT}
  Check:
  1. Security group: is port ${SERVER_PORT} open for your IP?
  2. Server firewall: firewall-cmd --add-port=${SERVER_PORT}/tcp
  3. SSH port: try changing SERVER_PORT in this script"
fi

# ---- 1. Package project ----
step "Packaging project (excluding node_modules, .git, dist)..."
TARBALL="/tmp/q-uploder-$(date +%Y%m%d%H%M%S).tar.gz"
cd "${PROJECT_DIR}"
tar --exclude='node_modules' \
    --exclude='.git' \
    --exclude='dist' \
    --exclude='*.tar.gz' \
    --exclude='tmp' \
    -czf "${TARBALL}" .
ok "Package created: ${TARBALL} ($(du -h "${TARBALL}" | cut -f1))"

# ---- 2. Upload to server ----
step "Uploading to server..."
scp ${SCP_OPTS} -P ${SERVER_PORT} -q "${TARBALL}" "${SERVER_USER}@${SERVER_IP}:/tmp/q-uploder.tar.gz"
ok "Upload complete"

# ---- 3. Remote deploy ----
step "Deploying on server..."

ssh ${SSH_OPTS} -p ${SERVER_PORT} "${SERVER_USER}@${SERVER_IP}" bash -s -- "${REMOTE_DIR}" << 'ENDSSH'
set -euo pipefail

REMOTE_DIR="$1"

# Stop existing services if any, backup .env.production
if [ -d "${REMOTE_DIR}" ]; then
    echo "[*] Stopping existing services..."
    cd "${REMOTE_DIR}"
    docker compose down 2>/dev/null || true

    if [ -f "${REMOTE_DIR}/p2p-signal-server/.env.production" ]; then
        cp "${REMOTE_DIR}/p2p-signal-server/.env.production" /tmp/q-uploader-env.bak
    fi
fi

# Extract project
echo "[*] Extracting project..."
mkdir -p "${REMOTE_DIR}"
tar --overwrite -xzf /tmp/q-uploder.tar.gz -C "${REMOTE_DIR}"
rm -f /tmp/q-uploder.tar.gz
echo "[*] Verifying extraction..."
ls -la "${REMOTE_DIR}/docker-compose.yml" "${REMOTE_DIR}/p2p-signal-server/server.js" 2>&1

# Restore .env.production if backed up
if [ -f /tmp/q-uploader-env.bak ]; then
    cp /tmp/q-uploader-env.bak "${REMOTE_DIR}/p2p-signal-server/.env.production"
    rm -f /tmp/q-uploader-env.bak
fi

# Generate .env.production from template if missing
if [ ! -f "${REMOTE_DIR}/p2p-signal-server/.env.production" ]; then
    echo "[*] Generating .env.production with random AUTH_TOKEN..."
    AUTH_TOKEN=$(tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 32)
    cat > "${REMOTE_DIR}/p2p-signal-server/.env.production" <<ENVEOF
PORT=3080
AUTH_TOKEN=${AUTH_TOKEN}
ICE_SERVERS=[{"urls":"stun:stun.l.google.com:19302"}]
ENVEOF
    echo "[+] Generated AUTH_TOKEN=${AUTH_TOKEN}"
fi

# Build and start
echo "[*] Building Docker images..."
cd "${REMOTE_DIR}"
docker compose build --no-cache

echo "[*] Starting services..."
docker compose up -d --force-recreate

echo "[*] Waiting for health check..."
sleep 5

echo ""
echo "=== Container Status ==="
docker compose ps

echo ""
echo "[+] Deploy finished!"
ENDSSH

# ---- 4. Cleanup ----
rm -f "${TARBALL}"
ok "Local temp files cleaned"

echo ""
echo -e "${GREEN}========================================"
echo "  Deploy Complete!"
echo "  Open: http://${SERVER_IP}"
echo "========================================"
echo ""
echo "Useful commands:"
echo "  ssh ${SSH_OPTS} -p ${SERVER_PORT} ${SERVER_USER}@${SERVER_IP} docker compose -f ${REMOTE_DIR}/docker-compose.yml logs -f"
echo "  ssh ${SSH_OPTS} -p ${SERVER_PORT} ${SERVER_USER}@${SERVER_IP} docker compose -f ${REMOTE_DIR}/docker-compose.yml ps"
echo "  ssh ${SSH_OPTS} -p ${SERVER_PORT} ${SERVER_USER}@${SERVER_IP} docker compose -f ${REMOTE_DIR}/docker-compose.yml restart"


 5、配置域名。

         先进行域名注册。选的是阿里云的这款,

添加域名解析,从而可以用域名访问网站,

(官方文档为网站配置A记录将域名指向服务器IP-云解析DNS-阿里云

最后还进行了ICP备案,

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐