class_docstr: SSHTunnel represents a control to start, stop and check an SSH tunnel status.
  Its internal state could be serialized to a file and restored from it using pydantic.
code: def create(
      cls,
      host: str,
      port: int,
      app_port: int,
      *,
      id_rsa_path: str = "~/.ssh/id_rsa",
      docker_host: Optional[str] = None,
      docker_port: Optional[int] = None,
  ) -> "SSHTunnel":
      temp_dir = tempfile.mkdtemp()
      os.chmod(temp_dir, 0o755)  # grant any user read access
      control_path = f"{temp_dir}/control"
  
      cmd = ["ssh", "-i", id_rsa_path, "-M", "-S", control_path]
      cmd += [
          "-o",
          "StrictHostKeyChecking=no",
          "-o",
          "UserKnownHostsFile=/dev/null",
      ]
      cmd += [
          "-o",
          "StreamLocalBindMask=0111",
          "-o",
          "StreamLocalBindUnlink=yes",
      ]
      cmd += ["-o", "ServerAliveInterval=60"]
      cmd += [
          "-f",
          "-N",
          "-L",
          f"{_sock_path(temp_dir)}:localhost:{app_port}",
      ]
      if docker_host is not None:
          # use `host` as a jump host
          proxy = ["ssh", "-i", id_rsa_path, "-W", "%h:%p"]
          proxy += [
              "-o",
              "StrictHostKeyChecking=no",
              "-o",
              "UserKnownHostsFile=/dev/null",
          ]
          proxy += ["-p", str(port), host]
          cmd += ["-o", f"ProxyCommand={shlex.join(proxy)}"]
          # connect to `docker_host`
          cmd += ["-p", str(docker_port), docker_host]
      else:
          # connect to `host` directly
          cmd += ["-p", str(port), host]
  
      start_cmd = cmd
      exit_cmd = ["ssh", "-S", control_path, "-O", "exit"]
      check_cmd = ["ssh", "-S", control_path, "-O", "check"]
      return cls(
          temp_dir=temp_dir,
          start_cmd=start_cmd,
          exit_cmd=exit_cmd,
          check_cmd=check_cmd,
      )
  
file_path: dstack/gateway/src/dstack/gateway/services/tunnel.py
imports: import logging
  import os
  import shlex
  import shutil
  import subprocess
  import tempfile
  from typing import List, Optional
  from pydantic import BaseModel
  from dstack.gateway.errors import SSHError
label_desc: Use image node_id 1 for calling the SSHTunnel obj's underlying member method code with example usage: obj.create(cls, host, port, app_port) and returns: cls