Development
This commit is contained in:
parent
ac0aa4de22
commit
91d8b950b7
5 changed files with 38 additions and 35 deletions
|
|
@ -165,19 +165,19 @@ def main():
|
|||
help="VLAN ID of the WireGuard VLAN to add the peer to (e.g. 40)")
|
||||
args = parser.parse_args()
|
||||
|
||||
# -- Validate IP -----------------------------------------------------------
|
||||
# Validate IP =======================================================
|
||||
try:
|
||||
peer_ip = str(ipaddress.IPv4Address(args.ip))
|
||||
except ValueError:
|
||||
die(f"'{args.ip}' is not a valid IPv4 address.")
|
||||
|
||||
# -- Load config and find WG VLAN ------------------------------------------
|
||||
# Load config and find WG VLAN ==========================================
|
||||
data = load_config()
|
||||
vlan = find_wg_vlan(data, iface=args.iface, vlan_id=args.vlan_id)
|
||||
|
||||
iface = resolve_wg_iface(vlan, data)
|
||||
|
||||
# -- Validate peer IP is within subnet -------------------------------------
|
||||
# Validate peer IP is within subnet =================================
|
||||
try:
|
||||
network = ipaddress.IPv4Network(f"{vlan['subnet']}/{vlan['subnet_mask']}", strict=False)
|
||||
except (KeyError, ValueError) as e:
|
||||
|
|
@ -186,19 +186,19 @@ def main():
|
|||
if ipaddress.IPv4Address(peer_ip) not in network:
|
||||
die(f"IP {peer_ip} is not within the VPN subnet {network}.")
|
||||
|
||||
# -- Check for duplicates --------------------------------------------------
|
||||
# Check for duplicates ==============================================
|
||||
peers = vlan.setdefault("peers", [])
|
||||
if any(p.get("name") == args.name for p in peers):
|
||||
die(f"A peer named '{args.name}' already exists.")
|
||||
if any(p.get("ip") == peer_ip for p in peers):
|
||||
die(f"IP {peer_ip} is already assigned to another peer.")
|
||||
|
||||
# -- Generate keypair and read server public key ---------------------------
|
||||
# Generate keypair and read server public key =======================
|
||||
print(f"Generating keypair for '{args.name}'...")
|
||||
private_key, public_key = generate_keypair()
|
||||
srv_pub = server_pubkey(iface)
|
||||
|
||||
# -- Update config.json ------------------------------------------------------
|
||||
# Update config.json ================================================
|
||||
peers.append({
|
||||
"name": args.name,
|
||||
"ip": peer_ip,
|
||||
|
|
@ -209,7 +209,7 @@ def main():
|
|||
save_config(data)
|
||||
print(f"Added peer '{args.name}' to config.json.")
|
||||
|
||||
# -- Write client conf -----------------------------------------------------
|
||||
# Write client conf =================================================
|
||||
conf_content = build_client_conf(vlan, peer_ip, private_key, srv_pub, args.split_tunnel)
|
||||
if args.output:
|
||||
out_path = Path(args.output)
|
||||
|
|
|
|||
|
|
@ -545,7 +545,7 @@ def main():
|
|||
print(" This wizard installs required packages and optionally")
|
||||
print(" sets up the web dashboard and external HTTPS access.")
|
||||
|
||||
# -- Package manager -------------------------------------------
|
||||
# Package manager ===================================================
|
||||
pm = detect_pm()
|
||||
if pm is None:
|
||||
print()
|
||||
|
|
@ -557,11 +557,11 @@ def main():
|
|||
print(f"\n Detected package manager: {pm}")
|
||||
pm_ok = True
|
||||
|
||||
# -- Core packages ---------------------------------------------
|
||||
# Core packages =====================================================
|
||||
if pm_ok:
|
||||
install_core_packages(pm)
|
||||
|
||||
# -- Dashboard -------------------------------------------------
|
||||
# Dashboard =========================================================
|
||||
header("Dashboard (optional)")
|
||||
print(" The Routlin Dashboard is a web UI for managing the router.")
|
||||
print(" It runs as a Docker container. Without it, config.json must")
|
||||
|
|
@ -596,7 +596,7 @@ def main():
|
|||
default="y"
|
||||
)
|
||||
|
||||
# -- Docker ----------------------------------------------------
|
||||
# Docker ============================================================
|
||||
header("Docker")
|
||||
if pm_ok:
|
||||
install_docker(pm)
|
||||
|
|
@ -605,15 +605,15 @@ def main():
|
|||
else:
|
||||
print(" Docker is already installed.")
|
||||
|
||||
# -- docker-compose.yml ----------------------------------------
|
||||
# docker-compose.yml ================================================
|
||||
setup_docker_compose(reuse_config=reuse_config)
|
||||
create_dotfiles()
|
||||
|
||||
# -- Dashboard timer -------------------------------------------
|
||||
# Dashboard timer ===================================================
|
||||
header("Dashboard Timer")
|
||||
install_dashboard_timer()
|
||||
|
||||
# -- External access -------------------------------------------
|
||||
# External access ===================================================
|
||||
header("External Access (optional)")
|
||||
ext_domain = _external_access_domain()
|
||||
if ext_domain:
|
||||
|
|
@ -643,7 +643,7 @@ def main():
|
|||
print(next_step)
|
||||
return
|
||||
|
||||
# -- Caddy -----------------------------------------------------
|
||||
# Caddy =============================================================
|
||||
header("Caddy (HTTPS)")
|
||||
if pm_ok:
|
||||
install_caddy(pm)
|
||||
|
|
|
|||
|
|
@ -331,11 +331,11 @@ def validate_config(data):
|
|||
_vid = _derived_ids[i]
|
||||
vlan_ifaces.append(_lan if _vid == 1 else f"{_lan}.{_vid}")
|
||||
|
||||
# -- upstream_dns block ----------------------------------------------------
|
||||
# upstream_dns block ============================================
|
||||
if not data.get("upstream_dns", {}).get("upstream_servers"):
|
||||
errors.append("upstream_dns.upstream_servers is missing or empty.")
|
||||
|
||||
# -- WAN / LAN interfaces --------------------------------------------------
|
||||
# WAN / LAN interfaces ==========================================
|
||||
gen = data.get("network_interfaces", {})
|
||||
wan = gen.get("wan_interface", "")
|
||||
lan = gen.get("lan_interface", "")
|
||||
|
|
@ -357,7 +357,7 @@ def validate_config(data):
|
|||
if wan == lan:
|
||||
errors.append(f"network_interfaces.wan_interface and network_interfaces.lan_interface must be different (both set to '{wan}').")
|
||||
|
||||
# -- Blocklist library -----------------------------------------------------
|
||||
# Blocklist library =============================================
|
||||
blocklists_by_name = {}
|
||||
for idx, bl in enumerate(data.get("dns_blocking", {}).get("blocklists", [])):
|
||||
name = bl.get("name", "")
|
||||
|
|
@ -373,7 +373,7 @@ def validate_config(data):
|
|||
else:
|
||||
blocklists_by_name[name] = bl
|
||||
|
||||
# -- Per-VLAN validation ---------------------------------------------------
|
||||
# Per-VLAN validation ===========================================
|
||||
vlan_networks = {} # iface -> IPv4Network (used for NAT section)
|
||||
|
||||
for i, (vlan, iface) in enumerate(zip(_all_vlans, vlan_ifaces)):
|
||||
|
|
@ -403,7 +403,7 @@ def validate_config(data):
|
|||
errors.append(f"{label}: mdns_reflection must be false for WireGuard interfaces.")
|
||||
|
||||
if is_wg(vlan):
|
||||
# -- vpn_information -----------------------------------------------
|
||||
# vpn_information =======================================
|
||||
vpi = vlan.get("vpn_information")
|
||||
if not isinstance(vpi, dict):
|
||||
errors.append(f"{label}: vpn_information must be a plain object.")
|
||||
|
|
@ -418,7 +418,7 @@ def validate_config(data):
|
|||
else:
|
||||
seen_listen_ports[lp] = name
|
||||
|
||||
# -- subnet/subnet_mask --------------------------------------------
|
||||
# subnet/subnet_mask ====================================
|
||||
for field in ("subnet", "subnet_mask"):
|
||||
if not vlan.get(field):
|
||||
errors.append(f"{label}: missing required field '{field}'.")
|
||||
|
|
@ -430,7 +430,7 @@ def validate_config(data):
|
|||
except ValueError as e:
|
||||
errors.append(f"{label}: invalid subnet/subnet_mask: {e}")
|
||||
|
||||
# -- server_identities ---------------------------------------------
|
||||
# server_identities =====================================
|
||||
if not vlan.get("server_identities"):
|
||||
errors.append(f"{label}: server_identities is empty or missing.")
|
||||
identity_ips = []
|
||||
|
|
@ -449,7 +449,7 @@ def validate_config(data):
|
|||
else:
|
||||
identity_ips.append(ip_addr)
|
||||
|
||||
# -- vpn_information.explicit_overrides ----------------------------
|
||||
# vpn_information.explicit_overrides ====================
|
||||
eo = vpi.get("explicit_overrides", {}) if isinstance(vpi, dict) else {}
|
||||
if not isinstance(eo, dict):
|
||||
errors.append(f"{label}: vpn_information.explicit_overrides must be a plain object.")
|
||||
|
|
@ -476,7 +476,7 @@ def validate_config(data):
|
|||
if domain_val and not domainname(domain_val):
|
||||
errors.append(f"{label}: vpn_information.domain '{domain_val}' is not a valid domain name.")
|
||||
|
||||
# -- peers ---------------------------------------------------------
|
||||
# peers =================================================
|
||||
seen_peer_names = {}
|
||||
seen_peer_ips = {}
|
||||
for pidx, peer in enumerate(vlan.get("peers", [])):
|
||||
|
|
@ -556,7 +556,7 @@ def validate_config(data):
|
|||
if ip:
|
||||
identity_ips.append(ip)
|
||||
|
||||
# -- Validate explicit_overrides ---------------------------------------
|
||||
# Validate explicit_overrides ===============================
|
||||
eo = d.get("explicit_overrides", {})
|
||||
if not isinstance(eo, dict):
|
||||
errors.append(f"{label}: explicit_overrides must be a plain object.")
|
||||
|
|
@ -642,7 +642,7 @@ def validate_config(data):
|
|||
if bl_name not in blocklists_by_name:
|
||||
errors.append(f"{label}: use_blocklists references unknown blocklist '{bl_name}'.")
|
||||
|
||||
# -- NAT / firewall validation ---------------------------------------------
|
||||
# NAT / firewall validation =====================================
|
||||
valid_protos = VALID_PROTOCOLS
|
||||
known_interfaces = set(seen_interfaces.keys())
|
||||
|
||||
|
|
@ -675,7 +675,7 @@ def validate_config(data):
|
|||
if net:
|
||||
nat_check_ip_in_network(f"{label} redirect_to", r.get("redirect_to", ""), net)
|
||||
|
||||
# -- port_forwarding validation (top-level) --------------------------------
|
||||
# port_forwarding validation (top-level) ========================
|
||||
for idx, r in enumerate(data.get("port_forwarding", [])):
|
||||
desc = r.get("description", "?")
|
||||
label = f"port_forwarding[{idx}] '{desc}'"
|
||||
|
|
@ -709,13 +709,13 @@ def validate_config(data):
|
|||
if r.get("dst_port") is not None:
|
||||
nat_check_port(f"{label} dst_port", r.get("dst_port"))
|
||||
|
||||
# -- radius_default uniqueness check ---------------------------------------
|
||||
# radius_default uniqueness check ===============================
|
||||
defaults = [v["name"] for v in data.get("vlans", []) if v.get("radius_default") is True]
|
||||
if len(defaults) > 1:
|
||||
errors.append(f"Multiple VLANs have radius_default: true ({', '.join(defaults)}). "
|
||||
f"Only one VLAN may be the RADIUS default.")
|
||||
|
||||
# -- RADIUS requires multiple VLANs ----------------------------------------
|
||||
# RADIUS requires multiple VLANs ================================
|
||||
non_wg_vlans = [v for v in data.get("vlans", []) if not is_wg(v)]
|
||||
has_radius_clients = any(
|
||||
r.get("radius_client")
|
||||
|
|
@ -728,7 +728,7 @@ def validate_config(data):
|
|||
"Dynamic VLAN assignment requires at least two VLANs."
|
||||
)
|
||||
|
||||
# -- host_overrides validation ---------------------------------------------
|
||||
# host_overrides validation =====================================
|
||||
all_vlan_nets = list(vlan_networks.values())
|
||||
for idx, entry in enumerate(data.get("host_overrides", [])):
|
||||
lbl = f"host_overrides[{idx}] '{entry.get('host', '?')}'"
|
||||
|
|
@ -744,7 +744,7 @@ def validate_config(data):
|
|||
if all_vlan_nets and not any(ip_addr in net for net in all_vlan_nets):
|
||||
errors.append(f"{lbl}: '{ip_str}' does not fall within any configured VLAN subnet.")
|
||||
|
||||
# -- banned_ips validation -------------------------------------------------
|
||||
# banned_ips validation =========================================
|
||||
for idx, entry in enumerate(data.get("banned_ips", [])):
|
||||
ip_val = entry.get("ip", "")
|
||||
lbl = f"banned_ips[{idx}] '{entry.get('description', '')}'"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue