---
name: chinese-cloud-deploy
description: Deploy web services to Chinese cloud servers (Tencent, Alibaba) with network-restricted environments. Covers domestic mirrors, WeCom/WeChat callbacks, domain procurement, free SSL, Nginx reverse proxy, and Docker-free fallbacks.
triggers:
  - Deploying to Tencent Cloud / Alibaba Cloud
  - Setting up HTTPS for 企业微信 (WeCom) callback URLs
  - Installing Docker/apt/pip on servers with foreign-site blocks
  - Getting free/cheap domains for Chinese-hosted services
  - Self-signed SSL fallback strategies
---

# Chinese Cloud Server Deployment

Deploy web services on Chinese cloud servers where foreign sites (GitHub, Docker Hub, PyPI) are blocked by security group / firewall rules.

## Agent Behavior Rules

**Progress reports MUST use tables:**
- Show each component's status (✅/❌/⚠️) in a table
- Never say "doing X" or "working on Y" — report results, not activity
- When a step completes, immediately report the outcome with a table

**When the user says "你自己部署" / "你自己直接操作" / "你自己做":**
- The user expects you to handle cross-server operations without asking them to paste commands
- EXHAUST remote execution options before giving manual commands: Tencent TAT API (if credentials available) → SSH (if accessible) → GitHub Actions → then last resort: give a one-line paste command
- Never reply with a command for the user to run when remote execution is available — the user said "自己部署" because that's what they want
- See `references/tencent-tat-remote-exec.md` for the full TAT API pattern

**When the user says "不太懂" / "小白" / "为什么后面改了":**
- Immediately switch to simple analogy mode (no technical jargon)
- Give one step at a time, not multi-step plans
- When presenting options, use a single-sentence summary before any table
- User uses web consoles (腾讯云/阿里云控制台) and Cloudflare dashboard — not SSH. Instructions should say "点" not "执行命令"

**When blocked or stuck:**
1. Report what was attempted + what failed in one line
2. Immediately present alternative approaches (table format)
3. Do NOT keep retrying the same thing silently
4. If multiple things fail in a row, stop and ask: "选哪个方案？"

**Execution flow:**
- Small, safe changes (reading files, checking status): just do it
- Multi-step sequences: briefly state the plan (1-2 lines), then execute
- Do NOT present long numbered plans for routine operations — the user wants speed
- **Action-first rule**: Never speculate or guess before investigating. Run the diagnostic command FIRST, then report findings. The user's time is wasted by "could be X, could be Y" — just check and say what IS.
- When something isn't working, check logs, check processes, check the database — don't ask the user to verify things you can verify yourself.

## Quick Decision Flow

```
Project to deploy on Chinese VPS
  │
  ├─ Need Docker? → Use domestic mirrors ONLY
  │   ├─ Docker: mirrors.cloud.tencent.com/docker-ce
  │   ├─ Docker Hub: mirror.ccs.tencentyun.com
  │   └─ Fallback: skip Docker, run directly with Python
  │
  ├─ Need HTTPS? (required for 企微 callback)
  │   ├─ Have domain? → Caddy (auto Let's Encrypt, simpler than Nginx+certbot)
  │   ├─ Have domain? (fallback) → Tencent Cloud free TrustAsia SSL + Nginx
  │   ├─ No domain, ports OPEN? → Caddy or Let's Encrypt certbot
  │   ├─ No domain, ports BLOCKED by ISP? → Non-standard port (8443) or Cloudflare Tunnel
  │   └─ Self-signed cert: Nginx works, 企微 REJECTS
  │
  ├─ Ports 80/443 blocked by ISP (no ICP filing)?
  │   ├─ Verify with check-host.net: `curl -s "https://check-host.net/check-tcp?host=IP:443"`
  │   ├─ Workaround: use non-standard port (8443) — 企微 callback supports custom ports
  │   └─ Workaround: Cloudflare Tunnel — outbound only, no open ports needed
  │
  └─ Need domain?
      ├─ Free, instant → freedns.afraid.org (mooo.com, etc.)
      ├─ Free, needs VPN → DuckDNS (duckdns.org)
      ├─ Cheapest paid → .top (¥9/year, ¥26 renewal)
      └─ Professional → .com (¥55/year, ¥75 renewal)
```

## Tencent Cloud Firewall: Light Server vs CVM

**Critical pitfall** — these are different consoles:

| | CVM (云服务器) | Light Server (轻量应用服务器) |
|---|---|---|
| Console path | 控制台 → 安全组 → 入站规则 | 控制台 → 轻量应用服务器 → [实例] → 防火墙 → 添加规则 |
| Term used | 安全组 (Security Group) | 防火墙 (Firewall) |

Post-deployment ports that MUST be opened:
```
TCP:80   允许   所有IP     ← HTTP (needed for Let's Encrypt or redirect)
TCP:443  允许   所有IP     ← HTTPS
```

The local firewall (iptables/ufw) is typically inactive on Tencent servers — the blocking happens at the cloud console level only.

**Firewall rule edit bug**: Light Server 防火墙规则编辑后可能不生效。添加端口不通时，先删除旧规则再新建一条，比编辑已有规则更可靠。

## ISP-Level Port Blocking (No ICP Filing)

**Critical pitfall**: Even with firewall rules open, ports 80/443 are often blocked at the ISP level for servers without ICP filing (备案). Verify with check-host.net:

```bash
curl -s "https://check-host.net/check-tcp?host=YOUR_IP:443" -H "Accept: application/json"
# Then check results: curl -s "https://check-host.net/check-result/REQUEST_ID" -H "Accept: application/json"
```

If only 1-2 nodes out of 60+ can reach your ports, it's ISP blocking — not a firewall misconfiguration.

**Workaround — non-standard port**: Switch Nginx from 443 to 8443:
```bash
sudo sed -i 's/listen 443 ssl/listen 8443 ssl/' /etc/nginx/sites-available/app
sudo systemctl reload nginx
# Then open TCP:8443 in firewall
```
企微 callback URLs support custom ports: `https://domain:8443/wx-callback`

## Intranet Penetration / Tunnels (No Domain, Ports Blocked)

When the server has no domain and ports are blocked:

| Service | URL | Free Tier | Notes |
|---------|-----|-----------|-------|
| **natapp** | natapp.cn | 1 tunnel, random domain | CDN may 403 from server — have user download binary |
| **cpolar** | cpolar.com | 1 tunnel | Backup option |
| **Cloudflare Tunnel** | cloudflare.com | Unlimited | Named tunnels only — see references |

### natapp setup
1. User registers at natapp.cn → gets `authtoken`
2. User downloads `natapp_linux_amd64.zip` from their browser (server can't reach CDN)
3. User sends the ZIP to the agent
4. Agent: `unzip natapp.zip && chmod +x natapp`
5. Start: `./natapp -authtoken=TOKEN` → gets `https://xxx.natappfree.cc`

### Cloudflare Tunnel (detailed)
See `references/cloudflare-tunnel-china.md` for full deployment from Chinese servers (named tunnels, API auth, DNS CNAME, error 1016, token expiry). See `references/cloudflare-tunnel-debugging.md` for 502 diagnostics and token-vs-config tunnel types.

## GitHub Cloning from China

`github.com` is blocked on port 443, but `api.github.com` is often accessible. Use the API to download repos as ZIP:

```bash
curl -sL -o repo.zip -H "Authorization: Bearer $TOKEN" \
  "https://api.github.com/repos/OWNER/REPO/zipball"
```

Full technique and mirror comparison: `references/github-clone-china.md`

**GitHub push**: Use `github-api-push` skill — REST API PUT to push files when `git push` is blocked. `api.github.com` is often reachable.

**Cross-server file transfer**: When two servers can't talk directly, use GitHub API as relay — upload from server A, download from raw.githubusercontent.com on server B. See `github-api-push` skill's "Cross-Server File Transfer" section.

## File-Sharing Services from China (PITFALL)

Most free file-sharing services are **unreliable from Chinese servers** — do NOT spend time retrying them:

| Service | Result from China | Reason |
|---------|:---:|--------|
| transfer.sh | Connection refused | Blocked at network level |
| 0x0.st | 405 / blocked | Method filtering |
| file.io | Redirect loop | Cloudflare interception |
| tmpfiles.org | SSL timeout | GFW reset |

**Only tried-and-tested options from China:**
- **GitHub API** → `raw.githubusercontent.com` (almost always reachable)
- **WeCom file messages** (企微发文件): send file via WeCom API, download from WeCom CDN
- Browser-based transfers through cloudflared tunnels (requires human to click download)

When you need programmatic file transfer between isolated servers, skip the file-sharing services and go straight to GitHub API.

## Cross-Server Remote Execution via Tencent TAT API

When Hermes runs on server A but needs to operate on server B (another Tencent instance), use the TAT (Tencent Automation Tools) API. This is the **primary way** to avoid asking the user to paste commands — when the user says "你自己部署", this is what you reach for.

**Quick flow:**
1. User provides `SecretId` + `SecretKey` + instance ID (e.g., `lhins-5ertfkpu`) + region (e.g., `ap-beijing`)
2. Build TC3-HMAC-SHA256 signed request → `POST tat.tencentcloudapi.com` `RunCommand`
3. Commands MUST be Base64-encoded in the `Content` field
4. Set `Timeout` generously (300-600s for downloads; 60s for quick extracts)
5. Poll with `DescribeInvocationTasks` until `TaskStatus` is `SUCCESS`/`FAILED`/`TIMEOUT`
6. Output is also Base64-encoded — decode before reading

**Credentials**: User gets them from 腾讯云控制台 → 访问管理 → API密钥管理. After use, save to memory.

**Comparison:**
| Method | Reliable | User Effort |
|--------|:--------:|:-----------:|
| TAT API | ✅ | Zero |
| User pastes commands | ⚠️ | High |
| Direct scp | ❌ Blocked | N/A |

**PITFALL**: `Content` must be Base64-encoded. Plain text → `InvalidParameterValue`.
**PITFALL**: `ExitCode: 0` during `RUNNING` doesn't mean success — wait for `TaskStatus: SUCCESS`.
**PITFALL**: GitHub raw downloads from Chinese servers can be slow (15-25 KB/s). ~3MB ≈ 2-3 min.

Full code (signature helper, polling, deployment workflow): `references/tencent-tat-remote-exec.md`

## npm as Alternative Download Channel

When GitHub/curl/wget all fail (foreign CDNs blocked), **npm registry often works** via domestic mirrors:

```bash
npm config set registry https://registry.npmmirror.com
sudo npm install -g localtunnel   # escape hatch for tunnel clients
lt --port 5000                      # exposes localhost:5000
```

## 企微 Integration Path Selection (SIMPLE-FIRST)

**Rule: always use the simplest path first. Don't default to callback URL verification.**

| 路径 | 通信方向 | 需要公网URL | 需要SSL证书 | 适用场景 |
|------|----------|-------------|-------------|----------|
| **群机器人 Webhook** | 单向推送 | ❌ | ❌ | 定时推送到群 |
| **自建应用主动推送** | 单向推送 | ❌ | ❌ | 向指定用户推送 |
| **自建应用回调** | 双向 | ✅ 必须 | ✅ 必须 | 接收用户消息 |
| **智能机器人长连接** | 双向 | ❌ | ❌ | 群内互动问答 |

**Decision flow**: (1) Push-only → Webhook or API push; (2) Bidirectional, no public IP → 智能机器人 WebSocket; (3) Bidirectional, has public IP → callback (last resort).

Common error: jumping to callback URL config when the project already has `QW_WEBHOOK_URL` or `send_wx_app_message`. Check `.env` first for existing push channels.

Full comparison: `references/wework-bot-architecture.md`

## 1. Install Docker on Chinese Servers

Foreign Docker install scripts will time out. Use Tencent Cloud mirror:

```bash
# Add Tencent Cloud Docker mirror
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://mirrors.cloud.tencent.com/docker-ce/linux/ubuntu/gpg | \
  sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg --yes
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
  https://mirrors.cloud.tencent.com/docker-ce/linux/ubuntu $(lsb_release -cs) stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list

sudo apt update && sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
```

Configure Docker Hub mirror:
```json
// /etc/docker/daemon.json
{ "registry-mirrors": ["https://mirror.ccs.tencentyun.com"] }
```

PITFALL: `docker pull` will hang indefinitely without the mirror configured. If the mirror also fails (rare), skip Docker entirely and run the service directly with Python virtualenv.

## 2. Install Python Packages

```bash
pip3 install -i https://mirrors.cloud.tencent.com/pypi/simple flask requests cryptography
```

## 3. Nginx + HTTPS Setup

### Cross-Server Reverse Proxy (service on server A, domain on server B)

When a service runs on server A but the domain's DNS points to server B, use server B's Nginx to reverse-proxy to server A:

```nginx
# On server B (the domain host)
server {
    listen 80;
    server_name sub.example.com;

    location / {
        proxy_pass http://SERVER_A_IP:PORT;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}
```

**Prerequisites**: Server A's firewall must allow inbound TCP from server B's IP on the service port. Open only to the specific source IP (`SERVER_B_IP/32` on Lighthouse), not `0.0.0.0/0`.

**PITFALL**: DNS for the subdomain must resolve to server B's IP (where Nginx runs), NOT server A's IP. Nginx receives the request and forwards it internally.

### Self-Signed Certificate (quick test, 企微 REJECTS)

```bash
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout /etc/nginx/ssl/selfsigned.key \
  -out /etc/nginx/ssl/selfsigned.crt \
  -subj "/C=CN/ST=Guangdong/L=Shenzhen/O=Org/CN=SERVER_IP"
```

### Nginx Config Template

```nginx
server {
    listen 80;
    listen 443 ssl;
    server_name _;  # or specific domain

    ssl_certificate /etc/nginx/ssl/selfsigned.crt;
    ssl_certificate_key /etc/nginx/ssl/selfsigned.key;

    location / {
        proxy_pass http://127.0.0.1:5000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}
```

PITFALL: 企业微信 (WeCom) callback URL validation REQUIRES a valid CA-signed certificate. Self-signed certificates WILL be rejected.

PITFALL: 企微回调 AES 解密 IV 必须是 AES 密钥的前 16 字节，不是密文的前 16 字节。错误写法 `iv = encrypted_bytes[:16]`，正确写法 `iv = aes_key[:16]`。此 bug 会导致回调验证返回 "decrypt failed"。同时 `cipher_text` 应为完整的 `encrypted_bytes`，不要切掉前 16 字节。

PITFALL: 企微回调验证时 `msg_signature` 必须校验。使用 `SHA1(sort(token, timestamp, nonce, echostr))` 与请求中的 `msg_signature` 比对，不匹配则拒绝。仅解密而不验签是不安全的。

PITFALL: 企微回调 URL query string 中的 `echostr` 参数是 base64 编码，包含 `+` 字符。Flask 的 `request.args.get("echostr")` 会将 `+` 解析为空格，导致 base64 字符串被截断（只剩 5 个字符），解密时报 "number of data characters (5) cannot be 1 more than a multiple of 4"。**修复**：改用手动解析原始 query string → `unquote` 解码，避免 `+`→空格 转换：
```python
from urllib.parse import unquote
raw_qs = request.query_string.decode("utf-8", errors="replace")
params = {}
for pair in raw_qs.split("&"):
    if "=" in pair:
        k, v = pair.split("=", 1)
        params[k] = unquote(v)
echo = params.get("echostr", "")
```
注意：`urllib.parse.parse_qs` 默认也会把 `+` 当空格，不可用。

PITFALL: 企微回调验证失败时，按以下顺序排查（不要跳过步骤）：
1. 查日志确认请求是否到达（先看 webhook_receiver.log，再看 utils 关联的 article-maker.log——两处日志不同！）
2. 如果无日志 → 网络/防火墙问题（检查腾讯云安全组/防火墙）
3. 如果有 "Echo decryption failed" → 先看 article-maker.log 里的具体异常
4. 如果异常说 base64 字符数异常 → URL `+` 截断问题（本文档的 URL parsing pitfall）
5. 如果异常是 padding/解密失败 → AES IV 问题（本文档的 IV pitfall）

PITFALL: 企微后台保存回调URL返回 "openapi回调地址请求不通过" → 这是网络层问题，不是代码问题。企微服务器无法连接到你的回调地址。优先检查腾讯云安全组/防火墙是否放行了该端口，而不是改代码。

PITFALL: 腾讯云轻量服务器使用"防火墙"而非"安全组"（控制台 → 轻量应用服务器 → [实例] → 防火墙 → 添加规则）。CVM 使用安全组。两者入口不同。

PITFALL: 腾讯云轻量服务器防火墙"来源"字段要求 CIDR 格式。填单个 IP 时必须加 `/32` 后缀（如 `47.120.3.199/32`），只填 IP 地址会添加失败。开放所有来源用 `0.0.0.0/0`。

PITFALL: 腾讯云轻量服务器防火墙规则存在"编辑不生效"的已知 bug。添加了端口但仍然不通时，**删除旧规则再新建一条**（先删再建），不要编辑已有规则。

PITFALL: 腾讯云服务器访问自己的公网 IP 会失败（NAT 回流/hairpin NAT 问题）。不要用 `curl https://公网IP:端口` 自测——改用外部工具（check-host.net、用户浏览器）验证端口可达性。

PITFALL: Lighthouse（轻量应用服务器）可能**完全不接受任何公网 IP 入站连接**（包括 SSH 22 端口），即使防火墙规则已添加。这种情况下所有外部流量必须经过 Cloudflare Tunnel 等隧道进入。**验证方法**：用外部工具测试多个端口（22/80/443）——如果全部超时，就说明公网 IP 方向不通，不要继续折腾防火墙规则。切换思路用 Cloudflare Tunnel 或跨服务器反代。
  - 当用户说"防火墙规则加了但还是不通"，第一时间验证公网 IP 是否根本不可达，而不是让用户反复改规则。

PITFALL: **Lighthouse 控制台自带的「文件管理」功能路径受限**。Web 文件管理器通过 TAT agent 运行，存在以下限制：
  - 默认入口可能在 `/root/` 或 `/`，**不在** `/home/<username>/` 下
  - 权限为 `drwxr-x---` 的用户目录（如 `/home/agentuser/`）在文件管理器中**不可见**
  - 隐藏目录（`.` 开头如 `.hermes/`）默认不显示，需手动开启「显示隐藏文件」开关
  - 即使切换到 `/root/`，部分实例上 `/root/` 下的文件也可能不显示

  **用户反馈"找不到路径"时的标准流程**：
  1. 先确认目录确实存在：`ls -la /home/` 或 `ls -la /root/`
  2. 如果用户只需要在文件管理器中查看/下载文件，**直接复制到 `/home/` 下并设 755 权限**：
     ```bash
     sudo cp -r /home/agentuser/.hermes/知识库 /home/知识库
     sudo chmod -R 755 /home/知识库
     ```
     `/home/` 是全局可读的，不会被权限挡住。
  3. 如果 `/home/` 也看不到（极少数情况），改放到用户能访问的已知可见路径。
  4. 不要反复建议用户调整文件管理器设置——直接复制文件是最快、最可靠的方案。

PITFALL: Cloudflare Tunnel DNS 迁移后访问新域名返回 404 而非连接成功——即使 DNS 记录页已自动生成 Tunnel 类型记录、tunnel ingress 配置正确、tunnel 进程健康运行。**根因**：Cloudflare 自动检测并创建了 DNS 记录，但 Zero Trust → Networks → Tunnels 中的 "Public Hostname" 列表可能未自动添加该域名。**修复**：Cloudflare 控制台 → Zero Trust → Networks → Tunnels → 找到对应隧道 → Public Hostname 标签 → 确认该子域名已列出。如未列出，手动添加：Subdomain 填子域名前缀，Domain 选主域名，Service 填 `http://localhost:PORT`。

PITFALL: 向已有 Cloudflare Tunnel 添加新子域名（域名不在 Cloudflare 上时）的完整流程：
  1. Cloudflare 添加站点 → 自动扫描导入 DNS 记录 → 获取 nameserver
  2. 原 DNS 商改 nameserver 指向 Cloudflare
  3. 本机 tunnel config.yml 添加 ingress 规则 → 重启 tunnel
  4. Cloudflare DNS 页自动出现 Tunnel 类型记录（自动检测）
  5. **Zero Trust → Tunnels → Public Hostname 确认子域名已添加**（如未自动添加，手动补）
  遗漏第 5 步会返回 Cloudflare edge 404。

PITFALL: Cloudflare Tunnel 在中国服务器上可用，但需要特定技巧。Quick tunnel（`trycloudflare.com`）能注册连接但**不转发流量**（返回 Cloudflare edge 404，不命中本地服务）。正确方案：

**命名隧道（Named Tunnel）部署步骤：**

1. **获取 API Token**：Cloudflare 控制台 → Profile → API Tokens → Create Custom Token，权限：`Cloudflare Tunnel:Edit` + `DNS:Edit`（Zone 级别选 `icoach.chat` 而非 Account）
2. **创建隧道（API）**：`POST /client/v4/accounts/{account_id}/cfd_tunnel`
3. **获取 Tunnel Token**：`GET .../cfd_tunnel/{id}/token`
4. **配置 Ingress（API）**：`PUT .../cfd_tunnel/{id}/configurations`，指向 `http://localhost:5000`
5. **启动隧道**：`cloudflared tunnel --no-autoupdate --no-prechecks --protocol http2 run --token $TOKEN`
   - `--no-prechecks` 跳过会卡死的 connectivity 检测
   - `--protocol http2` 替代默认 QUIC（QUIC 从国内不稳定）
6. **DNS CNAME**：Cloudflare DNS → `icoach.chat CNAME → {tunnel-id}.cfargotunnel.com`（proxied 橙色云朵）

PITFALL: `cloudflared tunnel login` 浏览器授权回调被 GFW 阻断 → 必须用 API Token 方式。API Token 需 Zone 级别 DNS:Edit（选 `icoach.chat` 不是 Account），否则 DNS CNAME 添加报 "Authentication error"。

PITFALL: 已有 A 记录时添加根域名 CNAME 冲突 → 先删 A 记录再建 CNAME。

PITFALL: CNAME 目标 UUID 漏字符 → 报 1016 Origin DNS error。Tunnel healthy + ingress 正确 + DNS proxied + 仍 1016 → 检查 CNAME 目标是否完整。

详见 `references/cloudflare-tunnel-china.md`。

PITFALL: Nginx 访问日志中 agent 的 curl 测试显示来源 IP 为 `127.0.0.1`、UA 为 `curl/x.x.x`。诊断"企微请求是否到达"时，必须区分 agent 本地测试（127.0.0.1）和企微服务器请求（公网 IP）。如果日志中只有 127.0.0.1 的请求，说明企微从未到达——问题在防火墙。

PITFALL: webhook_receiver.py 和 utils.py 使用**不同的 logger**：webhook_receiver → `webhook_receiver.log`，utils → `article-maker.log`。解密异常详情在 `article-maker.log`，通用错误在 `webhook_receiver.log`。排查时两个日志都要看。

You must use either:
- Tencent Cloud free TrustAsia SSL (needs a domain)
- Let's Encrypt (needs open port 80 or DNS API access)
- A tunnel service with built-in HTTPS (natapp, cpolar — only if accessible)

## 4. Domains: Free vs Paid

See `references/domain-pricing.md` for full comparison.

**Best picks:**
- **Free + instant + accessible from China**: freedns.afraid.org (pick `mooo.com` etc.)
- **Cheapest paid**: `.top` on Tencent Cloud — ¥9 first year, ¥26 renewal
- **Professional**: `.com` — ¥55 first year, ¥75 renewal

PITFALL: DuckDNS, natapp, cpolar websites may be inaccessible from Chinese servers. freedns.afraid.org is the most reliable free option tested.

PITFALL: `.xyz` is cheap first year (¥6) but renewal jumps to ¥60-80. Avoid.

## 5. Troubleshooting: Network/Port Checks

```bash
# Check if foreign sites accessible
curl -I --connect-timeout 5 https://github.com

# Check if specific port open (inbound)
curl -s http://httpbin.org/ip  # shows public IP

# Check local firewall
sudo ufw status
sudo iptables -L OUTPUT -n

# Check Docker pull
sudo docker pull python:3.11-slim
```

If GitHub/Docker Hub time out but `mirrors.cloud.tencent.com` returns 200, the block is at the cloud provider security group level, not the server itself.

## 6. Service Persistence

### Systemd Service (Recommended)

```ini
# /etc/systemd/system/myapp.service
[Unit]
Description=My Web App
After=network.target

[Service]
Type=simple
User=agentuser
WorkingDirectory=/home/agentuser/project/scripts
Environment=PYTHONPATH=/home/agentuser/project/scripts
ExecStart=/usr/bin/python3 webhook_receiver.py
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
```

```bash
sudo systemctl daemon-reload
sudo systemctl enable --now myapp
sudo systemctl status myapp
```

### Quick Background Process (Non-Docker)
```bash
nohup python3 webhook_receiver.py > webhook.log 2>&1 &
```

## 7. Verification Checklist

After deployment, verify in this order:
1. `curl -s http://127.0.0.1:5000/health` — app runs locally
2. `ss -tlnp | grep -E "80|443|8443"` — Nginx listens on `0.0.0.0`, NOT `127.0.0.1`
3. `curl -sk https://127.0.0.1/health` — local HTTPS works
4. External test — wait for DNS propagation (5-10 min) AND firewall rules to take effect
5. If `curl https://domain/health` times out but (1)-(3) pass: firewall is the culprit, recheck console

**PITFALL**: The server cannot curl its own public IP (no hairpin NAT). Test externally via check-host.net or user's browser.

## 8. Next.js Deployment on Chinese Servers

Node.js 22 is pre-installed on Ubuntu 24.04. Use npm mirror before install:
```bash
npm config set registry https://registry.npmmirror.com
```

**Native package fallback**: Packages with native compilation (`better-sqlite3`) fail because prebuild downloads are blocked. Use pure-JS alternatives (`sql.js`).

**Process survival**: `next start` spawns a child `next-server` process that survives parent bash death. When killing, target ALL: `fuser -k PORT/tcp` is more reliable than `kill PID`.

**Restart pitfall**: `npm run build` produces new `.next` but the running process loaded the old build at startup. Safe sequence:
```bash
fuser -k PORT/tcp && sleep 1 && ss -tlnp | grep PORT   # verify port freed
npm run build && node node_modules/.bin/next start -p PORT &
```

**`.env.local` not hot-reloaded**: Changing `.env.local` does NOT take effect during running process. Must fully kill and restart.

See also: `references/nextjs-deploy-china.md`, `references/nextjs-live-debugging.md`, `references/nextjs-local-auth-db.md`

## 9. DeepSeek API Quirks

**`response_format: json_object` requires "json" in prompt**: DeepSeek validates that when using `response_format: { type: 'json_object' }`, the word "json" (case-insensitive) must appear somewhere in the system or user prompt. Without it: HTTP 400 `"Prompt must contain the word 'json' in some form"`. Fix: add `你必须以 JSON 格式输出` to the system prompt.

See also: `references/deepseek-api-quirks.md`, `references/gfw-workarounds.md`

## References

- `references/caddy-reverse-proxy.md` — Caddy reverse proxy + auto-SSL on Chinese servers (simpler Nginx alternative). Covers ZeroSSL EAB pitfall, HTTP-only mode during DNS transition, Cloudflare latency diagnosis
- `references/cloudflare-tunnel-china.md` — Cloudflare Tunnel from Chinese servers: named tunnels, API auth, DNS CNAME, error 1016, token expiry
- `references/cloudflare-tunnel-debugging.md` — Tunnel 502 diagnostics, token-vs-config tunnels, ingress URL debugging
- `references/deepseek-api-quirks.md` — DeepSeek `json_object` prompt requirement, model selection
- `references/domain-pricing.md` — Full comparison of domain registrars, pricing, and renewal costs
- `references/gfw-workarounds.md` — GFW workarounds: npm native packages, GitHub alternatives, Next.js on Tencent
- `references/github-clone-china.md` — GitHub clone from China: API zipball, SSH-over-443, mirror comparison
- `references/hermes-lightvela-architecture.md` — Lightvela Agent 两服务器架构：Hermes 运行在独立服务器上，与用户 Lighthouse 实例隔离。文件共享、权限锁 700、数据迁移方案
- `references/tencent-tat-remote-exec.md` — TAT API remote command execution: TC3 signing, RunCommand, polling, cross-server deployment pattern
- `references/gdcjgk-net-infra.md` — gdcjgk.net 双服务器架构（腾讯 Lighthouse + 阿里云）、DNS 提供商、跨云通信限制
- `references/nextjs-deploy-china.md` — Next.js on Tencent Cloud: npm mirror, env vars, Nginx proxy, 500 errors
- `references/nextjs-live-debugging.md` — Next.js live debugging: process timestamps, port checks, stale builds
- `references/nextjs-local-auth-db.md` — Replacing Clerk+Supabase with local cookie auth + JSON-file DB
- `references/we-mp-rss-api.md` — we-mp-rss API: endpoint paths, auth headers, response format
- `references/we-mp-rss-auth.md` — we-mp-rss auth pitfalls: SHA256 vs bcrypt, user_id requirement, database setup
- `references/we-mp-rss-ops.md` — we-mp-rss 运维：调度器卡死修复、API 端点路径、认证
- `references/wework-bot-architecture.md` — 企微 4 integration paths comparison with decision matrix
- `references/wx-callback-debug.md` — 企微回调 AES decryption (IV bug, URL + truncation) and diagnostic flow
