返回列表 发布新帖

[玩法教程] docker开启macvlan后互通的脚本分享

548 3
发表于 2026-3-2 11:07:59 | 查看全部 阅读模式 IP:–河北–衡水

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
image.png 通过脚本可以让宿主机访问macvlan的容器,mac
  1. #!/bin/bash
  2. set -e

  3. ###############################################################################
  4. # macvlan 自动互通管理工具(mv-agent)— based on your original version
  5. #
  6. # 在你提供的原脚本基础上只做“必要增强”:
  7. #   1) pick_host_ip 修复:不再只适配 /24(不再 ${subnet%.*})
  8. #      - 严格按 CIDR 计算 host_min/host_max(纯 bash)
  9. #   2) shim IP 固定:默认固定一个可复现的 shim IP,避免重启漂移
  10. #      - 默认策略:/24 用 .250;非 /24 用 (host_max - 5)
  11. #      - 可用 /etc/mv-agent.conf 覆盖:SHIM_IP_<NETKEY>=x.x.x.x
  12. #   3) 日志增强:安装探测 + 运行时同步输出
  13. #      - 宿主机原生 IP(主出口)
  14. #      - shim IP(桥接用)
  15. #      - macvlan 容器列表(name + ip)
  16. #      - 明确提示:容器访问宿主机用 shim IP;用原生 IP 通常失败
  17. #   4) 兼容 Synology / Docker:.Containers 是 map,用 $id,$c 遍历取 IPv4Address
  18. #   5) 路由更稳:对每个目标 IP 用 route replace,并清理 shim dev 上旧 /32
  19. #   6) shim 地址用 /32(避免生成 connected /24 路由干扰 bridge0 选路)
  20. #
  21. # 配置文件(可选):/etc/mv-agent.conf
  22. #   SHIM_HOST_OFFSET=5
  23. #   SHIM_IP_<NETKEY>=192.168.20.250
  24. #     - NETKEY:网络名转大写,非字母数字替换为 _
  25. ###############################################################################

  26. ############################
  27. # 全局变量
  28. ############################
  29. AGENT_BIN="/usr/local/bin/mv-agent.sh"
  30. SERVICE_FILE="/etc/systemd/system/mv-agent.service"
  31. OLD_SERVICE="macvlan-sync.service"
  32. CONF_FILE="/etc/mv-agent.conf"

  33. ############################
  34. # 颜色 & 工具函数
  35. ############################
  36. green()  { echo -e "\033[32m$*\033[0m"; }
  37. yellow() { echo -e "\033[33m$*\033[0m"; }
  38. red()    { echo -e "\033[31m$*\033[0m"; }

  39. ts() { date "+%F %T"; }

  40. pause() { read -rp "按回车继续..."; }

  41. require_root() {
  42.   if [[ $EUID -ne 0 ]]; then
  43.     red "请使用 root 或 sudo 运行此脚本"
  44.     exit 1
  45.   fi
  46. }

  47. require_cmd() {
  48.   command -v "$1" >/dev/null 2>&1 || { red "缺少依赖命令:$1"; exit 1; }
  49. }

  50. netkey() {
  51.   local net="$1"
  52.   echo "$net" | tr '[:lower:]' '[:upper:]' | sed 's/[^A-Z0-9]/_/g'
  53. }

  54. hash8() {
  55.   if command -v md5sum >/dev/null 2>&1; then
  56.     printf '%s' "$1" | md5sum | awk '{print $1}' | cut -c1-8
  57.   else
  58.     printf '%s' "$1" | cksum | awk '{print $1}' | cut -c1-8
  59.   fi
  60. }

  61. # 接口名长度安全(<=15)
  62. host_if_name() {
  63.   local net="$1"
  64.   echo "mvhost-$(hash8 "$net")"
  65. }

  66. get_host_primary_ip() {
  67.   ip -4 route get 1.1.1.1 2>/dev/null \
  68.     | awk '{for(i=1;i<=NF;i++) if($i=="src"){print $(i+1); exit}}'
  69. }

  70. ############################
  71. # IPv4/CIDR 计算(纯 bash)
  72. ############################
  73. ipv4_to_int() {
  74.   local ip="$1" a b c d
  75.   IFS=. read -r a b c d <<< "$ip"
  76.   echo $(( (a<<24) + (b<<16) + (c<<8) + d ))
  77. }

  78. int_to_ipv4() {
  79.   local n="$1"
  80.   echo "$(( (n>>24) & 255 )).$(( (n>>16) & 255 )).$(( (n>>8) & 255 )).$(( n & 255 ))"
  81. }

  82. # 输入:CIDR(192.168.20.0/24)
  83. # 输出:echo "host_min host_max prefix"
  84. cidr_host_range() {
  85.   local subnet="$1"
  86.   local ip="${subnet%/*}"
  87.   local prefix="${subnet#*/}"

  88.   [[ "$prefix" =~ ^[0-9]+$ ]] || return 1
  89.   (( prefix >= 0 && prefix <= 32 )) || return 1
  90.   (( prefix < 31 )) || return 1  # /31 /32 无可用 host

  91.   local ipi mask net bc host_min host_max
  92.   ipi="$(ipv4_to_int "$ip")"
  93.   mask=$(( (0xFFFFFFFF << (32-prefix)) & 0xFFFFFFFF ))
  94.   net=$(( ipi & mask ))
  95.   bc=$(( net | ((~mask) & 0xFFFFFFFF) ))
  96.   host_min=$(( net + 1 ))
  97.   host_max=$(( bc - 1 ))
  98.   echo "$host_min $host_max $prefix"
  99. }

  100. ############################
  101. # shim IP 选择逻辑(固定、可复现)
  102. ############################
  103. pick_host_ip() {
  104.   local net="$1"      # network name
  105.   local subnet="$2"   # 192.168.20.0/24

  106.   # 读配置(可选)
  107.   if [[ -f "$CONF_FILE" ]]; then
  108.     # shellcheck disable=SC1090
  109.     source "$CONF_FILE"
  110.   fi

  111.   local key="SHIM_IP_$(netkey "$net")"
  112.   local forced="${!key:-}"
  113.   local prefixlen="${subnet#*/}"
  114.   local host_min host_max _
  115.   read -r host_min host_max _ < <(cidr_host_range "$subnet") || return 1

  116.   # 1) 配置强制指定
  117.   local ip=""
  118.   if [[ -n "$forced" ]]; then
  119.     ip="$forced"
  120.   else
  121.     # 2) 默认固定策略:/24 用 .250;非 /24 用 host_max - offset(默认 5)
  122.     local offset="${SHIM_HOST_OFFSET:-5}"
  123.     if [[ "$prefixlen" == "24" ]]; then
  124.       # 用网络地址前三段 + .250(严格属于 /24)
  125.       local net_ip="${subnet%/*}"
  126.       local base="${net_ip%.*}"
  127.       ip="${base}.250"
  128.     else
  129.       local shim=$(( host_max - offset ))
  130.       if (( shim < host_min )); then shim=$host_min; fi
  131.       ip="$(int_to_ipv4 "$shim")"
  132.     fi
  133.   fi

  134.   # 冲突检查:若冲突则在 host_max 向下找一个可用(仍然固定在尾部区间,避免漂移到随机位置)
  135.   # 注意:这里不再 ping 探测(ICMP 可能被禁);只做本机 addr + neigh 冲突检查
  136.   if ip -4 addr | grep -qw "$ip" || ip neigh | grep -qw "$ip"; then
  137.     local cand
  138.     for cand_int in $(seq "$host_max" -1 "$host_min"); do
  139.       cand="$(int_to_ipv4 "$cand_int")"
  140.       if ip -4 addr | grep -qw "$cand" || ip neigh | grep -qw "$cand"; then
  141.         continue
  142.       fi
  143.       ip="$cand"
  144.       break
  145.     done
  146.   fi

  147.   # shim 采用 /32(关键)
  148.   echo "${ip}/32"
  149.   return 0
  150. }

  151. ############################
  152. # 配置探测 & 展示
  153. ############################
  154. show_detected_config() {
  155.   echo
  156.   green "🔍 检测 macvlan 网络配置"
  157.   echo "--------------------------------------"

  158.   local host_ip
  159.   host_ip="$(get_host_primary_ip || true)"
  160.   echo "$(ts) 宿主机原生 IP(主出口): ${host_ip:-<unknown>}"
  161.   echo

  162.   local nets=()
  163.   mapfile -t nets < <(docker network ls --filter driver=macvlan --format '{{.Name}}' || true)
  164.   if (( ${#nets[@]} == 0 )); then
  165.     yellow "未检测到 macvlan driver 的 Docker 网络。"
  166.     echo
  167.     return 0
  168.   fi

  169.   for net in "${nets[@]}"; do
  170.     [[ -n "$net" ]] || continue

  171.     parent="$(docker network inspect "$net" --format '{{index .Options "parent"}}' 2>/dev/null || true)"
  172.     subnet="$(docker network inspect "$net" \
  173.       --format '{{range .IPAM.Config}}{{println .Subnet}}{{end}}' 2>/dev/null \
  174.       | grep -m1 -E '^[0-9]+\.' || true)"

  175.     host_if="$(host_if_name "$net")"
  176.     picked_ip="$(pick_host_ip "$net" "$subnet" 2>/dev/null || echo "无法自动选择")"

  177.     echo
  178.     echo "网络名           : $net"
  179.     echo "  parent 接口    : ${parent:-<unknown>}"
  180.     echo "  IPv4 子网      : ${subnet:-<unknown>}"
  181.     echo "  宿主机接口名  : $host_if"
  182.     echo "  计划 shim IP  : $picked_ip"
  183.     echo "  提示           : 容器访问宿主机请用 shim IP;用宿主机原生 IP 通常失败"
  184.   done

  185.   echo
  186.   echo "将执行以下操作:"
  187.   echo "  - 为每个 macvlan 网络创建宿主机 macvlan 接口(如不存在)"
  188.   echo "  - 为每个 macvlan 容器添加 /32 路由(并清理 stale)"
  189.   echo "  - 启用并启动 systemd 服务:mv-agent"
  190.   echo "  - 自动监听 Docker event,变化即时生效"
  191.   echo "  - 停止并移除旧服务:$OLD_SERVICE(如存在)"
  192.   echo "--------------------------------------"
  193.   echo
  194. }

  195. ############################
  196. # 清理旧服务
  197. ############################
  198. stop_old_service() {
  199.   if systemctl list-unit-files 2>/dev/null | grep -q "^$OLD_SERVICE"; then
  200.     yellow "检测到旧服务 $OLD_SERVICE,正在清理..."
  201.     systemctl disable --now "$OLD_SERVICE" 2>/dev/null || true
  202.     rm -f "/etc/systemd/system/$OLD_SERVICE"
  203.     systemctl daemon-reload
  204.     green "旧服务已清理"
  205.   fi
  206. }

  207. ############################
  208. # 安装 mv-agent
  209. ############################
  210. install_agent() {
  211.   green "准备安装 / 重新安装 mv-agent"

  212.   # 安装日志:默认写入 /var/log(不可写则退化到 /tmp)
  213.   local log_dir="/var/log"
  214.   local ts_compact
  215.   ts_compact="$(date +%Y%m%d-%H%M%S)"
  216.   local logfile="${log_dir}/mv-agent-install-${ts_compact}.log"
  217.   mkdir -p "$log_dir" 2>/dev/null || true
  218.   if ! ( : >> "$logfile" ) 2>/dev/null; then
  219.     log_dir="/tmp"
  220.     logfile="${log_dir}/mv-agent-install-${ts_compact}.log"
  221.     : > "$logfile"
  222.   fi

  223.   require_cmd docker
  224.   require_cmd ip
  225.   require_cmd awk
  226.   require_cmd sed
  227.   require_cmd grep
  228.   require_cmd cut
  229.   require_cmd sort

  230.   show_detected_config
  231.   read -rp "确认继续安装?[y/N]: " confirm
  232.   [[ "$confirm" =~ ^[yY]$ ]] || { yellow "已取消安装"; return; }

  233.   echo
  234.   yellow "安装日志将同时输出到屏幕,并保存到:$logfile"
  235.   echo

  236.   {

  237.   stop_old_service

  238.   green "写入 mv-agent 主程序..."
  239.   cat > "$AGENT_BIN" << 'EOF'
  240. #!/bin/bash
  241. set -euo pipefail

  242. AGENT_TAG="[mv-agent]"
  243. CONF_FILE="/etc/mv-agent.conf"

  244. log(){ echo "$AGENT_TAG $*"; }
  245. warn(){ echo "$AGENT_TAG [WARN] $*" >&2; }
  246. err(){ echo "$AGENT_TAG [ERR ] $*" >&2; }
  247. ts(){ date "+%F %T"; }

  248. netkey() {
  249.   local net="$1"
  250.   echo "$net" | tr '[:lower:]' '[:upper:]' | sed 's/[^A-Z0-9]/_/g'
  251. }

  252. hash8() {
  253.   if command -v md5sum >/dev/null 2>&1; then
  254.     printf '%s' "$1" | md5sum | awk '{print $1}' | cut -c1-8
  255.   else
  256.     printf '%s' "$1" | cksum | awk '{print $1}' | cut -c1-8
  257.   fi
  258. }

  259. host_if_name() {
  260.   local net="$1"
  261.   echo "mvhost-$(hash8 "$net")"
  262. }

  263. get_host_primary_ip() {
  264.   ip -4 route get 1.1.1.1 2>/dev/null \
  265.     | awk '{for(i=1;i<=NF;i++) if($i=="src"){print $(i+1); exit}}'
  266. }

  267. ipv4_to_int() {
  268.   local ip="$1" a b c d
  269.   IFS=. read -r a b c d <<< "$ip"
  270.   echo $(( (a<<24) + (b<<16) + (c<<8) + d ))
  271. }

  272. int_to_ipv4() {
  273.   local n="$1"
  274.   echo "$(( (n>>24) & 255 )).$(( (n>>16) & 255 )).$(( (n>>8) & 255 )).$(( n & 255 ))"
  275. }

  276. cidr_host_range() {
  277.   local subnet="$1"
  278.   local ip="${subnet%/*}"
  279.   local prefix="${subnet#*/}"

  280.   [[ "$prefix" =~ ^[0-9]+$ ]] || return 1
  281.   (( prefix >= 0 && prefix <= 32 )) || return 1
  282.   (( prefix < 31 )) || return 1

  283.   local ipi mask net bc host_min host_max
  284.   ipi="$(ipv4_to_int "$ip")"
  285.   mask=$(( (0xFFFFFFFF << (32-prefix)) & 0xFFFFFFFF ))
  286.   net=$(( ipi & mask ))
  287.   bc=$(( net | ((~mask) & 0xFFFFFFFF) ))
  288.   host_min=$(( net + 1 ))
  289.   host_max=$(( bc - 1 ))
  290.   echo "$host_min $host_max $prefix"
  291. }

  292. pick_host_ip() {
  293.   local net="$1"
  294.   local subnet="$2"

  295.   if [[ -f "$CONF_FILE" ]]; then
  296.     # shellcheck disable=SC1090
  297.     source "$CONF_FILE"
  298.   fi

  299.   local key="SHIM_IP_$(netkey "$net")"
  300.   local forced="${!key:-}"
  301.   local prefixlen="${subnet#*/}"
  302.   local host_min host_max _
  303.   read -r host_min host_max _ < <(cidr_host_range "$subnet") || return 1

  304.   local ip=""
  305.   if [[ -n "$forced" ]]; then
  306.     ip="$forced"
  307.   else
  308.     local offset="${SHIM_HOST_OFFSET:-5}"
  309.     if [[ "$prefixlen" == "24" ]]; then
  310.       local net_ip="${subnet%/*}"
  311.       local base="${net_ip%.*}"
  312.       ip="${base}.250"
  313.     else
  314.       local shim=$(( host_max - offset ))
  315.       if (( shim < host_min )); then shim=$host_min; fi
  316.       ip="$(int_to_ipv4 "$shim")"
  317.     fi
  318.   fi

  319.   if ip -4 addr | grep -qw "$ip" || ip neigh | grep -qw "$ip"; then
  320.     local cand cand_int
  321.     for cand_int in $(seq "$host_max" -1 "$host_min"); do
  322.       cand="$(int_to_ipv4 "$cand_int")"
  323.       if ip -4 addr | grep -qw "$cand" || ip neigh | grep -qw "$cand"; then
  324.         continue
  325.       fi
  326.       ip="$cand"
  327.       break
  328.     done
  329.   fi

  330.   # shim 用 /32(避免 connected /24 路由干扰)
  331.   echo "${ip}/32"
  332. }

  333. list_container_name_ips() {
  334.   local net="$1"

  335.   # 兼容性处理:
  336.   #   - 有的 Docker 把 .Containers 作为 map(key 是 container-id)
  337.   #   - 有的 Docker 把 .Containers 作为 list
  338.   # 我们优先尝试 map 写法,若无输出则退回 list 写法。
  339.   local out=""
  340.   out="$(docker network inspect "$net" --format '{{range $id,$c := .Containers}}{{println $c.Name $c.IPv4Address}}{{end}}' 2>/dev/null || true)"
  341.   if [[ -z "$out" ]]; then
  342.     out="$(docker network inspect "$net" --format '{{range .Containers}}{{println .Name .IPv4Address}}{{end}}' 2>/dev/null || true)"
  343.   fi
  344.   echo "$out" | sed '/^$/d' || true
  345. }

  346. list_container_ips() {
  347.   local net="$1"

  348.   local out=""
  349.   out="$(docker network inspect "$net" --format '{{range $id,$c := .Containers}}{{println $c.IPv4Address}}{{end}}' 2>/dev/null || true)"
  350.   if [[ -z "$out" ]]; then
  351.     out="$(docker network inspect "$net" --format '{{range .Containers}}{{println .IPv4Address}}{{end}}' 2>/dev/null || true)"
  352.   fi

  353.   echo "$out"     | cut -d/ -f1     | sed '/^$/d' || true
  354. }

  355. reconcile_routes() {
  356.   local host_if="$1"
  357.   local desired_ips_file="$2"

  358.   # 清理该 dev 上旧 /32(避免越积越多)
  359.   # 注意:set -o pipefail 下 grep 找不到匹配会返回 1,可能导致 mv-agent 直接退出并被 systemd 重启。
  360.   # 这里改用 process substitution,并在 grep 后加 || true,确保“无旧路由”也是正常情况。
  361.   while read -r r; do
  362.     [[ -n "$r" ]] || continue
  363.     ip route del "$r" dev "$host_if" 2>/dev/null || true
  364.   done < <(ip -4 route show dev "$host_if" 2>/dev/null     | awk '{print $1}'     | grep -E '/32$' || true)


  365.   # 强制写入目标 /32(replace 覆盖冲突)
  366.   while read -r ip; do
  367.     [[ -n "$ip" ]] || continue
  368.     ip route replace "$ip/32" dev "$host_if" 2>/dev/null || true
  369.   done < "$desired_ips_file"
  370. }

  371. sync_all() {
  372.   local host_primary
  373.   host_primary="$(get_host_primary_ip || true)"

  374.   local nets=()
  375.   mapfile -t nets < <(docker network ls --filter driver=macvlan --format '{{.Name}}' || true)
  376.   [[ ${#nets[@]} -gt 0 ]] || { warn "$(ts) 未找到 macvlan network"; return 0; }

  377.   for net in "${nets[@]}"; do
  378.     [[ -n "$net" ]] || continue

  379.     local parent subnet host_if have_ip host_ip
  380.     parent="$(docker network inspect "$net" --format '{{index .Options "parent"}}' 2>/dev/null || true)"
  381.     subnet="$(docker network inspect "$net" \
  382.       --format '{{range .IPAM.Config}}{{println .Subnet}}{{end}}' 2>/dev/null \
  383.       | grep -m1 -E '^[0-9]+\.' || true)"

  384.     [[ -z "$parent" || -z "$subnet" ]] && continue

  385.     host_if="$(host_if_name "$net")"
  386.     have_ip="$(ip -4 addr show "$host_if" 2>/dev/null | awk '/inet /{print $2}' | head -n1 || true)"

  387.     if [[ -z "$have_ip" ]]; then
  388.       host_ip="$(pick_host_ip "$net" "$subnet")" || continue
  389.       log "$(ts) net=$net parent=$parent host_if=$host_if shim_ip=$host_ip"
  390.       ip link add "$host_if" link "$parent" type macvlan mode bridge 2>/dev/null || true
  391.       ip addr replace "$host_ip" dev "$host_if" 2>/dev/null || true
  392.       ip link set "$host_if" up
  393.     else
  394.       ip link set "$host_if" up 2>/dev/null || true
  395.       host_ip="$have_ip"
  396.     fi

  397.     # 收集容器 IP 列表
  398.     local desired_ips_file
  399.     desired_ips_file="$(mktemp)"
  400.     list_container_ips "$net" > "$desired_ips_file"
  401.     if [[ ! -s "$desired_ips_file" ]]; then
  402.       warn "$(ts) [WARN] 未读取到容器 IP(net=$net)。将输出 docker inspect 的 Containers 片段以便排障。"
  403.       docker network inspect "$net" --format '{{json .Containers}}' 2>/dev/null | head -c 2000 | while read -r l; do warn "$(ts)   Containers(json)=$l"; done
  404.     fi

  405.     # 写路由(清理+replace)
  406.     reconcile_routes "$host_if" "$desired_ips_file"
  407.     rm -f "$desired_ips_file"

  408.     # 日志输出:宿主机 IP、shim、容器列表、提示语
  409.     local shim_plain="${host_ip%/*}"
  410.     log "$(ts) ✅ 同步完成:net=$net"
  411.     log "$(ts)   - 宿主机原生 IP(主出口) : ${host_primary:-<unknown>}"
  412.     log "$(ts)   - shim 接口              : $host_if"
  413.     log "$(ts)   - shim IP                : $host_ip"
  414.     log "$(ts)   - 容器列表(name ip):"
  415.     list_container_name_ips "$net" | while read -r line; do
  416.       [[ -n "$line" ]] || continue
  417.       log "$(ts)       $line"
  418.     done
  419.     log "$(ts)   - 访问提示:"
  420.     log "$(ts)       * macvlan 容器访问宿主机:请使用 shim IP($shim_plain)"
  421.     log "$(ts)       * 使用宿主机原生 IP(${host_primary:-<host_ip>})在 macvlan 场景下通常会失败/不通"
  422.   done
  423. }

  424. sync_all

  425. log "$(ts) 监听 Docker 事件..."
  426. docker events \
  427.   --filter type=container \
  428.   --filter event=start \
  429.   --filter event=restart \
  430.   --filter event=destroy \
  431.   --format '{{.Action}} {{.Actor.Attributes.name}}' \
  432. | while read -r _; do
  433.     sync_all
  434.   done
  435. EOF

  436.   chmod +x "$AGENT_BIN"

  437.   green "写入 systemd 服务..."
  438.   cat > "$SERVICE_FILE" << EOF
  439. [Unit]
  440. Description=Macvlan auto connectivity agent
  441. After=network-online.target docker.service
  442. Wants=network-online.target docker.service

  443. [Service]
  444. Type=simple
  445. ExecStart=$AGENT_BIN
  446. Restart=always
  447. RestartSec=3

  448. [Install]
  449. WantedBy=multi-user.target
  450. EOF

  451.   systemctl daemon-reload
  452.   systemctl enable --now mv-agent

  453.   green "✅ mv-agent 安装完成并已启动"
  454.   yellow "提示:容器访问宿主机请用 shim IP(桥接 IP);宿主机原生 IP 通常不通。"

  455.   # 安装完成后,给出一屏“关键信息”汇总(方便确认是否生效)
  456.   echo
  457.   green "=========================================="
  458.   green " 安装完成关键信息(可复制留存)"
  459.   green "=========================================="
  460.   local host_primary
  461.   host_primary="$(get_host_primary_ip || true)"
  462.   echo "$(ts) 宿主机原生 IP(主出口): ${host_primary:-<unknown>}"
  463.   echo
  464.   echo "$(ts) shim 接口 / IP(mvhost-*):"
  465.   ip -br addr | sed 's/^/  /'
  466.   echo
  467.   echo "$(ts) macvlan 容器列表(name ip):"
  468.   docker network ls --filter driver=macvlan --format '{{.Name}}' | while read -r net; do
  469.     [[ -n "$net" ]] || continue
  470.     echo "  network: $net"
  471.     docker network inspect "$net" --format '{{range $id,$c := .Containers}}{{println "    " $c.Name $c.IPv4Address}}{{end}}' 2>/dev/null || true
  472.   done
  473.   echo
  474.   yellow "访问提示:macvlan 容器访问宿主机请使用 shim IP(上面 mvhost-* 的 IPv4/32 地址);使用宿主机原生 IP 往往会失败/不通。"
  475.   echo

  476.   } 2>&1 | tee -a "$logfile"

  477.   echo
  478.   green "📄 安装日志已保存:$logfile"
  479.   echo
  480. }

  481. ############################
  482. # 卸载 mv-agent
  483. ############################
  484. uninstall_agent() {
  485.   yellow "开始卸载 mv-agent..."

  486.   systemctl disable --now mv-agent 2>/dev/null || true
  487.   rm -f "$SERVICE_FILE" "$AGENT_BIN"

  488.   for ifc in $(ip -br link | awk '{print $1}' | sed 's/@.*//' | grep '^mvhost-' || true); do
  489.     ip link del "$ifc" 2>/dev/null || true
  490.   done

  491.   systemctl daemon-reload
  492.   green "✅ mv-agent 已完全卸载"
  493. }

  494. ############################
  495. # 状态查看
  496. ############################
  497. status_agent() {
  498.   systemctl status mv-agent || true
  499.   echo
  500.   journalctl -u mv-agent -n 80 --no-pager 2>/dev/null || true
  501. }

  502. ############################
  503. # 主菜单
  504. ############################
  505. require_root

  506. while true; do
  507.   # 不使用 clear,避免覆盖终端历史输出(可通过 NO_CLEAR=0 强制清屏)
  508.   if [[ "${NO_CLEAR:-1}" == "0" ]]; then
  509.     clear
  510.   fi
  511.   echo "=========================================="
  512.   echo " macvlan 自动互通管理工具(mv-agent)"
  513.   echo "=========================================="
  514.   echo "1) 安装 / 重新安装 mv-agent"
  515.   echo "2) 卸载 mv-agent"
  516.   echo "3) 查看 mv-agent 状态"
  517.   echo "4) 退出"
  518.   echo
  519.   read -rp "请选择操作 [1-4]: " choice

  520.   case "$choice" in
  521.     1) install_agent; pause ;;
  522.     2) uninstall_agent; pause ;;
  523.     3) status_agent; pause ;;
  524.     4) exit 0 ;;
  525.     *) red "无效选择"; pause ;;
  526.   esac
  527. done
复制代码

vlan的容器通过中间的叫shim IP的ip也可以访问宿主机 达到互通效果

评论3

梦梦Lv.7绿联NAS社区会员用户 发表于 2026-3-2 15:23:30 来自手机 | 查看全部 IP:–河北–衡水
大佬!!
CHN_PotatoLv.4 发表于 2026-3-2 17:19:42 | 查看全部 IP:–浙江–绍兴
哥哥姐姐给你们点赞了
aIL4ERLv.1绿联NAS社区会员用户 发表于 2026-3-4 10:24:15 | 查看全部 IP:–湖南
doumao佬牛啊,这里也能看到你

评论

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Copyright © 2026 绿联NAS私有云社区 版权所有 All Rights Reserved. 粤公网安备44030002002555号| 粤ICP备12028978号
关灯 在本版发帖
联系技术支持
返回顶部
快速回复 返回顶部 返回列表