[Asm] 纯文本查看 复制代码
#!/bin/ash
# ============================================================
# luci-app-fwport v2 一键安装脚本
# 后端: /usr/sbin/fwport (IPv4 DNAT + IPv6 放行)
# 前端: LuCI 自定义视图 (网络 -> 端口转发管理)
# ============================================================
set -e
echo "=========================================="
echo " luci-app-fwport v2 安装"
echo " IPv4端口转发 + IPv6端口放行 统一管理"
echo "=========================================="
# ---------- 清理旧版 ----------
echo "[1/6] 清理旧版文件..."
rm -f /usr/lib/lua/luci/controller/fwport.lua 2>/dev/null || true
rm -rf /usr/lib/lua/luci/model/cbi/fwport 2>/dev/null || true
rm -f /usr/bin/fwport /bin/fwport 2>/dev/null || true
echo " 旧版已清理"
# ---------- 创建目录 ----------
echo "[2/6] 创建目录..."
mkdir -p /usr/sbin
mkdir -p /usr/lib/lua/luci/controller
mkdir -p /usr/lib/lua/luci/view/fwport
mkdir -p /etc/config
echo " 目录就绪"
# ---------- 写入后端脚本 ----------
echo "[3/6] 安装后端脚本 /usr/sbin/fwport..."
cat > /usr/sbin/fwport <<'SCRIPT_EOF'
#!/bin/ash
DEFAULT_TARGET="192.168.31.196"
if [ -f "/etc/config/fwport" ]; then
dt=$(uci get fwport.@global[0].default_target 2>/dev/null || echo "")
[ -n "$dt" ] && DEFAULT_TARGET="$dt"
fi
info() { echo -e "\033[0;32m[INFO]\033[0m $*"; }
warn() { echo -e "\033[1;33m[WARN]\033[0m $*"; }
error() { echo -e "\033[0;31m[ERROR]\033[0m $*"; }
cmd_list() {
echo "===== IPv4 端口转发 (DNAT) ====="
printf "%-20s %-8s %-15s %-25s %-8s\n" "名称" "协议" "外部端口" "目标" "状态"
echo "---------------------------------------------------------------"
idx=0
while true; do
name=$(uci get firewall.@redirect[$idx].name 2>/dev/null)
[ -z "$name" ] && break
proto=$(uci get firewall.@redirect[$idx].proto 2>/dev/null)
sp=$(uci get firewall.@redirect[$idx].src_dport 2>/dev/null)
dip=$(uci get firewall.@redirect[$idx].dest_ip 2>/dev/null)
dp=$(uci get firewall.@redirect[$idx].dest_port 2>/dev/null)
enabled=$(uci get firewall.@redirect[$idx].enabled 2>/dev/null || echo "1")
[ -z "$dp" ] && dp="$sp"
[ "$enabled" = "0" ] && status="禁用" || status="启用"
printf "%-20s %-8s %-15s %-25s %-8s\n" "$name" "$proto" "$sp" "${dip}:${dp}" "$status"
idx=$((idx+1))
done
echo ""
echo "===== IPv6 端口放行 ====="
printf "%-25s %-8s %-15s %-8s\n" "名称" "协议" "端口" "状态"
echo "---------------------------------------------------"
idx=0
while true; do
name=$(uci get firewall.@rule[$idx].name 2>/dev/null)
[ -z "$name" ] && break
fam=$(uci get firewall.@rule[$idx].family 2>/dev/null)
src=$(uci get firewall.@rule[$idx].src 2>/dev/null)
dst=$(uci get firewall.@rule[$idx].dest 2>/dev/null)
proto=$(uci get firewall.@rule[$idx].proto 2>/dev/null)
dp=$(uci get firewall.@rule[$idx].dest_port 2>/dev/null)
enabled=$(uci get firewall.@rule[$idx].enabled 2>/dev/null || echo "1")
if [ "$fam" = "ipv6" ] && [ "$src" = "wan" ] && [ "$dst" = "lan" ] && [ -n "$dp" ]; then
[ "$enabled" = "0" ] && status="禁用" || status="启用"
printf "%-25s %-8s %-15s %-8s\n" "$name" "$proto" "$dp" "$status"
fi
idx=$((idx+1))
done
}
cmd_add() {
port="$1"; proto="$2"; target="${3:-$DEFAULT_TARGET}"
[ -z "$port" ] && { error "用法: fwport add <端口> <tcp|udp> [目标IP]"; exit 1; }
[ -z "$proto" ] && proto="tcp"
info "添加 $port/$proto -> $target"
exists=0; idx=0
while true; do
name=$(uci get firewall.@redirect[$idx].name 2>/dev/null)
[ -z "$name" ] && break
sp=$(uci get firewall.@redirect[$idx].src_dport 2>/dev/null)
pr=$(uci get firewall.@redirect[$idx].proto 2>/dev/null)
[ "$sp" = "$port" ] && [ "$pr" = "$proto" ] && { exists=1; warn "IPv4已存在: $name"; }
idx=$((idx+1))
done
if [ $exists -eq 0 ]; then
uci add firewall redirect > /dev/null
uci set firewall.@redirect[-1].name="Forward-${port}"
uci set firewall.@redirect[-1].src="wan"
uci set firewall.@redirect[-1].proto="$proto"
uci set firewall.@redirect[-1].src_dport="$port"
uci set firewall.@redirect[-1].dest="lan"
uci set firewall.@redirect[-1].dest_ip="$target"
uci set firewall.@redirect[-1].dest_port="$port"
uci set firewall.@redirect[-1].target="DNAT"
info " [OK] IPv4 DNAT"
fi
exists=0; idx=0
while true; do
name=$(uci get firewall.@rule[$idx].name 2>/dev/null)
[ -z "$name" ] && break
fam=$(uci get firewall.@rule[$idx].family 2>/dev/null)
dp=$(uci get firewall.@rule[$idx].dest_port 2>/dev/null)
pr=$(uci get firewall.@rule[$idx].proto 2>/dev/null)
[ "$fam" = "ipv6" ] && [ "$dp" = "$port" ] && [ "$pr" = "$proto" ] && { exists=1; warn "IPv6已存在: $name"; }
idx=$((idx+1))
done
if [ $exists -eq 0 ]; then
uci add firewall rule > /dev/null
uci set firewall.@rule[-1].name="Allow-IPv6-${port}"
uci set firewall.@rule[-1].src="wan"
uci set firewall.@rule[-1].proto="$proto"
uci set firewall.@rule[-1].dest="lan"
uci set firewall.@rule[-1].dest_port="$port"
uci set firewall.@rule[-1].family="ipv6"
uci set firewall.@rule[-1].target="ACCEPT"
info " [OK] IPv6 放行"
fi
uci commit firewall
/etc/init.d/firewall restart > /dev/null 2>&1
info "完成!"
}
cmd_del() {
port="$1"; proto="$2"
[ -z "$port" ] && { error "用法: fwport del <端口> <tcp|udp>"; exit 1; }
[ -z "$proto" ] && proto="tcp"
info "删除 $port/$proto"
deleted=0; idx=0
while true; do
name=$(uci get firewall.@redirect[$idx].name 2>/dev/null)
[ -z "$name" ] && break
sp=$(uci get firewall.@redirect[$idx].src_dport 2>/dev/null)
pr=$(uci get firewall.@redirect[$idx].proto 2>/dev/null)
if [ "$sp" = "$port" ] && [ "$pr" = "$proto" ]; then
uci delete firewall.@redirect[$idx]
info " [OK] 删除IPv4: $name"
deleted=1
continue
fi
idx=$((idx+1))
done
idx=0
while true; do
name=$(uci get firewall.@rule[$idx].name 2>/dev/null)
[ -z "$name" ] && break
fam=$(uci get firewall.@rule[$idx].family 2>/dev/null)
dp=$(uci get firewall.@rule[$idx].dest_port 2>/dev/null)
pr=$(uci get firewall.@rule[$idx].proto 2>/dev/null)
if [ "$fam" = "ipv6" ] && [ "$dp" = "$port" ] && [ "$pr" = "$proto" ]; then
uci delete firewall.@rule[$idx]
info " [OK] 删除IPv6: $name"
deleted=1
continue
fi
idx=$((idx+1))
done
[ $deleted -eq 0 ] && { warn "未找到匹配规则"; return; }
uci commit firewall
/etc/init.d/firewall restart > /dev/null 2>&1
info "完成!"
}
usage() {
echo "iStoreOS 防火墙端口管理工具"
echo ""
echo "用法: fwport <命令> [参数]"
echo ""
echo "命令:"
echo " list 列出所有规则"
echo " add <端口> <协议> [IP] 添加端口 (默认目标: $DEFAULT_TARGET)"
echo " del <端口> <协议> 删除端口"
echo " backup 备份配置"
echo " restart 重启防火墙"
}
case "$1" in
list) cmd_list ;;
add) shift; cmd_add "$@" ;;
del) shift; cmd_del "$@" ;;
backup) cp /etc/config/firewall /root/firewall-$(date +%Y%m%d-%H%M%S).bak; info "已备份到 /root/" ;;
restart) /etc/init.d/firewall restart; info "已重启" ;;
*) usage ;;
esac
SCRIPT_EOF
chmod +x /usr/sbin/fwport
ln -sf /usr/sbin/fwport /bin/fwport
echo " 后端脚本已安装"
# ---------- 写入配置文件 ----------
echo "[4/6] 安装配置文件..."
if [ ! -f "/etc/config/fwport" ]; then
cat > /etc/config/fwport <<'CONF_EOF'
config global
option default_target '192.168.31.196'
CONF_EOF
echo " 配置文件已创建: /etc/config/fwport"
else
echo " 配置文件已存在,跳过"
fi
# ---------- 写入LuCI控制器 ----------
echo "[5/6] 安装 LuCI 页面..."
cat > /usr/lib/lua/luci/controller/fwport.lua <<'CTL_EOF'
module("luci.controller.fwport", package.seeall)
function index()
entry({"admin", "network", "fwport"}, call("action_main"), _("端口转发管理"), 90)
end
function action_main()
local http = require "luci.http"
local uci = require "luci.model.uci".cursor()
if http.formvalue("add") then
local port = http.formvalue("port")
local proto = http.formvalue("proto") or "tcp"
local target = http.formvalue("target")
if port and port ~= "" then
local cmd = "/usr/sbin/fwport add " .. port .. " " .. proto
if target and target ~= "" then
cmd = cmd .. " " .. target
end
luci.sys.call(cmd .. " >/dev/null 2>&1")
end
http.redirect(luci.dispatcher.build_url("admin", "network", "fwport"))
return
end
if http.formvalue("del") then
local port = http.formvalue("del_port")
local proto = http.formvalue("del_proto") or "tcp"
if port and port ~= "" then
luci.sys.call("/usr/sbin/fwport del " .. port .. " " .. proto .. " >/dev/null 2>&1")
end
http.redirect(luci.dispatcher.build_url("admin", "network", "fwport"))
return
end
local default_target = uci:get("fwport", "global", "default_target") or "192.168.31.196"
local ipv4_rules = {}
local idx = 0
while true do
local section = "@redirect[" .. idx .. "]"
local name = uci:get("firewall", section, "name")
if not name then break end
local proto = uci:get("firewall", section, "proto") or "tcp"
local src_dport = uci:get("firewall", section, "src_dport") or ""
local dest_ip = uci:get("firewall", section, "dest_ip") or ""
local dest_port = uci:get("firewall", section, "dest_port") or src_dport
local enabled = uci:get("firewall", section, "enabled") or "1"
table.insert(ipv4_rules, {
name = name, proto = proto, src_dport = src_dport,
dest_ip = dest_ip, dest_port = dest_port, enabled = enabled
})
idx = idx + 1
end
local ipv6_rules = {}
idx = 0
while true do
local section = "@rule[" .. idx .. "]"
local name = uci:get("firewall", section, "name")
if not name then break end
local family = uci:get("firewall", section, "family") or ""
local src = uci:get("firewall", section, "src") or ""
local dest = uci:get("firewall", section, "dest") or ""
local dest_port = uci:get("firewall", section, "dest_port") or ""
local proto = uci:get("firewall", section, "proto") or "tcp"
local enabled = uci:get("firewall", section, "enabled") or "1"
if family == "ipv6" and src == "wan" and dest == "lan" and dest_port ~= "" then
table.insert(ipv6_rules, {
name = name, proto = proto, dest_port = dest_port, enabled = enabled
})
end
idx = idx + 1
end
luci.template.render("fwport/main", {
default_target = default_target,
ipv4_rules = ipv4_rules,
ipv6_rules = ipv6_rules
})
end
CTL_EOF
# ---------- 写入视图模板 ----------
cat > /usr/lib/lua/luci/view/fwport/main.htm <<'VIEW_EOF'
<%+header%>
<h2 name="content"><%:端口转发管理%></h2>
<fieldset class="cbi-section">
<legend><%:添加端口规则%></legend>
<div class="cbi-section-descr"><%:添加后自动创建 IPv4 DNAT 转发 + IPv6 放行两条规则%></div>
<form method="post" action="<%=luci.dispatcher.build_url("admin", "network", "fwport")%>">
<table class="cbi-section-table" style="width:auto">
<tr>
<td class="cbi-value-field" style="padding-right:10px">
<input type="text" name="port" placeholder="<%:端口 如 8080%>" style="width:100px" required>
</td>
<td class="cbi-value-field" style="padding-right:10px">
<select name="proto">
<option value="tcp">TCP</option>
<option value="udp">UDP</option>
</select>
</td>
<td class="cbi-value-field" style="padding-right:10px">
<input type="text" name="target" value="<%=default_target%>" placeholder="<%:目标IPv4%>" style="width:140px">
</td>
<td>
<input type="submit" name="add" value="<%:添加%>" class="cbi-button cbi-button-apply">
</td>
</tr>
</table>
</form>
</fieldset>
<fieldset class="cbi-section">
<legend><%:IPv4 端口转发 (DNAT)%></legend>
<table class="cbi-section-table">
<tr class="cbi-section-table-titles">
<th class="cbi-section-table-cell"><%:名称%></th>
<th class="cbi-section-table-cell"><%:协议%></th>
<th class="cbi-section-table-cell"><%:外部端口%></th>
<th class="cbi-section-table-cell"><%:目标地址%></th>
<th class="cbi-section-table-cell"><%:状态%></th>
<th class="cbi-section-table-cell"><%:操作%></th>
</tr>
<% local i = 0; for _, r in ipairs(ipv4_rules) do i = i + 1 %>
<tr class="cbi-section-table-row cbi-rowstyle-<%=(i % 2 == 0 and 2 or 1)%>">
<td class="cbi-value-field"><%=r.name%></td>
<td class="cbi-value-field"><%=r.proto:upper()%></td>
<td class="cbi-value-field"><%=r.src_dport%></td>
<td class="cbi-value-field"><%=r.dest_ip%>:<%=r.dest_port%></td>
<td class="cbi-value-field">
<% if r.enabled == "0" then %>
<span style="color:#cc0000"><%:已禁用%></span>
<% else %>
<span style="color:#008800"><%:已启用%></span>
<% end %>
</td>
<td class="cbi-value-field">
<form method="post" action="<%=luci.dispatcher.build_url("admin", "network", "fwport")%>" style="display:inline;margin:0">
<input type="hidden" name="del_port" value="<%=r.src_dport%>">
<input type="hidden" name="del_proto" value="<%=r.proto%>">
<input type="submit" name="del" value="<%:删除%>" class="cbi-button cbi-button-remove">
</form>
</td>
</tr>
<% end %>
<% if #ipv4_rules == 0 then %>
<tr><td colspan="6" class="cbi-section-table-cell" style="text-align:center;color:#999;padding:15px"><%:暂无 IPv4 转发规则%></td></tr>
<% end %>
</table>
</fieldset>
<fieldset class="cbi-section">
<legend><%:IPv6 端口放行%></legend>
<table class="cbi-section-table">
<tr class="cbi-section-table-titles">
<th class="cbi-section-table-cell"><%:名称%></th>
<th class="cbi-section-table-cell"><%:协议%></th>
<th class="cbi-section-table-cell"><%:端口%></th>
<th class="cbi-section-table-cell"><%:状态%></th>
<th class="cbi-section-table-cell"><%:操作%></th>
</tr>
<% local j = 0; for _, r in ipairs(ipv6_rules) do j = j + 1 %>
<tr class="cbi-section-table-row cbi-rowstyle-<%=(j % 2 == 0 and 2 or 1)%>">
<td class="cbi-value-field"><%=r.name%></td>
<td class="cbi-value-field"><%=r.proto:upper()%></td>
<td class="cbi-value-field"><%=r.dest_port%></td>
<td class="cbi-value-field">
<% if r.enabled == "0" then %>
<span style="color:#cc0000"><%:已禁用%></span>
<% else %>
<span style="color:#008800"><%:已启用%></span>
<% end %>
</td>
<td class="cbi-value-field">
<form method="post" action="<%=luci.dispatcher.build_url("admin", "network", "fwport")%>" style="display:inline;margin:0">
<input type="hidden" name="del_port" value="<%=r.dest_port%>">
<input type="hidden" name="del_proto" value="<%=r.proto%>">
<input type="submit" name="del" value="<%:删除%>" class="cbi-button cbi-button-remove">
</form>
</td>
</tr>
<% end %>
<% if #ipv6_rules == 0 then %>
<tr><td colspan="5" class="cbi-section-table-cell" style="text-align:center;color:#999;padding:15px"><%:暂无 IPv6 放行规则%></td></tr>
<% end %>
</table>
</fieldset>
<div style="margin-top:15px;color:#666;font-size:12px;line-height:1.6">
<%:提示:添加规则会同时创建 IPv4 DNAT 转发和 IPv6 放行规则;删除按端口+协议同时删除两条。%><br>
<%:命令行同步可用:%> <code>fwport list</code> / <code>fwport add 443 tcp</code> / <code>fwport del 443 tcp</code>
</div>
<%+footer%>
VIEW_EOF
echo " LuCI 页面已安装"
# ---------- 清缓存重启 ----------
echo "[6/6] 清除缓存并重启服务..."
rm -f /tmp/luci-indexcache 2>/dev/null || true
rm -rf /tmp/luci-* 2>/dev/null || true
rm -rf /tmp/luci2-* 2>/dev/null || true
/etc/init.d/uhttpd restart 2>/dev/null || true
echo ""
echo "=========================================="
echo " 安装完成!"
echo "=========================================="
echo ""
echo "访问: 登录后台 -> 网络 -> 端口转发管理"
echo "如果菜单没出现,浏览器按 Ctrl+Shift+R 强制刷新"
echo ""
echo "命令行测试:"
echo " fwport list"
echo " fwport add 8080 tcp"
echo " fwport del 8080 tcp"
echo ""
echo "卸载:"
echo " rm -f /usr/sbin/fwport /bin/fwport"
echo " rm -f /usr/lib/lua/luci/controller/fwport.lua"
echo " rm -rf /usr/lib/lua/luci/view/fwport"
echo " rm -f /etc/config/fwport"
echo " rm -rf /tmp/luci-* && /etc/init.d/uhttpd restart"