> ## Documentation Index
> Fetch the complete documentation index at: https://ppio.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# OpenVPN

你可以使用 `.ovpn` 客户端配置文件将 PPIO Sandbox 接入 OpenVPN 网络,使 sandbox 能够通过 VPN 隧道访问私有网络。这对于访问内部服务、使用固定出口路径,或将特定流量路由到你自己的基础设施都很有用。

流程是:创建 sandbox,安装 OpenVPN,上传 `.ovpn` 配置,在后台启动 OpenVPN,然后确认 `tun0` 隧道网卡已就绪。

***

## 前置条件

* `pip install ppio-sandbox`
* `export PPIO_API_KEY=...`
* 本地已有一个 OpenVPN 客户端配置文件(`.ovpn`)

***

## 将 sandbox 接入 OpenVPN

<CodeGroup>
  ```python Python theme={null}
  """ppio-openvpn.py

  Connect a PPIO sandbox to an OpenVPN network using a `.ovpn` client
  configuration file (non-interactive).
  """

  import os
  import time

  from ppio_sandbox import PPIO

  ppio = PPIO()

  # 本地 .ovpn 客户端配置文件路径（位于项目目录中）。
  OVPN_PATH = os.path.join(os.path.dirname(__file__), "..", "client1.ovpn")
  REMOTE_CONFIG = "/home/user/client.ovpn"
  REMOTE_LOG = "/tmp/openvpn.log"

  def setup_openvpn(ovpn_config: str):
      """Connect a PPIO sandbox to an OpenVPN network."""

      # 创建 Sandbox
      print("Creating sandbox...")
      sandbox = ppio.sandbox.create("base")
      print(f"Sandbox created: {sandbox.sandbox_id}")

      # 步骤 1：安装 OpenVPN
      print("\\nInstalling OpenVPN...")
      response = sandbox.commands.run(
          "sudo apt update && sudo apt install -y openvpn",
          timeout=300,
      )
      if response.exit_code != 0:
          print(f"Error installing OpenVPN: {response.stderr}")
          return sandbox
      print("OpenVPN installed successfully.")

      # 步骤 2：写入 OpenVPN 配置文件
      print("\\nWriting OpenVPN configuration...")
      sandbox.files.write(REMOTE_CONFIG, ovpn_config)
      print(f"Configuration written to {REMOTE_CONFIG}")

      # 步骤 3：在后台启动 OpenVPN。
      #   `--pull-filter ignore redirect-gateway` 阻止服务端接管
      #   Sandbox 的默认路由 —— 否则 SDK 自身的控制连接
      #   会被卷入隧道，导致后续所有命令超时。
      #   VPN 子网路由仍会被正常安装。
      print("\\nStarting OpenVPN tunnel...")
      sandbox.commands.run(
          f'nohup sudo openvpn --config {REMOTE_CONFIG} '
          f'--pull-filter ignore "redirect-gateway" '
          f"> {REMOTE_LOG} 2>&1 &",
          background=True,
      )

      # 等待连接建立
      print("Waiting for VPN connection to establish...")
      time.sleep(10)

      # 步骤 4：验证连接 —— 检查 tun0 网卡是否存在
      print("\\nVerifying OpenVPN connection...")
      response = sandbox.commands.run("ip addr show tun0 2>/dev/null || true")
      if "inet " in response.stdout:
          print("VPN tunnel interface (tun0) is up:")
          print(response.stdout)
      else:
          print("Warning: tun0 interface not found. Checking OpenVPN logs...")
          log = sandbox.commands.run(f"cat {REMOTE_LOG} 2>/dev/null || true")
          print(f"OpenVPN log:\\n{log.stdout}")
          return sandbox

      print("\\nOpenVPN connection established successfully.")
      return sandbox

  def main() -> None:
      with open(OVPN_PATH, "r") as f:
          ovpn_config = f.read().strip()

      sandbox = setup_openvpn(ovpn_config)
      try:
          print("\\nSandbox is connected. Press Ctrl+C to disconnect and kill it.")
          while True:
              time.sleep(3600)
      except KeyboardInterrupt:
          print("\\nInterrupted — shutting down.")
      finally:
          sandbox.kill()
          print("Sandbox killed")

  if __name__ == "__main__":
      main()
  ```

  ```javascript JavaScript & TypeScript theme={null}
  // 使用 `.ovpn` 客户端配置文件将 PPIO Sandbox 接入 OpenVPN 网络
  // （非交互式）。
  import fs from 'fs'
  import path from 'path'

  import { PPIO } from 'ppio-sandbox'

  const ppio = new PPIO()

  // 本地 .ovpn 客户端配置文件路径（位于项目目录中）。
  const OVPN_PATH = path.join(__dirname, '..', 'client1.ovpn')
  const REMOTE_CONFIG = '/home/user/client.ovpn'
  const REMOTE_LOG = '/tmp/openvpn.log'

  async function setupOpenVPN(ovpnConfig) {

    // 创建 Sandbox
    console.log('Creating sandbox...')
    const sandbox = await ppio.sandbox.create('base')
    console.log(`Sandbox created: ${sandbox.sandboxId}`)

    // 步骤 1：安装 OpenVPN
    console.log('\\nInstalling OpenVPN...')
    const install = await sandbox.commands.run(
      'sudo apt update && sudo apt install -y openvpn',
      { timeoutMs: 300_000 }
    )
    if (install.exitCode !== 0) {
      console.log(`Error installing OpenVPN: ${install.stderr}`)
      return sandbox
    }
    console.log('OpenVPN installed successfully.')

    // 步骤 2：写入 OpenVPN 配置文件
    console.log('\\nWriting OpenVPN configuration...')
    await sandbox.files.write(REMOTE_CONFIG, ovpnConfig)
    console.log(`Configuration written to ${REMOTE_CONFIG}`)

    // 步骤 3：在后台启动 OpenVPN。
    //   `--pull-filter ignore redirect-gateway` 阻止服务端接管
    //   Sandbox 的默认路由 —— 否则 SDK 自身的控制连接
    //   会被卷入隧道，导致后续所有命令超时。
    //   VPN 子网路由仍会被正常安装。
    console.log('\\nStarting OpenVPN tunnel...')
    await sandbox.commands.run(
      `nohup sudo openvpn --config ${REMOTE_CONFIG} ` +
        `--pull-filter ignore "redirect-gateway" ` +
        `> ${REMOTE_LOG} 2>&1 &`,
      { background: true }
    )

    // 等待连接建立
    console.log('Waiting for VPN connection to establish...')
    await new Promise((r) => setTimeout(r, 10_000))

    // 步骤 4：验证连接 —— 检查 tun0 网卡是否存在
    console.log('\\nVerifying OpenVPN connection...')
    const res = await sandbox.commands.run('ip addr show tun0 2>/dev/null || true')
    if (res.stdout.includes('inet ')) {
      console.log('VPN tunnel interface (tun0) is up:')
      console.log(res.stdout)
    } else {
      console.log('Warning: tun0 interface not found. Checking OpenVPN logs...')
      const log = await sandbox.commands.run(`cat ${REMOTE_LOG} 2>/dev/null || true`)
      console.log(`OpenVPN log:\\n${log.stdout}`)
      return sandbox
    }

    console.log('\\nOpenVPN connection established successfully.')
    return sandbox
  }

  async function main() {
    const ovpnConfig = fs.readFileSync(OVPN_PATH, 'utf8').trim()
    const sandbox = await setupOpenVPN(ovpnConfig)
    try {
      console.log('\\nSandbox is connected. Press Ctrl+C to disconnect and kill it.')
      await new Promise(() => {}) // keep running until interrupted
    } finally {
      await sandbox.kill()
      console.log('Sandbox killed')
    }
  }

  main().catch(console.error)
  ```
</CodeGroup>

<Warning>
  **重要:** `--pull-filter ignore "redirect-gateway"` 参数可以防止 VPN 服务器接管 sandbox 的默认路由。如果不加该参数,SDK 的控制通道可能被路由进隧道,导致后续命令超时。VPN 子网路由仍会正常安装,因此你既能访问 VPN 网络,SDK 也能继续正常工作。
</Warning>

OpenVPN 以 `background=True` 方式启动,因此在你继续执行其他命令时它会保持运行。通过检查 `tun0` 网卡是否有 `inet` 地址来确认隧道已建立;如果隧道没有建立,请检查 `/tmp/openvpn.log`。

***

## 通过 Remote Shell 使用 OpenVPN

除了完全通过 SDK 操作,你也可以使用 CLI 在远程 shell 中交互式地配置 OpenVPN。这适合一次性连接、手动编辑 `.ovpn` 文件或实时调试隧道。

### 1. 连接到 sandbox

通过 sandbox ID 打开一个交互式远程 shell,将你的终端接入 sandbox 内的 shell。关于 connect 命令的详细说明,参见[远程 Shell](/docs/sandbox/cli-remote-shell)。

```bash theme={null}
ppio sandbox connect <sandbox_id>
```

### 2. 安装 OpenVPN 及相关工具

在远程 shell 中安装 OpenVPN,同时安装 `tmux`(让隧道在后台会话中持续运行)和 `vim`(用于编辑配置)。

```bash theme={null}
sudo apt update && sudo apt install -y openvpn tmux vim
```

### 3. 创建 / 编辑 .ovpn 配置

用 `vim` 打开客户端配置文件,粘贴(或调整)你的 OpenVPN 客户端配置,然后保存退出。文件会以 `client.ovpn` 为名创建在当前目录。

```bash theme={null}
sudo vim client.ovpn
```

### 4. 在后台 tmux 会话中启动 OpenVPN

在一个分离的 `tmux` 会话中启动 OpenVPN,使隧道独立于你的 shell 持续运行。`-d` 表示以分离方式启动会话,`-s openvpn` 为会话命名,引号中的命令是会话内要运行的内容。

```bash theme={null}
tmux new -d -s openvpn 'sudo openvpn client.ovpn'
```

隧道现已在后台运行。你可以继续使用当前 shell、断开连接,或按需管理该会话:

```bash theme={null}
# 连接以查看 OpenVPN 输出 / 日志
tmux attach -t openvpn
# 再次分离而不停止它：按 Ctrl+b 然后按 d

# 验证隧道网卡已就绪
ip addr show tun0

# 通过终止会话来停止隧道
tmux kill-session -t openvpn
```

<Note>
  **注意:** 运行 `tmux attach -t openvpn` 可重新接入会话查看 OpenVPN 实时日志;按 **Ctrl+b** 再按 **d** 可分离会话并让其继续运行。
</Note>
