#!/usr/bin/env python3 from gevent import pywsgi from flask import Flask, request, jsonify, render_template_string import yaml import subprocess import threading import os import re import socket NETPLAN_FILE = "/etc/netplan/01-netcfg.yaml" PORT = 6000 # 定义端口常量 app = Flask(__name__) # 前端 HTML 模板 HTML_TEMPLATE = """ 网络配置

网络配置

IP地址:

子网掩码位数:

网关:

DNS服务器:

注意:保存后会有20秒时间确认,超时自动回滚
""" def is_valid_ip(ip): """验证IP地址格式""" if not ip: return False pattern = r'^(\d{1,3}\.){3}\d{1,3}$' if re.match(pattern, ip): parts = ip.split('.') return all(0 <= int(part) <= 255 for part in parts) return False def get_current_ip(): """获取当前服务器的IP地址""" try: # 获取本机IP s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.connect(("8.8.8.8", 80)) current_ip = s.getsockname()[0] s.close() return current_ip except: return "127.0.0.1" def read_netplan(): """读取当前网络配置""" try: with open(NETPLAN_FILE) as f: data = yaml.safe_load(f) eth = data["network"]["ethernets"]["eth0"] addresses = eth.get("addresses", [""]) if addresses and addresses[0]: ip = addresses[0].split("/")[0] mask = addresses[0].split("/")[1] else: ip = "" mask = "24" gateway = eth.get("routes", [{}])[0].get("via", "") dns = eth.get("nameservers", {}).get("addresses", [""])[0] return {"ip": ip, "mask": mask, "gateway": gateway, "dns": dns} except Exception as e: print(f"读取配置文件出错: {e}") return {"ip": "", "mask": "24", "gateway": "", "dns": "8.8.8.8"} def apply_netplan_async(new_ip): """异步应用网络配置""" try: print(f"开始应用网络配置,新IP: {new_ip}") # 使用 netplan try 应用配置(20秒超时) process = subprocess.Popen( ["netplan", "try", "--timeout", "20"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True ) # 自动确认(按回车键) try: stdout, stderr = process.communicate(input='\n', timeout=5) print(f"网络配置已应用,新IP: {new_ip}") print(f"输出: {stdout}") if stderr: print(f"错误: {stderr}") # 重启网络服务确保配置生效 subprocess.run(["systemctl", "restart", "systemd-networkd"], capture_output=True, text=True) print("网络服务已重启") except subprocess.TimeoutExpired: # 如果用户没有手动确认,会自动回滚 print("等待用户确认中...配置将在20秒后自动回滚") process.kill() except Exception as e: print(f"应用网络配置时出错: {e}") except Exception as e: print(f"启动netplan进程出错: {e}") @app.route('/') def index(): net = read_netplan() current_ip = get_current_ip() print(f"当前访问IP: {request.remote_addr}, 服务器IP: {current_ip}") return render_template_string(HTML_TEMPLATE, **net, port=PORT) @app.route('/api/network', methods=['POST']) def set_network(): try: data = request.json ip = data.get("ip", "").strip() mask = data.get("mask", "").strip() gateway = data.get("gateway", "").strip() dns = data.get("dns", "").strip() # 验证IP地址格式 if not is_valid_ip(ip): return jsonify({ "success": False, "status": "IP地址格式不正确" }), 400 if not is_valid_ip(gateway): return jsonify({ "success": False, "status": "网关地址格式不正确" }), 400 if dns and not is_valid_ip(dns): return jsonify({ "success": False, "status": "DNS地址格式不正确" }), 400 # 基础验证 if not all([ip, mask, gateway, dns]): return jsonify({ "success": False, "status": "所有字段都必须填写" }), 400 # 读取并更新配置 with open(NETPLAN_FILE) as f: config = yaml.safe_load(f) # 更新网络配置 eth = config["network"]["ethernets"]["eth0"] eth["dhcp4"] = False eth["addresses"] = [f"{ip}/{mask}"] eth["routes"] = [{"to": "default", "via": gateway}] eth["nameservers"] = {"addresses": [dns]} # 保存配置文件 with open(NETPLAN_FILE, "w") as f: yaml.safe_dump(config, f, default_flow_style=False) print(f"配置文件已保存,新IP: {ip}") # 在后台异步应用网络配置 threading.Thread(target=apply_netplan_async, args=(ip,), daemon=True).start() return jsonify({ "success": True, "status": f"配置已保存并正在应用!\n\n请在20秒内按Enter键确认,否则配置会自动回滚。\n跳转地址: http://{ip}:{PORT}", "new_ip": ip, "port": PORT, "redirect_url": f"http://{ip}:{PORT}" }) except PermissionError: return jsonify({ "success": False, "status": "权限不足,请使用sudo运行此程序" }), 403 except FileNotFoundError: return jsonify({ "success": False, "status": f"配置文件 {NETPLAN_FILE} 不存在" }), 404 except Exception as e: return jsonify({ "success": False, "status": f"保存失败: {str(e)}" }), 500 @app.route('/check_connection') def check_connection(): """检查连接状态""" return jsonify({ "status": "connected", "message": "连接成功", "server_ip": get_current_ip(), "port": PORT }) def main(): """主函数""" # 检查权限 if os.geteuid() != 0: print("警告:请使用sudo运行此程序以获取必要的权限") return current_ip = get_current_ip() print("=" * 60) print("网络配置服务启动") print("=" * 60) print(f"当前服务器IP: {current_ip}") print(f"服务端口: {PORT}") print(f"访问地址: http://{current_ip}:{PORT}") print("=" * 60) print("保存配置后,会自动跳转到新的IP地址") print("注意:请确保防火墙允许端口访问") print("=" * 60) # 检查防火墙设置 try: result = subprocess.run(["ufw", "status"], capture_output=True, text=True) if "Status: active" in result.stdout: print("检测到UFW防火墙,请确保端口已开放:") print(f" sudo ufw allow {PORT}") print(f" sudo ufw allow 22 # SSH端口(可选)") except: pass # 使用gevent WSGI服务器 server = pywsgi.WSGIServer(('0.0.0.0', PORT), app) print(f"服务器已启动,监听端口: {PORT}") print("按 Ctrl+C 停止服务") try: server.serve_forever() except KeyboardInterrupt: print("\n服务已停止") except Exception as e: print(f"服务器错误: {e}") if __name__ == "__main__": main()