Flask app progress
This commit is contained in:
parent
c4fe022d42
commit
b0994069ad
38 changed files with 6631 additions and 220 deletions
|
|
@ -41,6 +41,7 @@ import sys
|
|||
import argparse
|
||||
from pathlib import Path
|
||||
from datetime import datetime, timezone
|
||||
from validation import ip as validate_ip
|
||||
|
||||
SCRIPT_DIR = Path(__file__).parent
|
||||
DHCP_CONFIG_FILE = SCRIPT_DIR / "core.json"
|
||||
|
|
@ -48,12 +49,12 @@ DDNS_CONFIG_FILE = SCRIPT_DIR / "ddns.json"
|
|||
WG_DIR = Path("/etc/wireguard")
|
||||
KEEPALIVE = 25
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# ===================================================================
|
||||
# Helpers
|
||||
# ------------------------------------------------------------------------------
|
||||
# ===================================================================
|
||||
|
||||
def die(msg):
|
||||
print(f"ERROR: {msg}")
|
||||
print(f"ERROR: {msg}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
def check_root():
|
||||
|
|
@ -63,7 +64,7 @@ def check_root():
|
|||
def chown_to_script_dir_owner(path):
|
||||
"""Chown a file to the owner of the script directory.
|
||||
Keeps SCRIPT_DIR files user-owned even when running as root.
|
||||
/etc/wireguard files are intentionally excluded — they stay root-owned.
|
||||
/etc/wireguard files are intentionally excluded - they stay root-owned.
|
||||
"""
|
||||
try:
|
||||
stat = SCRIPT_DIR.stat()
|
||||
|
|
@ -124,9 +125,9 @@ def _fmt_bytes(n):
|
|||
else:
|
||||
return f"{n / 1024**3:.2f} GB"
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# ===================================================================
|
||||
# Load core.json / dotfiles
|
||||
# ------------------------------------------------------------------------------
|
||||
# ===================================================================
|
||||
|
||||
def load_dhcp():
|
||||
if not DHCP_CONFIG_FILE.exists():
|
||||
|
|
@ -169,9 +170,9 @@ def save_peers(iface, peers):
|
|||
path.chmod(0o600)
|
||||
chown_to_script_dir_owner(path)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# ===================================================================
|
||||
# IP allocation
|
||||
# ------------------------------------------------------------------------------
|
||||
# ===================================================================
|
||||
|
||||
def next_available_ip(vlan, peers):
|
||||
"""
|
||||
|
|
@ -197,9 +198,9 @@ def next_available_ip(vlan, peers):
|
|||
|
||||
die(f"No available IPs in VPN subnet {network} (all .2-.254 allocated).")
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# ===================================================================
|
||||
# Key management
|
||||
# ------------------------------------------------------------------------------
|
||||
# ===================================================================
|
||||
|
||||
def generate_server_key(iface):
|
||||
"""Generate server private key and store at WG_DIR/<iface>.key (600)."""
|
||||
|
|
@ -228,9 +229,9 @@ def generate_peer_keypair():
|
|||
).stdout.strip()
|
||||
return private, public
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# ===================================================================
|
||||
# Endpoint resolution
|
||||
# ------------------------------------------------------------------------------
|
||||
# ===================================================================
|
||||
|
||||
def resolve_endpoint(listen_port):
|
||||
"""
|
||||
|
|
@ -294,9 +295,9 @@ def resolve_endpoint(listen_port):
|
|||
entry = f"{entry}:{listen_port}"
|
||||
return entry
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# ===================================================================
|
||||
# Split-tunnel route computation
|
||||
# ------------------------------------------------------------------------------
|
||||
# ===================================================================
|
||||
|
||||
def split_tunnel_routes(dhcp_data):
|
||||
"""
|
||||
|
|
@ -316,9 +317,9 @@ def split_tunnel_routes(dhcp_data):
|
|||
routes.append(str(net))
|
||||
return routes
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# ===================================================================
|
||||
# Client config
|
||||
# ------------------------------------------------------------------------------
|
||||
# ===================================================================
|
||||
|
||||
def build_client_conf(peer, private_key, server_public_key, endpoint,
|
||||
allowed_ips, dns, domain, mtu):
|
||||
|
|
@ -348,9 +349,9 @@ def write_client_conf(peer, private_key, server_public_key, endpoint,
|
|||
chown_to_script_dir_owner(conf_path)
|
||||
return conf_path
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# ===================================================================
|
||||
# WireGuard server conf
|
||||
# ------------------------------------------------------------------------------
|
||||
# ===================================================================
|
||||
|
||||
def build_wg_conf(vlan, peers, server_private_key):
|
||||
iface = vlan["interface"]
|
||||
|
|
@ -381,9 +382,9 @@ def build_wg_conf(vlan, peers, server_private_key):
|
|||
]
|
||||
return "\n".join(lines)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# ===================================================================
|
||||
# Live peer sync
|
||||
# ------------------------------------------------------------------------------
|
||||
# ===================================================================
|
||||
|
||||
def sync_peers_live(iface, peers):
|
||||
"""
|
||||
|
|
@ -418,9 +419,9 @@ def sync_peers_live(iface, peers):
|
|||
run(["wg", "set", iface, "peer", key, "remove"])
|
||||
print(f" Removed peer: {key[:16]}...")
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# ===================================================================
|
||||
# Interface selection
|
||||
# ------------------------------------------------------------------------------
|
||||
# ===================================================================
|
||||
|
||||
def validate_wg_vlans(wg_vlans):
|
||||
"""Die with a clear message if any wg VLAN is missing a valid vpn_information block."""
|
||||
|
|
@ -432,8 +433,11 @@ def validate_wg_vlans(wg_vlans):
|
|||
f"Add: \"vpn_information\": {{\"listen_port\": 51820, \"gateway\": \"...\"}}")
|
||||
if not isinstance(info.get("listen_port"), int):
|
||||
die(f"Interface '{iface}' vpn_information is missing a valid listen_port in core.json.")
|
||||
if not info.get("gateway"):
|
||||
gw = info.get("gateway", "")
|
||||
if not gw:
|
||||
die(f"Interface '{iface}' vpn_information is missing gateway in core.json.")
|
||||
elif not validate_ip(gw):
|
||||
die(f"Interface '{iface}' vpn_information.gateway '{gw}' is not a valid IP address.")
|
||||
|
||||
def pick_wg_interface(wg_vlans):
|
||||
"""
|
||||
|
|
@ -459,9 +463,9 @@ def pick_wg_interface(wg_vlans):
|
|||
pass
|
||||
print(" Invalid selection.")
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# ===================================================================
|
||||
# --add-peer
|
||||
# ------------------------------------------------------------------------------
|
||||
# ===================================================================
|
||||
|
||||
def cmd_add_peer(dhcp_data):
|
||||
check_root()
|
||||
|
|
@ -569,9 +573,9 @@ def cmd_add_peer(dhcp_data):
|
|||
print(" sudo python3 vpn.py --apply")
|
||||
print()
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# ===================================================================
|
||||
# --list-peers
|
||||
# ------------------------------------------------------------------------------
|
||||
# ===================================================================
|
||||
|
||||
def cmd_list_peers(dhcp_data):
|
||||
check_root()
|
||||
|
|
@ -718,9 +722,9 @@ def cmd_list_peers(dhcp_data):
|
|||
print(" sudo python3 vpn.py --apply")
|
||||
print()
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# ===================================================================
|
||||
# --apply
|
||||
# ------------------------------------------------------------------------------
|
||||
# ===================================================================
|
||||
|
||||
def cmd_apply(dhcp_data):
|
||||
check_root()
|
||||
|
|
@ -808,9 +812,9 @@ def cmd_apply(dhcp_data):
|
|||
else:
|
||||
print(f"WARNING: {core_py} not found -- run core.py --apply manually to load VPN firewall rules.")
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# ===================================================================
|
||||
# --disable
|
||||
# ------------------------------------------------------------------------------
|
||||
# ===================================================================
|
||||
|
||||
def cmd_disable(dhcp_data):
|
||||
check_root()
|
||||
|
|
@ -825,9 +829,9 @@ def cmd_disable(dhcp_data):
|
|||
else:
|
||||
print(f"WireGuard service {svc} stopped and disabled.")
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# ===================================================================
|
||||
# --status
|
||||
# ------------------------------------------------------------------------------
|
||||
# ===================================================================
|
||||
|
||||
def cmd_status(dhcp_data):
|
||||
check_root()
|
||||
|
|
@ -844,8 +848,8 @@ def cmd_status(dhcp_data):
|
|||
r_enabled = run(["systemctl", "is-enabled", svc], check=False)
|
||||
active = r_active.stdout.strip()
|
||||
enabled = r_enabled.stdout.strip()
|
||||
active_sym = "✓" if active == "active" else "✗"
|
||||
enabled_sym = "✓" if enabled == "enabled" else "✗"
|
||||
active_sym = "+" if active == "active" else "x"
|
||||
enabled_sym = "+" if enabled == "enabled" else "x"
|
||||
print(f" {svc:<45} {active_sym} {active:<10} {enabled_sym} {enabled}")
|
||||
|
||||
if active == "active":
|
||||
|
|
@ -869,9 +873,9 @@ def cmd_status(dhcp_data):
|
|||
enabled_peers = [p for p in peers if p.get("enabled") is True]
|
||||
print(f" peers: {len(enabled_peers)} configured, {info.get('peers', 0)} connected")
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# ===================================================================
|
||||
# --logs
|
||||
# ------------------------------------------------------------------------------
|
||||
# ===================================================================
|
||||
|
||||
def cmd_logs(dhcp_data):
|
||||
check_root()
|
||||
|
|
@ -940,9 +944,9 @@ def cmd_logs(dhcp_data):
|
|||
|
||||
print()
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# ===================================================================
|
||||
# Main
|
||||
# ------------------------------------------------------------------------------
|
||||
# ===================================================================
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue