# SAP 内网隧道部署文档

> **创建日期**: 2026-04-04
> **最后更新**: 2026-05-14（autossh + systemd 自愈升级）

## 背景

SAP 服务（`las-popci-01.faradayfuture.com:51000`）部署在公司内网，公网服务器无法直接访问。通过内网机器作为跳板，建立 SSH 反向隧道 + TCP 代理，使公网服务器能访问 SAP SOAP 接口。

## 网络架构

```
公网服务器                     内网跳板机                    SAP 服务器
(开发/UAT/正式)               (10.68.100.57)              (10.68.100.238)
                               las-sdiff-01

localhost:19443  ←─SSH反向隧道─  19444(Python代理)  ──→  51000(HTTP)
localhost:19022  ←─SSH反向隧道─  22(SSH)                 las-popci-01.faradayfuture.com
```

## 服务器清单

| 环境 | 公网 IP | SSH 用户 | SAP 代理端口 | SSH 回连端口 |
|------|---------|----------|-------------|-------------|
| 开发 | 43.159.171.147 | chentao | 19443 | 19022 |
| UAT | 43.153.69.73 | ubuntu | 19443 | 19022 |
| 正式 | 43.130.6.44 | srvadmin | 19443 | 19022 |

## 内网跳板机信息

| 项目 | 值 |
|------|-----|
| 内网 IP | 10.68.100.57 |
| 主机名 | las-sdiff-01 |
| 用户 | chentao |
| OS | Ubuntu 22.04 (5.15.0-140-generic) |
| DNS 服务器 | 10.68.100.10, 10.68.100.11 |
| 搜索域 | faradayfuture.com |

## SAP 服务信息

| 项目 | 值 |
|------|-----|
| 域名 | las-popci-01.faradayfuture.com |
| 内网 IP | 10.68.100.238 |
| HTTP 端口 | 51000 (可用) |
| HTTPS 端口 | 51001 (不可用) |
| 协议 | HTTP (非 HTTPS) |
| SOAP 路径 | /XISOAPAdapter/MessageServlet |
| 生产认证 | SAP_USER_PLACEHOLDER / SAP_PASSWORD_PLACEHOLDER |
| 测试认证 | RFC_POD / SAP_PASSWORD_PLACEHOLDER |

## 内网跳板机上的组件

### 1. TCP 代理（sap-proxy.py）

**路径**: `/home/chentao/sap-proxy.py`
**日志**: `/home/chentao/sap-proxy.log`
**端口**: 19444 → las-popci-01.faradayfuture.com:51000
**作用**: 将本地 19444 端口的 TCP 连接转发到 SAP

> 为什么需要代理而不是直接用 SSH -R 转发？
> 因为 SSH 反向隧道直连 SAP 51000 端口时出现 connection reset，
> 通过 Python TCP 代理中转后正常工作。

### 2. SSH 反向隧道

每台公网服务器一条隧道，通过 SSH 的 `-R` 参数将公网服务器的本地端口映射到内网代理：

- `-R 19443:localhost:19444` — SAP 代理端口
- `-R 19022:localhost:22` — SSH 回连端口（用于远程管理内网机器）

### 3. 自愈守护（autossh + systemd --user）

> 2026-05-14 升级：从 `crontab @reboot + nohup ssh` 改为 `autossh -M 0` + systemd user service 双层守护。
> 之前的 `@reboot` 模式机器开机才跑一次、断了不自愈；现在 ssh 子进程死 ~3s 重建，autossh 死 systemd 10s 重建，机器重启 linger + `WantedBy=default.target` 自动起。

**Service 文件**：`~/.config/systemd/user/sap-tunnel-{dev,uat,prod}.service`

每个 unit 用统一模板（仅 target 不同）：

```ini
[Unit]
Description=SAP reverse SSH tunnel to <env> (<user>@<host>)
After=network-online.target
Wants=network-online.target

[Service]
Environment=AUTOSSH_GATETIME=0
Environment=AUTOSSH_PORT=0
ExecStart=/usr/bin/autossh -M 0 -N \
  -o ServerAliveInterval=30 -o ServerAliveCountMax=3 \
  -o ExitOnForwardFailure=yes -o BatchMode=yes \
  -o StrictHostKeyChecking=accept-new \
  -R 19443:localhost:19444 -R 19022:localhost:22 \
  <user>@<host>
Restart=always
RestartSec=10

[Install]
WantedBy=default.target
```

| env | target |
|---|---|
| dev  | `chentao@43.159.171.147` |
| uat  | `ubuntu@43.153.69.73` |
| prod | `srvadmin@43.130.6.44` |

**前置条件**（一次性配置，已完成）：
- `sudo apt install autossh`
- `sudo loginctl enable-linger chentao`（用户不登录 systemd user services 也能跑）

**`sap-proxy.py` 仍走 crontab `@reboot`**——已稳定运行 1+ 年没死过，本次升级未触碰。
未来可一并 systemd 化。

> 旧的 crontab `@reboot ssh -R ...` 三行已注释（带 `[autossh migration 2026-05-14]` 标记），
> 备份在 `/tmp/crontab.backup.20260514-0708`。观察期稳定后可删除。

### 4. SSH 密钥

内网机器的密钥对: `/home/chentao/.ssh/id_ed25519`
公钥指纹: `SHA256:vJVMUmNEiuFdZ6htlZccsaGYT52+2rAojeX3D+tuPLg`
已添加到三台公网服务器的 `authorized_keys` 中。

### 5. DNS 配置

`/etc/resolv.conf` 需要配置为公司内网 DNS，否则无法解析 SAP 域名：

```
nameserver 10.68.100.10
nameserver 10.68.100.11
search faradayfuture.com
```

> 注意: 原始的 systemd-resolved (127.0.0.53) 不工作，
> netplan 配置 (/etc/netplan/*.yaml) 中虽然定义了正确的 DNS，
> 但 systemd-resolved 服务未正常运行，需要手动写入 /etc/resolv.conf。

## 公网服务器配置

### .env 配置

在各环境的 `backend/.env` 中添加 SAP endpoint 覆盖：

```bash
# 开发环境（43.159.171.147）特殊：通过正向隧道走 19445
SAP_PRODUCTION_ENDPOINT=http://localhost:19445/XISOAPAdapter/MessageServlet?senderParty=&senderService=BC_Dingding&receiverParty=&receiverService=S4P300&interface=SIOS_PurchaseRequestion&interfaceNamespace=http%3A%2F%2Fff.com%2FMM%2FPurchaseRequestion

# UAT 和正式环境：直接走反向隧道 19443
SAP_PRODUCTION_ENDPOINT=http://localhost:19443/XISOAPAdapter/MessageServlet?senderParty=&senderService=BC_Dingding&receiverParty=&receiverService=S4P300&interface=SIOS_PurchaseRequestion&interfaceNamespace=http%3A%2F%2Fff.com%2FMM%2FPurchaseRequestion
```

### 开发环境额外配置

开发机（43.159.171.147）多了一层正向隧道（因为开发过程中先建立的）：

```
localhost:19445 ──正向隧道──→ 内网机器:19444(代理) ──→ SAP:51000
```

这条正向隧道是通过以下命令建立的：
```bash
ssh -f -N -L 19445:localhost:19444 -p 19022 chentao@localhost
```

## 运维手册

### 检查隧道状态

从公网服务器上：
```bash
# 检查 SAP 代理端口是否在监听
ss -tlnp | grep 19443

# 测试 SAP 连通性
curl -k --max-time 5 -u "SAP_USER_PLACEHOLDER:SAP_PASSWORD_PLACEHOLDER" -o /dev/null -w "HTTP:%{http_code}\n" "http://localhost:19443/XISOAPAdapter/MessageServlet"
```

### SSH 回连内网机器

从任意一台公网服务器：
```bash
ssh -p 19022 chentao@localhost
```

### 隧道运维（systemd --user）

正常情况下不需要人为干预——autossh + systemd 自愈。常用命令在跳板机上：

```bash
# 状态
systemctl --user status sap-tunnel-prod          # 单个
systemctl --user list-units 'sap-tunnel-*'       # 全部

# 重启 / 启停（极少用到）
systemctl --user restart sap-tunnel-prod
systemctl --user stop sap-tunnel-prod

# 实时日志
journalctl --user -u sap-tunnel-prod -f
journalctl --user -u sap-tunnel-prod --since '1 hour ago'
```

**自愈验证**（任何怀疑故障时跑一遍）：

```bash
# 1. kill 某条 ssh 子进程（注意不要 kill autossh 父进程，否则它就不重建了）
pkill -9 -f '^/usr/bin/ssh.*srvadmin@43.130.6.44'

# 2. 等 5 秒
sleep 5

# 3. 应该看到新 ssh 子进程已起，autossh 父进程 PID 不变
pgrep -af 'ssh.*-R 19443'
```

实测重建时间 ~3 秒。

### 应急：systemd 也救不回来

极小概率发生（autossh 自身崩 + systemd Restart 也失败）：

```bash
systemctl --user daemon-reload
systemctl --user restart sap-tunnel-{dev,uat,prod}
```

如果还不行，看 `journalctl --user -u sap-tunnel-prod -e` 找根因；
最后兜底是临时手起 `nohup ssh -N -R ...` 救一次。

## 已知限制

1. **DNS 配置可能被覆盖**: `systemd-resolved` 或 `netplan apply` 可能覆盖手动写入的 `/etc/resolv.conf`，需要关注。
2. **单点故障**: 内网跳板机（10.68.100.57）是唯一通道，如果该机器不可用则所有 SAP 访问中断。
3. **缺主动监控**: 当前没有 health probe；隧道死 → SAP 同步报错 → 用户反馈才发现。tracking issue: #366。
