<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[我的小世界呀]]></title><description><![CDATA[哈喽~欢迎光临]]></description><link>https://bg.haomo.de</link><image><url>https://bg.haomo.de/innei.svg</url><title>我的小世界呀</title><link>https://bg.haomo.de</link></image><generator>Shiro (https://github.com/Innei/Shiro)</generator><lastBuildDate>Tue, 14 Apr 2026 02:38:22 GMT</lastBuildDate><atom:link href="https://bg.haomo.de/feed" rel="self" type="application/rss+xml"/><pubDate>Tue, 14 Apr 2026 02:38:22 GMT</pubDate><language><![CDATA[zh-CN]]></language><item><title><![CDATA[gemini-key]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://bg.haomo.de/posts/default/gemini-key">https://bg.haomo.de/posts/default/gemini-key</a></blockquote><p>AIzaSyD_gko3P392v6how2H7UpdeXQ0v2HLettc
AIzaSyDfJnq9ueYTx_vmIKv_KULrGevterpXguo
AIzaSyAeGFhuA-ZaYFMVBsQsQ0XzkAoPZYTLbhU</p><p style="text-align:right"><a href="https://bg.haomo.de/posts/default/gemini-key#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://bg.haomo.de/posts/default/gemini-key</link><guid isPermaLink="true">https://bg.haomo.de/posts/default/gemini-key</guid><dc:creator><![CDATA[haohao]]></dc:creator><pubDate>Thu, 20 Nov 2025 09:40:25 GMT</pubDate></item><item><title><![CDATA[配置SSH与日常同步工作流]]></title><description><![CDATA[<p>当前内容无法在 RSS 阅读器中正确渲染，请前往：<a href="https://bg.haomo.de/posts/default/git-1">https://bg.haomo.de/posts/default/git-1</a></p>]]></description><link>https://bg.haomo.de/posts/default/git-1</link><guid isPermaLink="true">https://bg.haomo.de/posts/default/git-1</guid><dc:creator><![CDATA[haohao]]></dc:creator><pubDate>Mon, 27 Oct 2025 01:35:22 GMT</pubDate></item><item><title><![CDATA[ros机械臂夹取]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://bg.haomo.de/posts/default/ros">https://bg.haomo.de/posts/default/ros</a></blockquote><div><h3 id=""><strong>第一步：把脚本保存到本地</strong></h3><pre class="language-bash lang-bash"><code class="language-bash lang-bash">cat &gt; ~/run_panda_pick_place.sh &lt;&lt;&#x27;BASH&#x27;
#!/usr/bin/env bash
set -euo pipefail

echo &quot;=== Panda MoveIt 抓取演示：一键运行脚本 ===&quot;

# 0) 基础环境
if [ -f /opt/ros/noetic/setup.bash ]; then
  source /opt/ros/noetic/setup.bash
else
  echo &quot;未发现 /opt/ros/noetic，需先安装 ROS Noetic。&quot;; exit 1
fi

ROS_DISTRO_CUR=$(rosversion -d 2&gt;/dev/null || echo &quot;unknown&quot;)
echo &quot;当前 ROS 发行版: ${ROS_DISTRO_CUR}&quot;
if [ &quot;${ROS_DISTRO_CUR}&quot; != &quot;noetic&quot; ]; then
  echo &quot;警告：脚本按 Noetic 准备，当前为 ${ROS_DISTRO_CUR}，继续尝试运行…&quot;
fi

# 1) 准备工作空间与依赖
WS=~/ws_arm
PKG=class_demo
mkdir -p &quot;$WS/src&quot;

# 确保必须的包存在（若缺失，尝试安装）
need_pkg() { rospack find &quot;$1&quot; &gt;/dev/null 2&gt;&amp;1 || return 0; return 1; }
if ! rospack find panda_moveit_config &gt;/dev/null 2&gt;&amp;1; then
  echo &quot;未找到 panda_moveit_config，准备安装依赖（需要 sudo 密码）…&quot;
  sudo apt update
  sudo apt install -y ros-noetic-moveit ros-noetic-moveit-resources-panda-moveit-config python3-catkin-tools
fi

# 2) 创建/更新演示包与脚本
if ! rospack find ${PKG} &gt;/dev/null 2&gt;&amp;1; then
  pushd &quot;$WS/src&quot; &gt;/dev/null
  if ! command -v catkin &gt;/dev/null 2&gt;&amp;1; then
    echo &quot;安装 catkin 工具（需要 sudo）…&quot;
    sudo apt update &amp;&amp; sudo apt install -y python3-catkin-tools
  fi
  if [ ! -f &quot;$WS/.catkin_tools/profiles/default/config.yaml&quot; ]; then
    catkin init
  fi
  if [ ! -d &quot;${PKG}&quot; ]; then
    catkin_create_pkg ${PKG} rospy geometry_msgs
    mkdir -p ${PKG}/scripts
  fi
  popd &gt;/dev/null
fi

# 写入演示脚本（已修正：组名、cartesian path 参数、无 f-string）
cat &gt; &quot;${WS}/src/${PKG}/scripts/pick_place_demo.py&quot; &lt;&lt;&#x27;PY&#x27;
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys, time, rospy
import moveit_commander, geometry_msgs.msg

def wait_for_state_update(scene, name, box_is_known=False, box_is_attached=False, timeout=4.0):
    start = time.time()
    while (time.time() - start) &lt; timeout and not rospy.is_shutdown():
        attached = len(scene.get_attached_objects([name])) &gt; 0
        known = name in scene.get_known_object_names()
        if attached == box_is_attached and (known == box_is_known or box_is_attached):
            return True
        rospy.sleep(0.1)
    return False

def open_gripper(hand):
    try:
        hand.set_named_target(&quot;open&quot;)
        hand.go(wait=True); return
    except Exception:
        pass
    joints = hand.get_active_joints()
    targets = {j: 0.04 for j in joints}
    hand.set_joint_value_target(targets); hand.go(wait=True)

def close_gripper(hand):
    try:
        hand.set_named_target(&quot;close&quot;)
        hand.go(wait=True); return
    except Exception:
        pass
    joints = hand.get_active_joints()
    targets = {j: 0.005 for j in joints}
    hand.set_joint_value_target(targets); hand.go(wait=True)

def main():
    rospy.init_node(&quot;class_pick_place&quot;)
    moveit_commander.roscpp_initialize(sys.argv)

    rospy.loginfo(&quot;初始化 MoveIt 接口…&quot;)
    robot = moveit_commander.RobotCommander()
    scene = moveit_commander.PlanningSceneInterface(synchronous=True)
    arm = moveit_commander.MoveGroupCommander(&quot;panda_arm&quot;)
    hand = moveit_commander.MoveGroupCommander(&quot;panda_hand&quot;)
    eef_link = arm.get_end_effector_link()

    arm.set_planner_id(&quot;RRTConnectkConfigDefault&quot;)
    arm.set_planning_time(5.0)
    arm.set_num_planning_attempts(10)
    arm.set_max_acceleration_scaling_factor(0.5)
    arm.set_max_velocity_scaling_factor(0.5)
    rospy.sleep(1.0)

    # 1) 添加方块
    rospy.loginfo(&quot;添加方块到场景…&quot;)
    box_name = &quot;demo_box&quot;
    box_pose = geometry_msgs.msg.PoseStamped()
    box_pose.header.frame_id = robot.get_planning_frame()
    box_pose.pose.position.x, box_pose.pose.position.y, box_pose.pose.position.z = 0.40, 0.00, 0.02
    box_pose.pose.orientation.w = 1.0
    scene.add_box(box_name, box_pose, size=(0.04, 0.04, 0.04))
    wait_for_state_update(scene, box_name, box_is_known=True)

    # 2) 张开夹爪
    rospy.loginfo(&quot;张开夹爪…&quot;)
    open_gripper(hand)

    # 3) 移动到方块上方
    rospy.loginfo(&quot;移动到方块上方（预抓取位）…&quot;)
    pre = geometry_msgs.msg.Pose()
    pre.position.x, pre.position.y, pre.position.z = 0.40, 0.00, 0.25
    pre.orientation.x, pre.orientation.y, pre.orientation.z, pre.orientation.w = 0.0, 1.0, 0.0, 0.0
    arm.set_pose_target(pre)
    arm.go(wait=True); arm.stop(); arm.clear_pose_targets()

    # 4) 直线下探到抓取高度
    rospy.loginfo(&quot;直线下探…&quot;)
    grasp = geometry_msgs.msg.Pose()
    grasp.position.x, grasp.position.y, grasp.position.z = 0.40, 0.00, 0.12
    grasp.orientation = pre.orientation
    start_pose = arm.get_current_pose().pose
    plan, fraction = arm.compute_cartesian_path([start_pose, grasp], 0.01, True)
    rospy.loginfo(&quot;下探轨迹覆盖率: {:.2f}&quot;.format(fraction))
    arm.execute(plan, wait=True)

    # 5) 闭合夹爪并附着方块
    rospy.loginfo(&quot;闭合夹爪并附着方块…&quot;)
    close_gripper(hand)
    touch_links = robot.get_link_names(group=&quot;panda_hand&quot;)
    scene.attach_box(eef_link, box_name, touch_links=touch_links)
    wait_for_state_update(scene, box_name, box_is_attached=True)

    # 6) 直线抬起
    rospy.loginfo(&quot;直线抬起…&quot;)
    start_pose = arm.get_current_pose().pose
    up_pose = geometry_msgs.msg.Pose()
    up_pose.position.x = start_pose.position.x
    up_pose.position.y = start_pose.position.y
    up_pose.position.z = start_pose.position.z + 0.12
    up_pose.orientation = start_pose.orientation
    plan, fraction = arm.compute_cartesian_path([start_pose, up_pose], 0.01, True)
    rospy.loginfo(&quot;抬起轨迹覆盖率: {:.2f}&quot;.format(fraction))
    arm.execute(plan, wait=True)

    # 7) 移动到放置位置上方
    rospy.loginfo(&quot;移动到放置位置上方…&quot;)
    place_top = geometry_msgs.msg.Pose()
    place_top.position.x, place_top.position.y, place_top.position.z = 0.30, -0.20, 0.25
    place_top.orientation = pre.orientation
    arm.set_pose_target(place_top)
    arm.go(wait=True); arm.stop(); arm.clear_pose_targets()

    # 8) 下放
    rospy.loginfo(&quot;下放…&quot;)
    place = geometry_msgs.msg.Pose()
    place.position.x, place.position.y, place.position.z = 0.30, -0.20, 0.12
    place.orientation = place_top.orientation
    start_pose = arm.get_current_pose().pose
    plan, fraction = arm.compute_cartesian_path([start_pose, place], 0.01, True)
    rospy.loginfo(&quot;下放轨迹覆盖率: {:.2f}&quot;.format(fraction))
    arm.execute(plan, wait=True)

    # 9) 张开夹爪，释放方块
    rospy.loginfo(&quot;张开夹爪，释放方块…&quot;)
    open_gripper(hand)
    scene.remove_attached_object(eef_link, name=box_name)
    wait_for_state_update(scene, box_name, box_is_known=True, box_is_attached=False)

    # 10) 移除世界中的方块（可选）
    rospy.loginfo(&quot;从场景移除方块（可选）…&quot;)
    scene.remove_world_object(box_name)

    # 11) 回到安全位
    rospy.loginfo(&quot;回到安全位…&quot;)
    safe = geometry_msgs.msg.Pose()
    safe.position.x, safe.position.y, safe.position.z = 0.35, 0.0, 0.35
    safe.orientation = pre.orientation
    arm.set_pose_target(safe)
    arm.go(wait=True); arm.stop(); arm.clear_pose_targets()

    rospy.loginfo(&quot;演示完成 ✅&quot;)

if __name__ == &quot;__main__&quot;:
    try:
        main()
    finally:
        try:
            moveit_commander.roscpp_shutdown()
        except Exception:
            pass
PY

chmod +x &quot;${WS}/src/${PKG}/scripts/pick_place_demo.py&quot;

# 3) 构建（保证可被 rospack/rosrun 找到）
pushd &quot;$WS&quot; &gt;/dev/null
if ! command -v catkin &gt;/dev/null 2&gt;&amp;1; then
  echo &quot;安装 catkin 工具（需要 sudo）…&quot;
  sudo apt update &amp;&amp; sudo apt install -y python3-catkin-tools
fi
catkin build
popd &gt;/dev/null

# 4) 启动 MoveIt + RViz（若未运行）
source &quot;${WS}/devel/setup.bash&quot;

if ! rosnode list 2&gt;/dev/null | grep -q &quot;^/move_group$&quot;; then
  echo &quot;启动 MoveIt demo（后台运行，日志在 ~/.ros/panda_demo.launch.log）…&quot;
  roslaunch panda_moveit_config demo.launch &gt; ~/.ros/panda_demo.launch.log 2&gt;&amp;1 &amp;
  echo -n &quot;等待 move_group 就绪&quot;
  for i in $(seq 1 60); do
    if rosnode list 2&gt;/dev/null | grep -q &quot;^/move_group$&quot;; then echo &quot; …OK&quot;; break; fi
    echo -n &quot;.&quot;; sleep 1
    if [ &quot;$i&quot; -eq 60 ]; then echo &quot; 超时未就绪，请查看 ~/.ros/panda_demo.launch.log&quot;; exit 1; fi
  done
else
  echo &quot;检测到 move_group 已在运行，跳过启动。&quot;
fi

# 5) 运行抓取演示
echo &quot;执行抓取脚本…&quot;
rosrun ${PKG} pick_place_demo.py || { echo &quot;脚本运行失败，查看 ~/.ros/log/latest/ 下日志&quot;; exit 1; }

echo
echo &quot;演示已完成。&quot;
echo &quot;若想让 RViz 画面更干净：&quot;
echo &quot;  - MotionPlanning → Trajectory：取消勾选 Show Trail、Loop Animation；&quot;
echo &quot;  - MotionPlanning → Planning Request：取消勾选 Query Start/Goal State；&quot;
echo &quot;  - 如仍有重复机器人，可关闭单独的 RobotModel 显示项。&quot;
echo &quot;你也可以手动 File → Save Config As… 保存一个清爽配置以便下次直接使用。&quot;
BASH</code></pre><hr/><h3 id=""><strong>第二步：给脚本执行权限</strong></h3><pre class="language-bash lang-bash"><code class="language-bash lang-bash">chmod +x ~/run_panda_pick_place.sh</code></pre><hr/><h3 id=""><strong>第三步：运行脚本</strong></h3><pre class="language-bash lang-bash"><code class="language-bash lang-bash">bash ~/run_panda_pick_place.sh</code></pre><hr/><h2 id="-">📘 说明与可选项</h2><p>脚本会自动完成以下操作：</p><ul><li>✅ 检查并安装必要依赖（<code>panda_moveit_config</code>、<code>catkin</code> 工具等）</li><li>✅ 创建或更新 <code>~/ws_arm/class_demo</code> 包，并写入修好的 <code>pick_place_demo.py</code></li><li>✅ 自动构建并启动 MoveIt + RViz</li><li>✅ 等待系统就绪后执行完整的抓取流程：
打开 → 下探 → 闭合 → 抬起 → 移动 → 放置 → 打开</li></ul><hr/><h2 id="-rviz-">🧩 RViz 视图优化</h2><p>脚本末尾已提示如何在 RViz 左侧 <strong>Displays</strong> 面板中清理画面：</p><ul><li><p>取消勾选：</p><ul><li><code>MotionPlanning → Trajectory → Show Trail</code></li><li><code>MotionPlanning → Trajectory → Loop Animation</code></li><li><code>MotionPlanning → Planning Request → Query Start/Goal State</code></li></ul></li><li><p>如仍有重复机器人，可关闭单独的 <code>RobotModel</code> 显示项。</p></li></ul><hr/><p>💡 <strong>可选：</strong></p><p>source ~/ws_arm/devel/setup.bash 
rosrun class_demo pick_place_demo.py</p></div><p style="text-align:right"><a href="https://bg.haomo.de/posts/default/ros#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://bg.haomo.de/posts/default/ros</link><guid isPermaLink="true">https://bg.haomo.de/posts/default/ros</guid><dc:creator><![CDATA[haohao]]></dc:creator><pubDate>Sun, 26 Oct 2025 07:11:27 GMT</pubDate></item><item><title><![CDATA[langgraph编排设计思路]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://bg.haomo.de/posts/default/langgraph">https://bg.haomo.de/posts/default/langgraph</a></blockquote><p>首先使用langchain配置链等作为节点使用，之后统一编排
langgraph的特点是全局共享状态信息，而langchain的链式设计思路是前后传递
langgraph的设计是基于状态的，</p><p style="text-align:right"><a href="https://bg.haomo.de/posts/default/langgraph#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://bg.haomo.de/posts/default/langgraph</link><guid isPermaLink="true">https://bg.haomo.de/posts/default/langgraph</guid><dc:creator><![CDATA[haohao]]></dc:creator><pubDate>Fri, 24 Oct 2025 07:26:30 GMT</pubDate></item><item><title><![CDATA[密钥]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://bg.haomo.de/posts/default/keysss">https://bg.haomo.de/posts/default/keysss</a></blockquote><span>uprock.com：477490 822613 149664 364377 743868 876196</span><p style="text-align:right"><a href="https://bg.haomo.de/posts/default/keysss#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://bg.haomo.de/posts/default/keysss</link><guid isPermaLink="true">https://bg.haomo.de/posts/default/keysss</guid><dc:creator><![CDATA[haohao]]></dc:creator><pubDate>Sun, 19 Oct 2025 06:50:56 GMT</pubDate></item><item><title><![CDATA[vps常用脚本记录]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://bg.haomo.de/posts/default/vps-1">https://bg.haomo.de/posts/default/vps-1</a></blockquote><div><h2 id="-1-nvmnodejs-">🚀 <strong>1. NVM（Node.js 版本管理器）一键安装与使用</strong></h2><h3 id="-">🧩 安装（任选其一）</h3><pre class="language-bash lang-bash"><code class="language-bash lang-bash"># 使用 curl
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash

# 或使用 wget
wget -qO- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash</code></pre><h3 id="-">⚙️ 激活环境</h3><p>重新打开终端，或执行以下命令（根据你的 Shell 类型选择）：</p><pre class="language-bash lang-bash"><code class="language-bash lang-bash"># Bash 用户
source ~/.bashrc

# Zsh 用户
source ~/.zshrc</code></pre><h3 id="-">💡 核心命令</h3><pre class="language-bash lang-bash"><code class="language-bash lang-bash"># 验证安装
nvm --version

# 安装最新 LTS 版本
nvm install --lts

# 安装指定版本
nvm install 18.17.0

# 查看所有已安装版本
nvm ls

# 切换版本
nvm use 18.17.0</code></pre><hr/><h2 id="-2-ssh-">🔐 <strong>2. SSH 免密登录（配置公钥）</strong></h2><p><strong>关键文件：</strong>服务器上的 <code>~/.ssh/authorized_keys</code></p><h3 id="-">✅ 方法一：一键配置（推荐）</h3><p>在<strong>本地电脑</strong>上执行，自动完成配置：</p><pre class="language-bash lang-bash"><code class="language-bash lang-bash">ssh-copy-id [用户名]@[服务器IP]</code></pre><h3 id="-">🛠 方法二：手动配置</h3><ol start="1"><li><p><strong>在本地电脑上</strong> 查看公钥：</p><pre class="language-bash lang-bash"><code class="language-bash lang-bash">cat ~/.ssh/id_rsa.pub</code></pre></li><li><p><strong>在服务器上</strong> 创建目录并写入公钥：</p><pre class="language-bash lang-bash"><code class="language-bash lang-bash"># 确保目录存在
mkdir -p ~/.ssh

# 将复制的公钥追加进去
echo &quot;在此处粘贴你的公钥内容&quot; &gt;&gt; ~/.ssh/authorized_keys

# 设置权限（必须执行）
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys</code></pre></li></ol><hr/><h2 id="ip">获取本地ip</h2><pre class="language-shell lang-shell"><code class="language-shell lang-shell">curl ipinfo.io/ip

#curl不可用

wget -qO- ifconfig.me</code></pre><h2 id="docker">还原docker命令</h2><pre class="language-shell lang-shell"><code class="language-shell lang-shell">docker inspect my_container | jq -r &#x27;
  .[0] as $c |
  &quot;docker run -d &quot; +
  ([$c.HostConfig.PortBindings | to_entries[] | &quot;--publish \(.key | split(&quot;/&quot;)[0]):\(.value[0].HostPort)&quot;] | join(&quot; &quot;)) + &quot; &quot; +
  ([$c.Mounts[] | &quot;--volume \(.Source):\(.Destination)&quot;] | join(&quot; &quot;)) + &quot; &quot; +
  &quot;--name &quot; + $c.Name[1:] + &quot; &quot; +
  $c.Config.Image + &quot; &quot; +
  ($c.Config.Cmd | join(&quot; &quot;))
&#x27;</code></pre><h2 id="dockeripv6">docker启动ipv6</h2><p>编辑/etc/docker/daemon.json文件，写入下面的内容：</p><pre class="language-yaml lang-yaml"><code class="language-yaml lang-yaml">{
  &quot;ipv6&quot;: true,
  &quot;fixed-cidr-v6&quot;: &quot;fc00:0000:0000:1::/64&quot;,
  &quot;experimental&quot;: true,
  &quot;ip6tables&quot;: true
}</code></pre><p>重启docker</p><pre class="language-shell lang-shell"><code class="language-shell lang-shell">systemctl restart docker</code></pre><h2 id="vps">vps加速</h2><pre class=""><code class="">wget -O tcpx.sh &quot;https://github.com/ylx2016/Linux-NetSpeed/raw/master/tcpx.sh&quot; &amp;&amp; chmod +x tcpx.sh &amp;&amp; ./tcpx.sh
bash &lt;(curl -fsSL https://tcp.hy2.sh/)</code></pre></div><p style="text-align:right"><a href="https://bg.haomo.de/posts/default/vps-1#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://bg.haomo.de/posts/default/vps-1</link><guid isPermaLink="true">https://bg.haomo.de/posts/default/vps-1</guid><dc:creator><![CDATA[haohao]]></dc:creator><pubDate>Sun, 12 Oct 2025 10:47:20 GMT</pubDate></item><item><title><![CDATA[用 Docker + redsocks 实现「一容器一出口 IP」：透明 SOCKS5 代理网关]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://bg.haomo.de/posts/default/redsocks-docker">https://bg.haomo.de/posts/default/redsocks-docker</a></blockquote><div><p>很多时候，给容器设置 <code>HTTP_PROXY/ALL_PROXY</code> 并不可靠：有的程序不读环境变量，有的仅支持 HTTP 代理不支持 SOCKS5，还有的会把一部分流量绕过代理。更稳的做法是<strong>系统级透明代理</strong>——把容器里的所有 TCP 流量统一“劫持”并转发到指定的 SOCKS5，由此实现<strong>一个容器一个出口 IP</strong>。</p><h2 id="">思路与架构</h2><p>我们做一个单独的“<strong>网关容器</strong>”，在这个容器里运行：</p><ul><li><strong>redsocks</strong>：把透明转发的 TCP 连接封装成 SOCKS5 请求；</li><li><strong>iptables (NAT/OUTPUT)</strong>：在该网络命名空间内重定向所有 TCP 出站流量到 redsocks 监听口。</li></ul><p>随后，让业务容器通过 <code>--network container:&lt;网关容器名&gt;</code> <strong>复用网关容器的网络栈</strong>。这样业务容器无感知地全量走代理。</p><pre class=""><code class="">业务容器 → (共享网络栈) → iptables(OUTPUT) → redsocks:12345 → SOCKS5 → 目标网站</code></pre><blockquote><p>优点：</p><ul><li>应用容器无需更改镜像或配置；</li><li>真正“全局”代理（TCP 层），不依赖应用是否支持代理环境变量；</li><li>多个网关容器 = 多个出口 IP；业务容器按需绑定对应网关。</li></ul></blockquote>
<hr/><h2 id="">环境准备</h2><ul><li>宿主机：Debian/Ubuntu（其他 Linux 发行版亦可）</li><li>已安装 Docker，能拉取镜像、启动容器</li><li>具备一个或多个可用 <strong>SOCKS5</strong> 代理（可匿名或带用户名/密码）</li></ul><hr/><h2 id="--">实操步骤（逐步 + 每步目的）</h2><blockquote><p>以网关容器名 <code>proxy1</code>、示例 SOCKS5 <code>60.188.121.252:20002</code> 为例。</p></blockquote>
<h3 id="1-">1. 创建“网关容器”</h3><pre class="language-bash lang-bash"><code class="language-bash lang-bash">docker run -d --name proxy1 --cap-add=NET_ADMIN --restart unless-stopped debian:bookworm-slim sleep infinity</code></pre><p><strong>目的</strong>：</p><ul><li><code>--cap-add=NET_ADMIN</code> 让容器内能设置 iptables；</li><li>独立网络命名空间，用作“透明代理网关”；</li><li><code>sleep infinity</code> 占位常驻；重启策略保证容器随宿主开机恢复。</li></ul><hr/><h3 id="2-redsocks--iptables--curl-">2. 安装工具（redsocks / iptables / curl 等）</h3><pre class="language-bash lang-bash"><code class="language-bash lang-bash">docker exec -it proxy1 bash -lc &#x27;apt update &amp;&amp; apt install -y redsocks iptables curl procps net-tools ca-certificates&#x27;</code></pre><p><strong>目的</strong>：</p><ul><li><code>redsocks</code> 做透明转发至 SOCKS5；</li><li><code>iptables</code> 写 NAT/OUTPUT 规则；</li><li><code>curl</code> 用于探测代理可用与验证出口 IP；</li><li><code>procps/net-tools</code> 用于排错（<code>ps/netstat</code>）；</li><li><code>ca-certificates</code> 避免 HTTPS 验证问题。</li></ul><hr/><h3 id="3--curl--socks5--redsocks">3. 先<strong>直接</strong>用 curl 测 SOCKS5 是否可用（绕过 redsocks）</h3><pre class="language-bash lang-bash"><code class="language-bash lang-bash">docker exec -it proxy1 bash -lc &#x27;curl -v --socks5 60.188.121.252:20002 https://ipinfo.io/ip&#x27;</code></pre><p><strong>目的</strong>：</p><ul><li>在启用透明代理前，确认 SOCKS5 本身能正常出外网；</li><li>如果这里就超时/失败，后续即使配置再完美也没用。</li></ul><hr/><h3 id="4--redsocksconf">4. 写入 <code>redsocks.conf</code></h3><pre class="language-bash lang-bash"><code class="language-bash lang-bash">docker exec -i proxy1 bash &lt;&lt;&#x27;EOF&#x27;
cat &gt; /etc/redsocks.conf &lt;&lt;&#x27;EOC&#x27;
base {
  log_debug = off;
  log_info  = on;
  log       = &quot;stderr&quot;;
  daemon    = on;
  redirector = iptables;
}
redsocks {
  local_ip   = 127.0.0.1;
  local_port = 12345;
  ip   = 60.188.121.252;   # SOCKS5 服务器 IP
  port = 20002;            # SOCKS5 端口
  type = socks5;
  # 若需认证，取消注释：
  # login = &quot;username&quot;;
  # password = &quot;password&quot;;
}
EOC
EOF</code></pre><p><strong>目的与要点</strong>：</p><ul><li>配置 redsocks 监听 <code>127.0.0.1:12345</code>；</li><li>指向你的 SOCKS5；</li><li><strong>配置文件必须是纯 ASCII</strong>，避免中文/奇怪字符导致解析失败。</li></ul><hr/><h3 id="5--redsocks">5. 启动 redsocks</h3><pre class="language-bash lang-bash"><code class="language-bash lang-bash">docker exec -d proxy1 bash -lc &#x27;redsocks -c /etc/redsocks.conf&#x27;</code></pre><p><strong>目的</strong>：</p><ul><li>让 redsocks 常驻监听；如需诊断可用 <code>-t</code> 前台模式查看日志。</li></ul><hr/><h3 id="6--iptables-">6. 下发 iptables 规则（透明转发）</h3><pre class="language-bash lang-bash"><code class="language-bash lang-bash">docker exec -it proxy1 bash -lc &#x27;
iptables -t nat -F
iptables -t nat -A OUTPUT -p tcp -d 127.0.0.1 -j RETURN
iptables -t nat -A OUTPUT -p tcp -d 60.188.121.252 -j RETURN
iptables -t nat -A OUTPUT -p tcp -j REDIRECT --to-ports 12345
&#x27;</code></pre><p><strong>目的与要点</strong>：</p><ul><li><strong>清空旧规则</strong>，避免重复叠加；</li><li>放行本机回环与代理服务器本身（否则会套娃死循环）；</li><li>其余 TCP 全部重定向到 <code>12345</code> → 由 redsocks 送往 SOCKS5。</li></ul><blockquote><p>说明：这是在 <strong>proxy1 的网络命名空间</strong> 内生效，不影响宿主机和其他容器。</p></blockquote>
<hr/><h3 id="7--ip">7. 验证出口 IP（确认透明代理生效）</h3><pre class="language-bash lang-bash"><code class="language-bash lang-bash">docker exec -it proxy1 bash -lc &#x27;curl -s https://ipinfo.io/ip&#x27;</code></pre><p><strong>期望</strong>：输出为 SOCKS5 的公网 IP（例如 <code>114.86.2.30</code>）。
<strong>意义</strong>：证明透明代理链路 <strong>容器 → iptables → redsocks → SOCKS5</strong> 正常。</p><hr/><h3 id="8--ip">8. 让业务容器复用网关网络（“一容器一出口 IP”）</h3><pre class="language-bash lang-bash"><code class="language-bash lang-bash">docker run -d \
  --restart unless-stopped \
  --network container:proxy1 \
  packetshare/packetshare \
  -accept-tos \
  -email=&lt;你的邮箱&gt; \
  -password=&lt;你的密码&gt;</code></pre><p><strong>目的</strong>：</p><ul><li>业务容器与 <code>proxy1</code> 共用网络栈，因此<strong>自动</strong>走上一步配置好的透明代理；</li><li>适用于任何镜像，不需要在应用层配置代理变量。</li></ul><hr/><h2 id="-socks5-">切换/替换 SOCKS5 的正确姿势</h2><p>当旧代理失效，需要切换新代理时：</p><pre class="language-bash lang-bash"><code class="language-bash lang-bash"># 1) 恢复直连（避免“断网时无法 apt/curl”）
docker exec -it proxy1 bash -lc &#x27;iptables -t nat -F&#x27;

# 2) 重启或重启 redsocks（如需）
docker restart proxy1   # 简洁做法，顺带清理僵尸进程

# 3) 先直接用 curl 测新 SOCKS5 是否通
docker exec -it proxy1 bash -lc &#x27;curl -v --socks5 &lt;NEW_IP:PORT&gt; https://ipinfo.io/ip&#x27;

# 4) 改 /etc/redsocks.conf，启动 redsocks
# 5) 重新下发三条 iptables 规则（放行 127.0.0.1 / 新代理 IP / 其余全转发）</code></pre><hr/><h2 id="--">常见问题 &amp; 排错建议</h2><ul><li><p><strong><code>redsocks</code> 报“unexpected char / unclosed section”</strong>
→ 配置文件含中文或 <code>${VAR}</code> 这类 shell 变量占位符（redsocks 不会展开），改为<strong>硬编码纯 ASCII</strong>。</p></li><li><p><strong><code>curl -s https://ipinfo.io/ip</code> 无输出</strong>
→ 常见于 SOCKS5 仅“能连上端口”但<strong>不能完整转发 HTTPS</strong>（公开代理很多这种）。先用
<code>curl -v --socks5 &lt;IP:PORT&gt; https://ipinfo.io/ip</code>
直连测试；查代理质量。</p></li><li><p><strong><code>[redsocks] &lt;defunct&gt;</code> 僵尸进程</strong>
→ 代理或配置异常导致短启即死，重启容器最干净：<code>docker restart proxy1</code>。
重新按「先清规则 → 测代理 → 写配置 → 启动 → 下规则」顺序来。</p></li><li><p><strong>DNS/UDP 泄漏</strong>
→ 透明代理本方案主要拦截 TCP。对 DNS 建议：应用侧启用 DoH/DoT 或使用 <code>redudp/dns2socks</code> 进一步处理。
（如果你的业务全是 HTTPS/TLS，通常已足够。）</p></li><li><p><strong>IPv6</strong>
→ 如需代理 IPv6，另配一组 <code>ip6tables -t nat ...</code> 规则（思路同 IPv4）。</p></li></ul><hr/><h2 id="-socks5">一键脚本（先测 SOCKS5，再自动建“网关容器”）</h2><p>把下面内容保存为 <code>create_proxy_container.sh</code>，赋予执行权限 <code>chmod +x create_proxy_container.sh</code>。
用法：<code>./create_proxy_container.sh &lt;容器名&gt; &lt;SOCKS_IP&gt; &lt;SOCKS_PORT&gt;</code></p><pre class="language-bash lang-bash"><code class="language-bash lang-bash">#!/bin/bash
# ===============================
# 一键创建 SOCKS5 透明代理“网关容器”
# 用法示例：
#   ./create_proxy_container.sh proxy1 60.188.121.252 20002
# ===============================

set -e

NAME=$1
SOCKS_IP=$2
SOCKS_PORT=$3
TARGET_URL=&quot;https://ipinfo.io/ip&quot;

if [[ -z &quot;$NAME&quot; || -z &quot;$SOCKS_IP&quot; || -z &quot;$SOCKS_PORT&quot; ]]; then
  echo &quot;用法: $0 &lt;容器名&gt; &lt;SOCKS_IP&gt; &lt;SOCKS_PORT&gt;&quot;
  exit 1
fi

# 1) 准备容器
if docker ps -a --format &#x27;{{.Names}}&#x27; | grep -q &quot;^${NAME}$&quot;; then
  echo &quot;容器 ${NAME} 已存在，直接使用。&quot;
else
  echo &quot;创建容器 ${NAME}...&quot;
  docker run -d --name &quot;$NAME&quot; --cap-add=NET_ADMIN --restart unless-stopped debian:bookworm-slim sleep infinity
fi

# 2) 安装依赖
echo &quot;安装 redsocks 和依赖...&quot;
docker exec &quot;$NAME&quot; bash -lc &#x27;apt update -qq &amp;&amp; apt install -y -qq redsocks iptables curl procps net-tools ca-certificates &gt;/dev/null&#x27;

# 3) 预检 SOCKS5 可用性（绕过透明代理）
echo &quot;测试 SOCKS5 代理是否可用 (${SOCKS_IP}:${SOCKS_PORT}) ...&quot;
if docker exec &quot;$NAME&quot; bash -lc &quot;curl -s --max-time 6 --socks5 ${SOCKS_IP}:${SOCKS_PORT} ${TARGET_URL}&quot; &gt;/dev/null 2&gt;&amp;1; then
  echo &quot;✅ SOCKS5 测试通过&quot;
else
  echo &quot;❌ SOCKS5 无法连接或超时，请检查地址/端口/可用性&quot;
  exit 1
fi

# 4) 写入 redsocks 配置
echo &quot;写入 /etc/redsocks.conf...&quot;
docker exec -i &quot;$NAME&quot; bash &lt;&lt;EOF
cat &gt; /etc/redsocks.conf &lt;&lt;EOC
base {
  log_debug = off;
  log_info  = on;
  log       = &quot;stderr&quot;;
  daemon    = on;
  redirector = iptables;
}
redsocks {
  local_ip   = 127.0.0.1;
  local_port = 12345;
  ip   = ${SOCKS_IP};
  port = ${SOCKS_PORT};
  type = socks5;
}
EOC
EOF

# 5) 启动 redsocks
echo &quot;启动 redsocks...&quot;
docker exec -d &quot;$NAME&quot; bash -lc &#x27;redsocks -c /etc/redsocks.conf&#x27;

# 6) 配置 iptables（先清空再下发）
echo &quot;配置 iptables 规则...&quot;
docker exec &quot;$NAME&quot; bash -lc &quot;
iptables -t nat -F
iptables -t nat -A OUTPUT -p tcp -d 127.0.0.1 -j RETURN
iptables -t nat -A OUTPUT -p tcp -d ${SOCKS_IP} -j RETURN
iptables -t nat -A OUTPUT -p tcp -j REDIRECT --to-ports 12345
&quot;

# 7) 验证出口 IP（透明代理是否生效）
echo &quot;验证透明代理出口 IP...&quot;
IP_OUT=$(docker exec &quot;$NAME&quot; bash -lc &quot;curl -s --max-time 8 ${TARGET_URL}&quot; || true)
if [[ -n &quot;$IP_OUT&quot; ]]; then
  echo &quot;✅ 代理工作正常，出口 IP: $IP_OUT&quot;
  echo &quot;👉 可通过 &#x27;--network container:${NAME}&#x27; 让其他容器共用此代理。&quot;
  exit 0
else
  echo &quot;⚠️ 未能获取出口 IP，请手动检查 redsocks/iptables/代理可用性。&quot;
  exit 2
fi</code></pre><p><strong>使用例子：</strong></p><pre class="language-bash lang-bash"><code class="language-bash lang-bash">./create_proxy_container.sh proxy1 60.188.121.252 20002

# 让业务容器共享代理网络
docker run -d --network container:proxy1 packetshare/packetshare \
  -accept-tos -email=&lt;你的邮箱&gt; -password=&lt;你的密码&gt;</code></pre><hr/></div><p style="text-align:right"><a href="https://bg.haomo.de/posts/default/redsocks-docker#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://bg.haomo.de/posts/default/redsocks-docker</link><guid isPermaLink="true">https://bg.haomo.de/posts/default/redsocks-docker</guid><dc:creator><![CDATA[haohao]]></dc:creator><pubDate>Wed, 08 Oct 2025 06:00:54 GMT</pubDate></item><item><title><![CDATA[为容器分配ipv6公网ip]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://bg.haomo.de/posts/default/docker-ipv6">https://bg.haomo.de/posts/default/docker-ipv6</a></blockquote><div><h1 id="">一、前置检查</h1><ol start="1"><li><strong>确认 IPv6 连通性（宿主机）</strong></li></ol><pre class="language-bash lang-bash"><code class="language-bash lang-bash">ip -6 addr show
ping -6 -c 3 google.com</code></pre><ol start="2"><li><strong>安装 Docker（若未安装）</strong></li></ol><pre class="language-bash lang-bash"><code class="language-bash lang-bash">curl -fsSL https://get.docker.com | sh</code></pre><p>全局启动ipv6
编辑 <strong>  /etc/docker/daemon.json </strong> ，加上以下内容。（如果没有这个文件直接创建。）</p><pre class="language-shell lang-shell"><code class="language-shell lang-shell">{
  &quot;ipv6&quot;: true,
  &quot;fixed-cidr-v6&quot;: &quot;fd00::/80&quot;,
  &quot;experimental&quot;: true,
  &quot;ip6tables&quot;: true
}</code></pre><p>重启docker engine</p><pre class="language-shell lang-shell"><code class="language-shell lang-shell">sudo systemctl restart docker</code></pre><p>测试</p><pre class="language-shell lang-shell"><code class="language-shell lang-shell">sudo docker run --rm -it busybox ping -6 -c4 ipv6-test.com</code></pre><ol start="3"><li><strong>安装并启用 nftables</strong></li></ol><pre class="language-bash lang-bash"><code class="language-bash lang-bash">apt-get update &amp;&amp; apt-get install -y nftables || yum install -y nftables
systemctl enable --now nftables
nft --version</code></pre><ol start="4"><li><strong>（建议）开启 IPv6 转发</strong></li></ol><blockquote><p>便于容器经宿主机转发出网（NAT66）。</p></blockquote><pre class="language-bash lang-bash"><code class="language-bash lang-bash">sysctl -w net.ipv6.conf.all.forwarding=1
echo &#x27;net.ipv6.conf.all.forwarding=1&#x27; &gt; /etc/sysctl.d/99-ipv6-forward.conf
sysctl --system</code></pre><hr/><h1 id="-docker-ipv6-">二、准备一个<strong>不冲突</strong>的 Docker IPv6 网络</h1><blockquote><p>你之前的 <code>docker0</code> 里有 <code>fd00:dead:beef::/48</code>，所以<strong>新网络不要使用该段下的前缀</strong>，以免报 “Pool overlaps”。</p></blockquote>
<ol start="1"><li><strong>选择一个 ULA 子网</strong>（示例用 <code>fd00:1:1:1::/64</code>）</li></ol><pre class="language-bash lang-bash"><code class="language-bash lang-bash">docker network create \
  --ipv6 \
  --subnet fd00:1:1:1::/64 \
  psnet</code></pre><ul><li>成功后可查看：</li></ul><pre class="language-bash lang-bash"><code class="language-bash lang-bash">docker network inspect psnet | grep -A3 IPv6</code></pre><blockquote><p>如果报 <code>overlaps</code>，就换个段，比如：<code>fd00:2:2:2::/64</code>、<code>fd10:abcd:1:1::/64</code> 等，总之<strong>与现有网络不重叠</strong>即可。</p></blockquote>
<hr/><h1 id="-ipv6">三、为容器分配<strong>内部 IPv6</strong>（落在上面的子网）</h1><blockquote><p>在<strong>用户自定义网络</strong>里才可以用 <code>--ip6</code> 指定静态 IPv6。
下面示例给容器分配 <code>fd00:1:1:1::101</code>（在 <code>fd00:1:1:1::/64</code> 内）。</p></blockquote><pre class="language-bash lang-bash"><code class="language-bash lang-bash">docker run -d \
  --restart unless-stopped \
  --network psnet \
  --ip6 fd00:1:1:1::101 \
  --name pxxxxe \
  pxxxe/pxxxe \</code></pre><p>检查分配结果：</p><pre class="language-bash lang-bash"><code class="language-bash lang-bash">docker inspect -f &#x27;{{.NetworkSettings.Networks.psnet.GlobalIPv6Address}}&#x27; packetshare</code></pre><p>应显示：<code>fd00:1:1:1::101</code></p><hr/><h1 id="-ipv6--ipv6nat66">四、把容器的内部 IPv6 映射到<strong>指定公网 IPv6</strong>（NAT66）</h1><blockquote><p>你的宿主机上有多个公网 IPv6（例如 <code>2a0e:97c0:3f0:1::1d63</code>），我们要让<strong>这个容器的出口永远使用它</strong>。
使用 <strong>nftables NAT（IPv6）</strong> 在 <strong>postrouting</strong> 做 SNAT。</p></blockquote>
<ol start="1"><li><strong>创建表与链（首次）</strong></li></ol><pre class="language-bash lang-bash"><code class="language-bash lang-bash">nft add table ip6 docker_snat
nft add chain ip6 docker_snat postrouting &#x27;{ type nat hook postrouting priority srcnat; }&#x27;</code></pre><ol start="2"><li><strong>添加 SNAT 规则</strong>
将容器内部 IPv6 <code>fd00:1:1:1::101</code> 映射为公网 <code>2a0e:97c0:3f0:1::1d63</code>：</li></ol><pre class="language-bash lang-bash"><code class="language-bash lang-bash">nft add rule ip6 docker_snat postrouting ip6 saddr fd00:1:1:1::101 snat to 2a0e:97c0:3f0:1::1d63</code></pre><ol start="3"><li><strong>查看当前规则</strong></li></ol><pre class="language-bash lang-bash"><code class="language-bash lang-bash">nft list table ip6 docker_snat
# 或带 handle 编号：
nft -a list table ip6 docker_snat</code></pre><hr/><h1 id="">五、验证</h1><ol start="1"><li><strong>容器内测试出网地址</strong></li></ol><pre class="language-bash lang-bash"><code class="language-bash lang-bash">docker exec -it packetshare curl -6 ifconfig.co
# 也可：docker exec -it packetshare curl -6 ip.sb</code></pre><p>返回应为：<code>2a0e:97c0:3f0:1::1d63</code>（你在 SNAT 指定的那个）。</p><ol start="2"><li><strong>连通性测试</strong></li></ol><pre class="language-bash lang-bash"><code class="language-bash lang-bash">docker exec -it packetshare ping -6 -c 3 google.com</code></pre><hr/><h1 id="">六、多个容器、多公网地址</h1><ul><li>再起一个容器（内部 IPv6 换一个）：</li></ul><pre class="language-bash lang-bash"><code class="language-bash lang-bash">docker run -d \
  --restart unless-stopped \
  --network psnet \
  --ip6 fd00:1:1:1::102 \
  --name packetshare2 \
  packetshare/packetshare \
    -accept-tos \
    -email=aabbccddwo@outlook.com \
    -password=Abcdefg6</code></pre><ul><li>给它 SNAT 到另一个公网 IPv6（如 <code>2a0e:97c0:3f0:1::1d62</code>）：</li></ul><pre class="language-bash lang-bash"><code class="language-bash lang-bash">nft add rule ip6 docker_snat postrouting ip6 saddr fd00:1:1:1::102 snat to 2a0e:97c0:3f0:1::1d62</code></pre><ul><li>验证：</li></ul><pre class="language-bash lang-bash"><code class="language-bash lang-bash">docker exec -it packetshare2 curl -6 ifconfig.co</code></pre><p>应看到 <code>2a0e:97c0:3f0:1::1d62</code>。</p><hr/><h1 id="">七、常用维护操作</h1><h3 id="1--snat-">1) 查看/删除 SNAT 规则</h3><ul><li>查看含 handle 编号：</li></ul><pre class="language-bash lang-bash"><code class="language-bash lang-bash">nft -a list table ip6 docker_snat</code></pre><ul><li>按 handle 删除：</li></ul><pre class="language-bash lang-bash"><code class="language-bash lang-bash">nft delete rule ip6 docker_snat postrouting handle &lt;编号&gt;</code></pre><ul><li>清空整链（谨慎）：</li></ul><pre class="language-bash lang-bash"><code class="language-bash lang-bash">nft flush chain ip6 docker_snat postrouting</code></pre><ul><li>删整张表（谨慎）：</li></ul><pre class="language-bash lang-bash"><code class="language-bash lang-bash">nft delete table ip6 docker_snat</code></pre><h3 id="2--nft-">2) 持久化 nft 规则（防重启丢失）</h3><ul><li>保存当前规则：</li></ul><pre class="language-bash lang-bash"><code class="language-bash lang-bash">nft list ruleset &gt; /etc/nftables.conf</code></pre><ul><li>确保开机加载：</li></ul><pre class="language-bash lang-bash"><code class="language-bash lang-bash">systemctl enable --now nftables</code></pre><blockquote><p>若你的发行版使用 <code>nftables-persistent</code>，也可以：</p><pre class="language-bash lang-bash"><code class="language-bash lang-bash">apt-get install -y nftables-persistent
netfilter-persistent save</code></pre></blockquote>
<h3 id="3-">3) 容器与网络排查</h3><ul><li>查看网络详情：</li></ul><pre class="language-bash lang-bash"><code class="language-bash lang-bash">docker network inspect psnet</code></pre><ul><li>查看容器 IPv6：</li></ul><pre class="language-bash lang-bash"><code class="language-bash lang-bash">docker inspect -f &#x27;{{.NetworkSettings.Networks.psnet.GlobalIPv6Address}}&#x27; &lt;容器名&gt;</code></pre><hr/><h1 id="">八、常见报错与快速修复</h1><ul><li><p><strong><code>invalid pool request: Pool overlaps...</code></strong>
说明你新建的子网与现有 Docker 网络（比如 <code>docker0</code>）重叠。<strong>换一个 ULA 前缀</strong>，如 <code>fd00:1:1:1::/64</code>、<code>fd10:abcd:1:1::/64</code>。</p></li><li><p><strong><code>user specified IP address is supported on user defined networks only</code></strong>
你在默认 <code>bridge</code> 网络里指定了 <code>--ip6</code>。请<strong>先用 <code>docker network create</code> 创建自定义网络</strong>，再在该网络里指定 <code>--ip6</code>。</p></li><li><p><strong><code>invalid subinterface vlan name eth0, example formatting is eth0.10</code></strong>（macvlan/ipvlan）
说明云环境不允许直接用二层（或需要显式子接口），多数 VPS 建议<strong>放弃 macvlan/ipvlan</strong>，改用<strong>本指南的 NAT66 方案</strong>。</p></li><li><p><strong>容器能解析但出不了网</strong>
先检查宿主 <code>ping -6</code> 是否通，再确认：</p></li></ul><ol start="1"><li><code>sysctl net.ipv6.conf.all.forwarding=1</code></li><li><code>nft</code> 的 SNAT 规则是否匹配了容器 <code>ip6 saddr</code></li><li>云厂商安全组/防火墙是否放行所需端口/协议（ICMPv6、TCP/UDP）</li></ol><hr/><h1 id="">九、（可选）一体化管理脚本</h1><blockquote><p>下面的脚本提供：<code>add</code> / <code>del</code> / <code>list</code></p><ul><li><code>add &lt;name&gt; &lt;public_ipv6&gt; &lt;email&gt; &lt;password&gt;</code>：创建网络（如无）→ 启容器 → 添加 SNAT</li><li><code>del &lt;name&gt;</code>：删除容器 + 清理对应 SNAT</li><li><code>list</code>：查看容器与 SNAT</li></ul></blockquote>
<p>保存为 <code>ps_manager.sh</code>：</p><pre class="language-bash lang-bash"><code class="language-bash lang-bash">#!/bin/bash
set -e

NETNAME=&quot;psnet&quot;
SUBNET=&quot;fd00:1:1:1::/64&quot;    # 如与现有冲突可改
TABLE=&quot;docker_snat&quot;
CHAIN=&quot;postrouting&quot;

init_env() {
  if ! docker network inspect $NETNAME &gt;/dev/null 2&gt;&amp;1; then
    echo &quot;[+] 创建 Docker 网络 $NETNAME ($SUBNET)&quot;
    docker network create --ipv6 --subnet $SUBNET $NETNAME
  fi
  if ! nft list table ip6 $TABLE &gt;/dev/null 2&gt;&amp;1; then
    echo &quot;[+] 创建 nftables 表 $TABLE&quot;
    nft add table ip6 $TABLE
    nft add chain ip6 $TABLE $CHAIN &#x27;{ type nat hook postrouting priority srcnat; }&#x27;
  fi
}

gen_internal_ip() { # 根据容器名生成稳定的末尾
  local NAME=$1
  local SUFFIX=$(echo -n $NAME | md5sum | cut -c1-4)
  echo &quot;fd00:1:1:1::${SUFFIX}&quot;
}

add_container() {
  local NAME=$1 PUBIP=$2 EMAIL=$3 PASSWORD=$4
  if [ -z &quot;$NAME&quot; ] || [ -z &quot;$PUBIP&quot; ] || [ -z &quot;$EMAIL&quot; ] || [ -z &quot;$PASSWORD&quot; ]; then
    echo &quot;用法: $0 add &lt;name&gt; &lt;public_ipv6&gt; &lt;email&gt; &lt;password&gt;&quot;; exit 1
  fi
  init_env
  local INIP=$(gen_internal_ip &quot;$NAME&quot;)
  echo &quot;[+] 启动容器 $NAME (内部=$INIP, 公网=$PUBIP)&quot;
  docker rm -f &quot;$NAME&quot; &gt;/dev/null 2&gt;&amp;1 || true
  docker run -d --restart unless-stopped --network &quot;$NETNAME&quot; --ip6 &quot;$INIP&quot; --name &quot;$NAME&quot; \
    packetshare/packetshare -accept-tos -email=&quot;$EMAIL&quot; -password=&quot;$PASSWORD&quot;
  echo &quot;[+] 添加 SNAT: $INIP -&gt; $PUBIP&quot;
  nft add rule ip6 $TABLE $CHAIN ip6 saddr $INIP snat to $PUBIP
  sleep 2
  docker exec -it &quot;$NAME&quot; curl -6 ifconfig.co || true
}

del_container() {
  local NAME=$1
  if [ -z &quot;$NAME&quot; ]; then echo &quot;用法: $0 del &lt;name&gt;&quot;; exit 1; fi
  local INIP=$(gen_internal_ip &quot;$NAME&quot;)
  echo &quot;[+] 删除容器 $NAME&quot;
  docker rm -f &quot;$NAME&quot; &gt;/dev/null 2&gt;&amp;1 || true
  echo &quot;[+] 清理 SNAT（匹配 $INIP）&quot;
  local HANDLES=$(nft -a list table ip6 $TABLE 2&gt;/dev/null | awk &quot;/$INIP/ {print \$NF}&quot;)
  for h in $HANDLES; do
    nft delete rule ip6 $TABLE $CHAIN handle $h &amp;&amp; echo &quot;  删除规则 handle $h&quot;
  done
}

list_all() {
  echo &quot;=== 容器 ===&quot;
  docker ps --format &quot;table {{.Names}}\t{{.Status}}\t{{.Networks}}&quot;
  echo; echo &quot;=== SNAT 规则 ===&quot;
  nft list table ip6 $TABLE || echo &quot;暂无 SNAT 规则&quot;
}

case &quot;$1&quot; in
  add) shift; add_container &quot;$@&quot;;;
  del) shift; del_container &quot;$@&quot;;;
  list) list_all;;
  *) echo &quot;用法: $0 {add|del|list}&quot;; exit 1;;
esac</code></pre><p>使用：</p><pre class="language-bash lang-bash"><code class="language-bash lang-bash">chmod +x ps_manager.sh
./ps_manager.sh add ps1 2a0e:97c0:3f0:1::1d63 aabbccddwo@outlook.com Abcdefg6
./ps_manager.sh list
./ps_manager.sh del ps1</code></pre><hr/><p><strong>完成！</strong>
照这份指南操作，你就能让每个容器用<strong>固定的内部 IPv6</strong>并<strong>映射到指定的公网 IPv6</strong>对外通信。</p></div><p style="text-align:right"><a href="https://bg.haomo.de/posts/default/docker-ipv6#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://bg.haomo.de/posts/default/docker-ipv6</link><guid isPermaLink="true">https://bg.haomo.de/posts/default/docker-ipv6</guid><dc:creator><![CDATA[haohao]]></dc:creator><pubDate>Tue, 30 Sep 2025 05:20:46 GMT</pubDate></item><item><title><![CDATA[我的 LangChain 学习之旅：从提示词工程到智能体构建]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://bg.haomo.de/posts/default/langchain-2">https://bg.haomo.de/posts/default/langchain-2</a></blockquote><div><h2 id="">引言</h2><p>今天是我深入 LangChain 生态系统的一天。虽然感觉学习过程中遇到了不少挑战和错误，但回过头来整理，发现收获远比想象的多。从基础的提示词重写到复杂的 Agent 构建，每一个概念都在为构建更智能的 AI 应用打下基础。</p><h2 id="">今天的主要学习内容</h2><h3 id="1--messagesplaceholder-">1. 深入理解 MessagesPlaceholder 的工作机制</h3><p><strong>学习重点</strong>：<code>MessagesPlaceholder</code> 不是普通的字符串占位符，而是专门用于在聊天提示模板中插入消息对象列表的特殊组件。</p><p><strong>核心发现</strong>：</p><ul><li>它保持了对话历史的结构完整性，让 LLM 能够理解真正的对话轮次</li><li>与普通的 <code>{chat_history}</code> 相比，它不会将消息列表转换为混乱的字符串</li><li>在多轮对话和 RAG 应用中至关重要</li></ul><pre class="language-python lang-python"><code class="language-python lang-python"># 关键代码示例
rewrite_prompt = ChatPromptTemplate.from_messages([
    (&quot;system&quot;, &quot;根据对话历史改写用户问题...&quot;),
    MessagesPlaceholder(variable_name=&quot;chat_history&quot;), 
    (&quot;human&quot;, &quot;{question}&quot;)
])</code></pre><h3 id="2--runnablepassthroughassign-">2. 掌握 RunnablePassthrough.assign 的数据流增强技术</h3><p><strong>学习重点</strong>：这是 LCEL 中的高级组件，用于在数据流中添加新信息而不丢失原有数据。</p><p><strong>实际应用场景</strong>：</p><ul><li>在 RAG 中同时保留用户问题和检索到的上下文</li><li>避免数据在链式传递中的丢失问题</li><li>实现数据流的优雅扩充</li></ul><pre class="language-python lang-python"><code class="language-python lang-python"># 实践案例
rag_chain = RunnablePassthrough.assign(
    context=(lambda x: x[&#x27;question&#x27;]) | retriever | format_docs
) | rag_prompt | model | StrOutputParser()</code></pre><h3 id="3--rag-">3. 完成 RAG 提示词重写功能的实现</h3><p><strong>项目成果</strong>：成功构建了一个能够根据对话历史重写用户问题的 RAG 系统，提高了检索的准确性和对话的连贯性。</p><p><strong>技术亮点</strong>：</p><ul><li>使用对话历史优化检索查询</li><li>实现了多轮对话的上下文理解</li><li>提升了 RAG 系统的用户体验</li></ul><h3 id="4--langchain-agent-">4. 深入理解 LangChain Agent 的核心概念</h3><p><strong>重要认知转变</strong>：Agent 与 Chain 的根本区别在于<strong>决策的动态性</strong>：</p><ul><li><strong>Chain</strong>：预定义的固定执行路径（像菜谱）</li><li><strong>Agent</strong>：基于 LLM 推理的动态决策系统（像有思考能力的大厨）</li></ul><p><strong>Agent 的三大核心组件</strong>：</p><ol start="1"><li><strong>Tools</strong>：Agent 可使用的能力集合</li><li><strong>LLM</strong>：作为推理引擎的大脑</li><li><strong>Agent Executor</strong>：驱动 ReAct 循环的执行器</li></ol><h3 id="5-">5. 探索多种搜索工具集成方案</h3><p><strong>实践经验</strong>：</p><ul><li>发现了 DuckDuckGo 搜索的局限性</li><li>学习了 Tavily 搜索的优势：&quot;We will be using Tavily (a search engine) as a tool&quot;</li><li>成功实现了 Wikipedia 专用搜索 Agent</li><li>体验了不同工具在实际应用中的表现差异</li></ul><h3 id="6--langchain-hub-">6. 理解 LangChain Hub 的价值和作用</h3><p><strong>重要发现</strong>：LangChain Hub 是一个&quot;提示词模板仓库&quot;，让开发者可以：</p><ul><li>使用经过验证的高质量 Prompt 模板</li><li>避免从零开始设计复杂的 Agent Prompt</li><li>利用社区的集体智慧和最佳实践</li></ul><pre class="language-python lang-python"><code class="language-python lang-python"># 使用 Hub 中的经典 ReAct 模板
prompt = hub.pull(&quot;hwchase17/react&quot;)</code></pre><h3 id="7--langchain--api-">7. 学会处理 LangChain 版本变更和 API 迁移</h3><p><strong>实战经验</strong>：</p><ul><li>遇到了 <code>ImportError</code> 和 <code>LangChainDeprecationWarning</code> 等版本兼容问题</li><li>学会了如何阅读错误信息和迁移指南</li><li>理解了 LangChain v0.2 的模块化架构变化</li><li>掌握了从旧版本 API 迁移到新版本的方法</li></ul><h2 id="">今天遇到的挑战与解决方案</h2><h3 id="-1">挑战 1：工具导入路径变更</h3><p><strong>问题</strong>：<code>from langchain.agents import load_tools</code> 已被弃用
<strong>解决</strong>：迁移到 <code>from langchain_community.agent_toolkits import load_tools</code></p><h3 id="-2tavily-">挑战 2：Tavily 集成问题</h3><p><strong>问题</strong>：<code>TavilySearchResults</code> 的导入和使用方式发生变化
<strong>解决</strong>：使用传统但稳定的 Wikipedia 搜索工具作为替代方案</p><h3 id="-3">挑战 3：版本兼容性</h3><p><strong>问题</strong>：不同版本间的 API 差异
<strong>解决</strong>：学会查阅最新文档，使用稳定的替代方案</p><h2 id="">核心技术收获</h2><h3 id="1-react-">1. ReAct 框架的深度理解</h3><p>掌握了 ReAct（Reasoning and Acting）的核心思想：&quot;The agent is using a reasoning engine to determine which actions to take to get a result&quot;</p><p><strong>ReAct 循环</strong>：</p><ul><li><strong>Thought</strong>（思考）：LLM 分析问题并规划下一步</li><li><strong>Action</strong>（行动）：决定调用哪个工具</li><li><strong>Action Input</strong>（行动输入）：确定工具的参数</li><li><strong>Observation</strong>（观察）：接收工具执行结果</li><li><strong>循环直至得到最终答案</strong></li></ul><h3 id="2-">2. 工具集成的最佳实践</h3><p>学会了如何：</p><ul><li>选择合适的搜索工具（Wikipedia vs Tavily vs DuckDuckGo）</li><li>自定义工具的创建和集成</li><li>处理工具调用中的错误和异常</li></ul><h3 id="3-">3. 项目结构和错误处理</h3><ul><li>实现了健壮的错误处理机制</li><li>学会了使用 <code>verbose=True</code> 调试 Agent 思考过程</li><li>掌握了模块化的代码组织方式</li></ul><h2 id="">实际应用价值</h2><p>今天的学习不仅仅是理论知识，更是实践技能的积累：</p><ol start="1"><li><strong>RAG 系统优化</strong>：通过提示词重写提升了检索质量</li><li><strong>智能对话能力</strong>：实现了具备工具调用能力的对话系统</li><li><strong>知识检索增强</strong>：构建了专门的 Wikipedia 知识助手</li><li><strong>错误处理经验</strong>：在解决版本兼容问题中积累了宝贵经验</li></ol><h2 id="">未来学习方向</h2><p>基于今天的学习基础，接下来的重点将是：</p><ol start="1"><li><strong>LangGraph 深入学习</strong>：官方推荐：&quot;For more information on Agents, please check out the LangGraph documentation&quot;</li><li><strong>多 Agent 系统构建</strong>：探索 &quot;multi-agent workflows&quot; 的应用场景</li><li><strong>生产环境部署</strong>：学习 Agent 的性能优化和监控</li><li><strong>自定义工具开发</strong>：为特定业务场景开发专用工具</li></ol><h2 id="">总结与反思</h2><p>虽然今天的学习过程中遇到了不少技术问题，但这些&quot;坑&quot;反而让我对 LangChain 的理解更加深入。&quot;理论只能带你走这么远。你可以整天阅读文档，但真正的魔法——以及真正的头疼——在你真正构建一个时才会出现&quot;</p><p>每一个错误都是学习的机会，每一次调试都是对框架更深层理解的积累。从提示词重写到 Agent 构建，从工具集成到版本迁移，这一天的学习为我在 AI 应用开发道路上奠定了坚实的基础。</p><p><strong>最重要的收获</strong>：不是完美地运行了某段代码，而是培养了面对技术挑战时的解决能力，以及对 LangChain 生态系统的整体把握。这种能力，比任何单一的技术点都更加宝贵。</p><hr/><p><em>学习永无止境，每一天的积累都在为更大的突破做准备。今天的&quot;挫折&quot;，都是明天成功的垫脚石。</em></p></div><p style="text-align:right"><a href="https://bg.haomo.de/posts/default/langchain-2#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://bg.haomo.de/posts/default/langchain-2</link><guid isPermaLink="true">https://bg.haomo.de/posts/default/langchain-2</guid><dc:creator><![CDATA[haohao]]></dc:creator><pubDate>Tue, 09 Sep 2025 11:09:10 GMT</pubDate></item><item><title><![CDATA[我的 LangChain 学习之旅：从核心概念到动手实践]]></title><description><![CDATA[<div><blockquote>该渲染由 Shiro API 生成，可能存在排版问题，最佳体验请前往：<a href="https://bg.haomo.de/posts/default/langchain_1">https://bg.haomo.de/posts/default/langchain_1</a></blockquote><div><h2 id="">前言</h2><p>我最近正在学习 LangChain，这是一个强大的大型语言模型（LLM）应用开发框架。为了检验自己的学习水平并明确下一步的方向，我与 AI 进行了一次深入的问答式学习。这篇笔记记录了从评估我的初始认知，到深入理解核心组件，再到解决具体疑惑的全过程。希望我的学习路径和思考，也能为同样走在这条路上的你提供一些参考。</p><h2 id="">第一站：知识水平评估——我最初的理解</h2><p>在开始时，我对 LangChain 的理解停留在比较宏观的层面：</p><ul><li><strong>核心价值</strong>：我认为 LangChain 的价值在于为 LLM 赋能，拓展其能力边界，解决大模型应用开发缓慢的问题。</li><li><strong>基础组件</strong>：
<ul><li><code>Models</code>: 对接 OpenAI 等大模型 API。</li><li><code>Prompts</code>: 提示词模板。</li><li><code>Output Parsers</code>: 用于结构化输出。</li></ul></li><li><strong>RAG 流程</strong>：我对检索增强生成（RAG）的流程非常熟悉，即 <strong>加载 -&gt; 分割 -&gt; 向量化 -&gt; 存储 -&gt; 检索</strong>。</li><li><strong>Chains vs. Agents</strong>：我能区分 <code>Chains</code>（确定性流程）和 <code>Agents</code>（动态决策）的区别。</li></ul><p>AI 指出了我的知识盲区：<code>Memory</code>（记忆）、<code>LCEL</code>（LangChain 表达式语言）以及 <code>Debugging</code>（调试）。这便是我此行学习的重点。</p><h2 id="">第二站：核心组件深潜——那些我新学到的关键概念</h2><p>这是本次学习的核心，我重点掌握了以下几个以前模糊不清或完全不知道的概念。</p><h3 id="1-lcel-langchain-expression-language---">1. LCEL (LangChain Expression Language) - 现代化的构建方式</h3><p>这是我此行最大的收获之一。LCEL 是 LangChain 官方推荐的、用于组合组件的声明式方法。</p><ul><li><strong>核心语法</strong>：使用管道符 <code>|</code> 将组件像流水线一样连接起来。</li><li><strong>代码示例</strong>：一个简单的“提示+模型+解析器”链。</li></ul><pre class="language-python lang-python"><code class="language-python lang-python">    from langchain_openai import ChatOpenAI
    from langchain_core.prompts import ChatPromptTemplate
    from langchain_core.output_parsers import StrOutputParser
  
    prompt = ChatPromptTemplate.from_template(&quot;请告诉我关于 {topic} 的一个笑话。&quot;)
    model = ChatOpenAI()
    output_parser = StrOutputParser()
  
    # 这就是 LCEL 的魔力！
    chain = prompt | model | output_parser
  
    chain.invoke({&quot;topic&quot;: &quot;程序员&quot;})</code></pre><ul><li><strong>优势</strong>：相比旧版的 <code>Chain</code> 类，LCEL 更简洁、透明，并且原生支持流式（streaming）、并行（parallelism）和异步（async）调用，是现代 LangChain 开发的基石。</li></ul><h3 id="2--lcel----setdebugtrue">2. 调试 LCEL 链 - <code>set_debug(True)</code></h3><p>学会了如何构建，下一步就是如何调试。对于 LCEL，<code>verbose=True</code> 的旧方法不再适用。取而代之的是一个强大的全局函数：</p><ul><li><strong>启用方法</strong>：</li></ul><pre class="language-python lang-python"><code class="language-python lang-python">    from langchain.globals import set_debug
  
    set_debug(True)</code></pre><ul><li><strong>日志解读</strong>：开启后，控制台会打印出非常详细的、带颜色的日志。通过 <code>&gt;</code> 符号可以清晰地看到链的嵌套结构，<code>[llm/start]</code>、<code>[parser/end]</code> 等标签则标明了每个组件的类型、输入、输出和耗时。这对于理解复杂链条的数据流向至关重要。</li></ul><h3 id="3-runnablesequence-vs-runnableparallel---">3. <code>RunnableSequence</code> vs. <code>RunnableParallel</code> - 链的两种基本形态</h3><p>在解读日志时，我遇到了这两个概念，它们的区别是理解 LCEL 工作流的关键。</p><ul><li><strong><code>RunnableSequence</code> (顺序执行)</strong>：就是由 <code>|</code> 创建的<strong>流水线</strong>。上一步的输出是下一步的输入，按顺序执行。</li><li><strong><code>RunnableParallel</code> (并行执行)</strong>：通常表现为一个<strong>字典</strong>。它会将同一个输入分发给字典中的多个处理分支，各分支独立运行，最后将结果汇总成一个字典。它常用于为下一步的 Prompt 准备多个不同的输入变量（如 <code>context</code> 和 <code>question</code>）。</li></ul><h3 id="4-runnablelambda---diy">4. <code>RunnableLambda</code> - 流水线上的“DIY”工具</h3><p>当标准组件不满足需求时，<code>RunnableLambda</code> 就派上了用场。</p><ul><li><strong>作用</strong>：它可以将<strong>任何一个 Python 函数</strong>包装成一个可以在 LCEL 链中使用的组件。</li><li><strong>应用场景</strong>：执行自定义的数据转换、调用外部 API、筛选数据，或者仅仅是在链中某个位置打印中间数据进行调试。在我们的对话式 RAG 示例中，它被用来从 <code>Memory</code> 对象中提取历史聊天记录。</li></ul><h3 id="5-retriever----">5. <code>Retriever</code> (检索器) - 知识库的“图书管理员”</h3><p>我之前只知道从向量数据库检索，但对 <code>Retriever</code> 的角色有些模糊。</p><ul><li><strong>核心职责</strong>：一个专门负责根据查询，从数据源中<strong>检索</strong>出最相关信息的<strong>接口</strong>。</li><li><strong>与 Vector Store 的区别</strong>：
<ul><li><code>Vector Store</code> (向量数据库) 是<strong>存储和搜索</strong>的底层工具，是被动的数据库。</li><li><code>Retriever</code> (检索器) 是封装了<strong>检索逻辑</strong>的上层应用接口。它知道“如何去取信息”。</li></ul></li><li><p><strong>代码示例</strong>：</p><pre class="language-python lang-python"><code class="language-python lang-python"># 从一个向量数据库创建一个检索器
retriever = vectorstore.as_retriever()</code></pre><p>这个 <code>retriever</code> 对象现在就是一个可以被放入 LCEL 链中的组件了。</p></li></ul><h2 id="">第三站：旅途终点与新起点</h2><p>通过这次深入的探讨学习，我不仅补齐了知识短板，更重要的是建立了一套完整的 LangChain 知识体系。</p><p><strong>我的核心收获</strong>：</p><ol start="1"><li><strong>现代化开发范式</strong>：我已完全理解 LCEL，并能用它来构建和调试链。这是从“能用”到“会用”的关键一步。</li><li><strong>构建对话应用的核心</strong>：掌握了 <code>Memory</code> 组件，我终于可以构建能联系上下文的聊天机器人了。</li><li><strong>洞察内部机制</strong>：通过学习解读 <code>debug</code> 日志，我对数据如何在链条中流动有了直观的认识，这让我有能力解决更复杂的问题。</li></ol><p>接下来，我将基于今天的学习，继续我的 LangChain 探索之旅：</p><ul><li><strong>动手实践</strong>：构建一个更复杂的、带有查询重写功能的对话式 RAG Agent。</li><li><strong>探索高级功能</strong>：研究 <code>LangServe</code> 以便将我的应用部署为 API。</li></ul></div><p style="text-align:right"><a href="https://bg.haomo.de/posts/default/langchain_1#comments">看完了？说点什么呢</a></p></div>]]></description><link>https://bg.haomo.de/posts/default/langchain_1</link><guid isPermaLink="true">https://bg.haomo.de/posts/default/langchain_1</guid><dc:creator><![CDATA[haohao]]></dc:creator><pubDate>Mon, 08 Sep 2025 08:56:47 GMT</pubDate></item></channel></rss>