首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >用腾讯codebuddy写了个 SSH 内网穿透工具,一键搞定远程访问

用腾讯codebuddy写了个 SSH 内网穿透工具,一键搞定远程访问

原创
作者头像
高老师
发布2026-07-23 17:22:49
发布2026-07-23 17:22:49
1130
举报

用 codebuddy 写了个 SSH 内网穿透工具,一行命令搞定远程访问+SSH免密配置

没有公网 IP?没关系,一根 SSH 隧道就能把本地服务暴露到公网。更疯狂的是——这个工具是我用 AI 对话「聊」出来的,全程没查文档、没翻 Stack Overflow。


缘起:被内网穿透逼疯的下午

事情是这样的:我在家里的一台树莓派上跑了一个小 Web 服务,想在公司访问它。家里是移动宽带,没有公网 IP,传统方案无非两种:

  • frp / ngrok 之类 — 需要额外部署服务端,配置繁琐
  • 花生壳之流 — 限速、收费、不够 geek

而我的云服务器上本来就有 SSH 服务在跑。脑子里突然闪过一个念头:

「SSH 不是能做反向隧道吗?用 autossh 保活,不就是一个最轻量的内网穿透方案?」

想法有了,剩下就是把它写出来。但这次我不想手动码代码——我想试试全程用 AI 把这个工具写出来


AI 充当了我的结对编程搭档

我的开发流程大概是这样的:

第一轮对话:把想法翻译成指令

代码语言:bash
复制
我:我想写一个 Python 脚本,自动配置 SSH 免密登录 + autossh 反向隧道。
    要求:跨平台,支持 Windows/Linux/macOS。
    自动检测并安装 autossh,然后用 paramiko 连接服务器,上传公钥,
    最后用 autossh -R 把本地 80 端口穿透出去

AI 直接给我生成了 setup_ssh.py 的骨架——生成 RSA 密钥对、用 paramiko 连接服务器、把公钥追加到 authorized_keys。逻辑清晰,异常处理也考虑到了认证失败、网络超时等边界情况。

第二轮对话:加个主入口,一键搞定

代码语言:bash
复制
我:再写个 main.py,把 setup_ssh.py 串起来。
    启动时先检查 paramiko 装没装,没装自动 pip install。
    autossh 也是,Linux 下自动安装,Windows 跳过。
    最后交互式询问要不要开反向隧道

于是 main.py 出来了。最让我惊艳的是,AI 自己处理了跨平台的差异:

  • Windows 下用 where 检测命令,Linux/macOS 用 which
  • autossh 在 Windows 上不可用,自动跳过
  • 多个 Linux 发行版的包管理器都覆盖了(apt/yum/dnf/pacman/brew)
  • 启动隧道前还知道 kill_existing_autossh() 防止端口冲突

第三轮对话:修 bug、加细节

代码语言:bash
复制
我:pgrep 找不到进程是因为 pgrep 可能没装,加个降级方案
我:serverAliveInterval 设成多少合适?帮我想想
我:日志写到哪?要不要加个 /var/log/autossh.log?

AI 每次都能精准定位到要改的代码位置,我也就越来越信任它,直接从「写代码」变成了「提需求」。


这个工具到底做了什么

简单来说,就三步路:

代码语言:bash
复制
你(内网机器)                    云服务器(有公网 IP)
──────────────                    ────────────────────
1. 免密 SSH ──────────────────→   公钥存好,以后不用输密码
2. autossh -R 8081:localhost:80 → 服务器监听 8081 端口
3. 别人访问 服务器IP:8081 ─────→  流量转发到你本地的 80 端口

这就是 SSH 反向隧道(Reverse SSH Tunnel) 的本质:

  • -R 8081:localhost:80 的意思是:把远程服务器的 8081 端口,映射到我这台机器的 80 端口
  • autossh 负责盯着这条隧道,断了自动重连
  • 你的本地服务天然跑在 localhost,不需要任何改造

对比传统方案:

方案

额外服务

配置难度

安全性

自由度

frp/ngrok

需要服务端

一般

花生壳

依赖第三方

受限

SSH 反向隧道

无(SSH 本来就有)


核心代码拆解

1. 自动生成密钥 + 免密登录

代码语言:python
复制
# setup_ssh.py 核心逻辑
def setup_ssh_key(hostname, username, password, public_key):
    ssh = paramiko.SSHClient()
    ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
    ssh.connect(hostname, username=username, password=password)
    
    commands = [
        'mkdir -p ~/.ssh',
        'chmod 700 ~/.ssh',
        f'echo "{public_key}" >> ~/.ssh/authorized_keys',
        'chmod 600 ~/.ssh/authorized_keys',
    ]
    for cmd in commands:
        ssh.exec_command(cmd)

之前每次都要手动 ssh-copy-id,现在脚本一条龙搞定。paramiko 在这里充当了「自动化 SSH 客户端」的角色,对于需要批量配置多台机器的场景尤其好用。

2. 反向隧道 + 自动保活

代码语言:python
复制
# main.py 核心——autossh 反向隧道
cmd = (
    f'autossh -fN '
    f'-o "ServerAliveInterval 60" '
    f'-o "ServerAliveCountMax 3" '
    f'-R 8081:localhost:80 {USERNAME}@{SERVER_IP}'
)

几个关键参数:

  • -f:后台运行,不占终端
  • -N:不执行远程命令,纯做端口转发
  • -R 8081:localhost:80:这是灵魂,反向端口转发
  • ServerAliveInterval 60:每 60 秒发心跳包,防止被中间路由器掐断
  • autossh 包装:一旦检测到隧道断开,自动重新建立连接

3. 跨平台兼容的细节

代码语言:python
复制
def find_command(cmd):
    finder = "where" if os.name == "nt" else "which"
    ...

def install_autossh():
    if os.name == "nt":
        return True  # Windows 下跳过
    ...

在 AI 的帮助下,这些跨平台的边角细节处理得很干净。Windows 用户虽然不能用 autossh(可以用 plink 替代,这是后续优化的方向),但至少脚本不会崩溃。


AI 开发的真实体验

整个工具从想法到可用,前后花了不到 30 分钟。说几个有意思的观察:

好的一面

  1. 上下文记忆很强:我第一轮说「用 paramiko」,后面再提「连接服务器」,AI 自动就知道我说的是 paramiko 的连接方式,不会跑偏到 subprocess 去调 ssh 命令
  2. 边界情况考虑周全:我自己写可能会漏掉的 try-except、权限检查、已存在进程清理——AI 全给补上了
  3. 跨平台经验超出预期:Windows 和 Linux 的差异处理、不同包管理器的适配,这些不是简单就能写出来的,说明训练数据里的实践经验很丰富

还需要人兜底的地方

  1. 安全问题不会提醒:密码硬编码在脚本里(PASSWORD = "..."),AI 不会主动提醒你这是安全隐患。正确的做法是用环境变量或密钥文件
  2. 大方向把握靠人:技术方案的选择(为什么用 SSH 隧道而不是 frp?)、架构的权衡(autossh 的局限性在哪?)——这些决策只能靠人来定,AI 是执行者
  3. 复杂调试 AI 帮不上:如果 autossh 隧道死活不通,到底是防火墙?SELinux?还是 sshd_config 里 GatewayPorts 没开?这类需要「排查系统环境」的问题,AI 只能给通用的排查清单

后续可以玩的方向

基于这个基础,用 AI 继续迭代的话可以搞这些:

  • 多端口穿透:一次性把 80、443、3306 等多个端口都穿透出去
  • 配置持久化:加个 config.yaml,支持多台服务器的配置管理
  • Web 管理面板:起一个本地 Web 面板,可视化查看隧道状态、一键启停
  • Docker 化:打包成一个容器,启动即穿透,真正零配置

上述每一项,大概率也都是「跟 AI 聊几句」就能出 MVP 的程度。


完整代码

main.py

代码语言:python
复制
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
简化的SSH免密登录配置脚本
直接使用预设的服务器信息
"""

import subprocess
import sys
import os
import platform

# 服务器配置信息
SERVER_IP = "xxxxxxxxxx"
USERNAME = "root"
PASSWORD = "Ai-TEst2@135246"

def find_command(cmd):
    """查找命令是否存在"""
    try:
        # Windows 用 where,类 Unix 用 which
        finder = "where" if os.name == "nt" else "which"
        result = subprocess.run(
            [finder, cmd],
            capture_output=True, text=True
        )
        return result.returncode == 0
    except FileNotFoundError:
        return False

def install_autossh():
    """检测并安装autossh(Windows跳过)"""
    # Windows 下不需要管理 autossh,直接跳过
    if os.name == "nt":
        print("检测到Windows环境,跳过autossh检测")
        return True

    # 已安装则直接返回
    if find_command("autossh"):
        print("autossh已安装")
        return True

    print("未检测到autossh,开始自动安装...")

    # 各发行版的安装命令
    install_cmds = {
        "apt-get": ["sudo", "apt-get", "install", "-y", "autossh"],
        "yum":     ["sudo", "yum", "install", "-y", "autossh"],
        "dnf":     ["sudo", "dnf", "install", "-y", "autossh"],
        "brew":    ["brew", "install", "autossh"],
        "pacman":  ["sudo", "pacman", "-S", "--noconfirm", "autossh"],
    }

    # 找到第一个可用的包管理器并执行安装
    for pm, cmd in install_cmds.items():
        if not find_command(pm):
            continue
        try:
            print(f"使用 {pm} 安装autossh...")
            subprocess.run(cmd, check=True)
            print("autossh安装成功!")
            return True
        except subprocess.CalledProcessError as e:
            print(f"通过 {pm} 安装失败,退出码: {e.returncode}")
            return False
        except FileNotFoundError:
            continue

    print("未找到支持的包管理器,请手动安装autossh:")
    print("  Debian/Ubuntu: sudo apt-get install autossh")
    print("  CentOS/RHEL:   sudo yum install autossh")
    print("  Fedora:        sudo dnf install autossh")
    print("  macOS:         brew install autossh")
    print("  Arch:          sudo pacman -S autossh")
    return False

def install_paramiko():
    """安装paramiko库"""
    try:
        import paramiko
        return True
    except ImportError:
        print("正在安装paramiko库...")
        try:
            subprocess.check_call([sys.executable, "-m", "pip", "install", "paramiko"])
            print("paramiko安装成功!")
            return True
        except subprocess.CalledProcessError:
            print("paramiko安装失败,请手动安装:pip install paramiko")
            return False

def test_ssh_connection():
    """测试SSH连接"""
    try:
        import paramiko
        
        print("正在测试SSH连接...")
        ssh = paramiko.SSHClient()
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        
        # 尝试连接
        ssh.connect(SERVER_IP, username=USERNAME, password=PASSWORD, timeout=10)
        print("SSH连接测试成功!")
        
        # 执行简单命令测试
        stdin, stdout, stderr = ssh.exec_command('whoami')
        result = stdout.read().decode('utf-8').strip()
        print(f"当前用户: {result}")
        
        ssh.close()
        return True
        
    except Exception as e:
        print(f"SSH连接测试失败: {e}")
        print("请检查:")
        print("1. 服务器IP地址是否正确")
        print("2. 用户名和密码是否正确")
        print("3. 服务器SSH服务是否运行")
        print("4. 网络连接是否正常")
        return False

def kill_existing_autossh():
    """杀掉已有的针对目标服务器的autossh隧道进程"""
    if os.name == "nt":
        return  # Windows跳过

    # 查找包含目标IP的autossh进程
    try:
        result = subprocess.run(
            ["pgrep", "-af", f"autossh.*{SERVER_IP}"],
            capture_output=True, text=True
        )
        if result.returncode != 0 or not result.stdout.strip():
            print("未发现已有的autossh隧道进程")
            return

        # 提取进程ID并逐个杀掉
        pids = []
        for line in result.stdout.strip().splitlines():
            parts = line.split(None, 1)
            if parts:
                pids.append(parts[0])

        print(f"发现已有autossh隧道进程: {', '.join(pids)}")
        for pid in pids:
            subprocess.run(["kill", pid], capture_output=True)
        print("已杀掉旧的autossh隧道进程")
    except FileNotFoundError:
        print("未找到pgrep命令,跳过清理旧进程")

def setup_autossh_tunnel():
    """配置autossh反向隧道(Windows跳过)"""
    if os.name == "nt":
        print("检测到Windows环境,跳过autossh反向隧道配置")
        return True

    if not find_command("autossh"):
        print("未检测到autossh,跳过反向隧道配置")
        return False

    # 询问用户是否需要配置反向隧道
    print("\n=== 配置autossh反向隧道 ===")
    print(f"将把本地 80 端口反向转发到服务器 {SERVER_IP}:8081")
    try:
        answer = input("是否需要配置autossh反向隧道?[y/N]: ").strip().lower()
    except (EOFError, KeyboardInterrupt):
        print("\n已取消")
        return False

    if answer not in ("y", "yes"):
        print("已跳过autossh反向隧道配置")
        return True

    # 先清理已有的隧道进程,避免重复
    kill_existing_autossh()

    # 启动反向隧道
    # -f 后台运行 / -N 不执行远程命令 / -R 反向端口转发
    cmd = (
        f'autossh -fN -o "ServerAliveInterval 60" -o "ServerAliveCountMax 3" '
        f'-R 8081:localhost:80 {USERNAME}@{SERVER_IP} '
        f'> /var/log/autossh.log 2>&1'
    )
    print(f"执行命令: {cmd}")

    try:
        subprocess.run(cmd, shell=True, check=True)
    except subprocess.CalledProcessError as e:
        print(f"autossh隧道启动失败,退出码: {e.returncode}")
        print("可查看日志: /var/log/autossh.log")
        return False

    # 验证是否启动成功
    try:
        result = subprocess.run(
            ["pgrep", "-af", f"autossh.*{SERVER_IP}"],
            capture_output=True, text=True
        )
        if result.returncode == 0 and result.stdout.strip():
            print("autossh反向隧道启动成功!")
            print(f"  本地 80 端口 -> 服务器 {SERVER_IP}:8081")
            return True
        else:
            print("autossh隧道进程未检测到,可能启动失败")
            print("可查看日志: /var/log/autossh.log")
            return False
    except FileNotFoundError:
        print("无法验证(缺少pgrep),请手动检查进程")
        return True

def main():
    print("=== SSH免密登录快速配置 ===")
    print(f"目标服务器: {SERVER_IP}")
    print(f"用户名: {USERNAME}")
    print(f"密码: {PASSWORD[:8]}...")  # 只显示前8位
    print()

    # 第一步:检测并安装autossh(Windows跳过)
    if not install_autossh():
        print("\nautossh未就绪,请先安装后重新运行脚本")
        return

    # 检查并安装paramiko
    if not install_paramiko():
        return
    
    # 先测试SSH连接
    if not test_ssh_connection():
        print("\n请先解决SSH连接问题,然后重新运行脚本")
        return
    
    # 运行主配置脚本
    script_path = os.path.join(os.path.dirname(__file__), "setup_ssh.py")
    cmd = [
        sys.executable, 
        script_path,
        "--host", SERVER_IP,
        "--user", USERNAME, 
        "--password", PASSWORD
    ]
    
    try:
        subprocess.run(cmd, check=True)
    except subprocess.CalledProcessError as e:
        print(f"配置失败,退出码: {e.returncode}")
        return
    except KeyboardInterrupt:
        print("\n用户取消操作")
        return

    # 最后一步:配置autossh反向隧道(Windows跳过)
    setup_autossh_tunnel()

if __name__ == '__main__':
    main()

setup_ssh.py

代码语言:python
复制
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
SSH免密登录自动配置脚本
使用paramiko库实现自动化SSH连接和密钥配置
"""

import os
import sys
import subprocess
import argparse
from pathlib import Path

try:
    import paramiko
except ImportError:
    print("错误:需要安装paramiko库")
    print("请运行:pip install paramiko")
    sys.exit(1)

def generate_ssh_key(key_path):
    """生成SSH密钥对"""
    if os.path.exists(key_path):
        print(f"SSH密钥已存在: {key_path}")
        return True
    
    try:
        print("正在生成SSH密钥对...")
        cmd = [
            'ssh-keygen', 
            '-t', 'rsa', 
            '-b', '4096', 
            '-f', key_path, 
            '-N', ''
        ]
        result = subprocess.run(cmd, capture_output=True, text=True)
        
        if result.returncode == 0:
            print("SSH密钥对生成成功!")
            return True
        else:
            print(f"SSH密钥生成失败: {result.stderr}")
            return False
    except FileNotFoundError:
        print("错误:未找到ssh-keygen命令,请确保已安装OpenSSH")
        return False

def read_public_key(pub_key_path):
    """读取公钥内容"""
    try:
        with open(pub_key_path, 'r', encoding='utf-8') as f:
            return f.read().strip()
    except FileNotFoundError:
        print(f"错误:公钥文件不存在: {pub_key_path}")
        return None

def setup_ssh_key(hostname, username, password, public_key):
    """通过SSH连接设置公钥"""
    try:
        print(f"正在连接到 {hostname}...")
        
        # 创建SSH客户端
        ssh = paramiko.SSHClient()
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        
        # 连接到服务器
        ssh.connect(hostname, username=username, password=password, timeout=10)
        print("SSH连接成功!")
        
        # 执行命令设置SSH密钥
        commands = [
            'mkdir -p ~/.ssh',
            'chmod 700 ~/.ssh',
            f'echo "{public_key}" >> ~/.ssh/authorized_keys',
            'chmod 600 ~/.ssh/authorized_keys',
            'sort ~/.ssh/authorized_keys | uniq > ~/.ssh/authorized_keys.tmp',
            'mv ~/.ssh/authorized_keys.tmp ~/.ssh/authorized_keys'
        ]
        
        for cmd in commands:
            print(f"执行命令: {cmd}")
            stdin, stdout, stderr = ssh.exec_command(cmd)
            
            # 等待命令执行完成
            exit_status = stdout.channel.recv_exit_status()
            if exit_status != 0:
                error_msg = stderr.read().decode('utf-8')
                print(f"命令执行失败: {error_msg}")
                return False
        
        print("SSH密钥配置完成!")
        ssh.close()
        return True
        
    except paramiko.AuthenticationException:
        print("错误:认证失败,请检查用户名和密码")
        return False
    except paramiko.SSHException as e:
        print(f"SSH连接错误: {e}")
        return False
    except Exception as e:
        print(f"未知错误: {e}")
        return False

def test_passwordless_login(hostname, username, private_key_path):
    """测试免密登录"""
    try:
        print("正在测试免密登录...")
        
        ssh = paramiko.SSHClient()
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        
        # 使用私钥连接
        private_key = paramiko.RSAKey.from_private_key_file(private_key_path)
        ssh.connect(hostname, username=username, pkey=private_key, timeout=10)
        
        # 执行测试命令
        stdin, stdout, stderr = ssh.exec_command('echo "SSH免密登录测试成功!"')
        result = stdout.read().decode('utf-8').strip()
        print(result)
        
        ssh.close()
        return True
        
    except Exception as e:
        print(f"免密登录测试失败: {e}")
        return False

def main():
    parser = argparse.ArgumentParser(description='SSH免密登录自动配置脚本')
    parser.add_argument('--host', required=True, help='服务器IP地址')
    parser.add_argument('--user', required=True, help='用户名')
    parser.add_argument('--password', required=True, help='密码')
    parser.add_argument('--key-path', help='SSH密钥路径(可选)')
    
    args = parser.parse_args()
    
    # 设置SSH密钥路径
    if args.key_path:
        ssh_dir = Path(args.key_path).parent
        private_key_path = args.key_path
        public_key_path = f"{args.key_path}.pub"
    else:
        ssh_dir = Path.home() / '.ssh'
        private_key_path = ssh_dir / 'id_rsa'
        public_key_path = ssh_dir / 'id_rsa.pub'
    
    # 创建SSH目录
    ssh_dir.mkdir(mode=0o700, exist_ok=True)
    
    print("=== SSH免密登录自动配置脚本 ===")
    print(f"服务器: {args.host}")
    print(f"用户名: {args.user}")
    print(f"SSH密钥路径: {private_key_path}")
    print()
    
    # 步骤1:生成SSH密钥对
    if not generate_ssh_key(str(private_key_path)):
        sys.exit(1)
    
    # 步骤2:读取公钥
    public_key = read_public_key(str(public_key_path))
    if not public_key:
        sys.exit(1)
    
    print(f"公钥内容: {public_key[:50]}...")
    
    # 步骤3:配置SSH密钥
    if not setup_ssh_key(args.host, args.user, args.password, public_key):
        sys.exit(1)
    
    # 步骤4:测试免密登录
    if test_passwordless_login(args.host, args.user, str(private_key_path)):
        print()
        print("✅ SSH免密登录配置成功!")
        print(f"现在可以使用以下命令免密登录:")
        print(f"ssh {args.user}@{args.host}")
    else:
        print()
        print("❌ 免密登录测试失败,请检查配置")
        print("您可以尝试手动测试:")
        print(f"ssh {args.user}@{args.host}")

if __name__ == '__main__':
    main()

总结

这个项目让我意识到一个变化:「写代码」这件事本身正在加速贬值,但「知道要写什么」的能力反而更值钱了

AI 可以帮你写出几乎没有任何语法错误的代码,但它不会告诉你「SSH 反向隧道是解决你问题的最优解」。你仍然需要理解网络原理、了解各种方案的 trade-off、知道什么时候该放弃优雅方案直接用 bash 脚本硬上——这些才是工程师真正的护城河。

工具永远只是工具。能驾驭工具的,永远是人。


原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 用 codebuddy 写了个 SSH 内网穿透工具,一行命令搞定远程访问+SSH免密配置
    • 缘起:被内网穿透逼疯的下午
    • AI 充当了我的结对编程搭档
      • 第一轮对话:把想法翻译成指令
      • 第二轮对话:加个主入口,一键搞定
      • 第三轮对话:修 bug、加细节
    • 这个工具到底做了什么
    • 核心代码拆解
      • 1. 自动生成密钥 + 免密登录
      • 2. 反向隧道 + 自动保活
      • 3. 跨平台兼容的细节
    • AI 开发的真实体验
      • 好的一面
      • 还需要人兜底的地方
    • 后续可以玩的方向
    • 完整代码
    • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档