feat: add networkd subsystem and fix code review issues
Phase 1-4: Networkd subsystem - lib/network.py: systemd-networkd config renderer (.network INI files) with full schema support: [Match], [Link], [Network], [Address], [Route], [DHCPv4], [DHCPv6] sections. One Address/=DNS= line per value per spec. Route sections use #N suffix per systemd.syntax(7). - lib/network.py: generate_network_files() with 50-<name>.network prefix and stale file cleanup - lib/network.py: collect_upstream_dns() filters local/private DNS - lib/network.py: infer_dhcp_ranges() and infer_zones() helpers - daemon/handlers/network.py: routes for GET/POST /network/interfaces and full apply with DNS upstream sync to dnsmasq - webui/api/network.py: Flask blueprint for /api/network/* endpoints - webui/api: interfaces page updated with IP config inline editing - lib/state.py: networkd collector using parse_networkctl_status() - system/sudoers.d/vacuum-walld: networkctl + systemd-network rules - system/systemd/vacuum-walld.service: ReadWritePaths for /etc/systemd/network - install.sh: ACME email now optional, configured from WebUI - lib/acme.py: get_email() falls back to declarative config Phase 5: Code review fixes - daemon/server.py: path params now win over JSON body and query params in request body merge (prevents config save name override) - daemon/server.py: remove dead 'import re' - daemon/handlers/network.py: replace Path.mkdir() with sudo mkdir for /etc/systemd/network (ProtectSystem=strict compatibility) - system/sudoers.d/vacuum-walld: pin systemctl to specific commands (reload/is-active dnsmasq instead of wildcard) - system/sudoers.d/vacuum-walld: restore !requiretty and section comment - lib/network.py: remove unused _MANAGEMENT_PORTS constant - webui/api/network.py: remove redundant body[\name\] = name in save_interface Tests: 332 passing (110 new/updated), ruff clean
This commit is contained in:
+34
-31
@@ -45,22 +45,22 @@ while [[ $# -gt 0 ]]; do
|
||||
" --dev Dev mode: auto-detect repo owner, skip safety warning" \
|
||||
" --mgmt-pass PASS WebUI basic auth password (required)" \
|
||||
" --mgmt-user USER WebUI basic auth username (default: admin)" \
|
||||
" --mgmt-domain DOMAIN Management domain (auto-detected)" \
|
||||
" --acme-email EMAIL ACME registration email (required)" \
|
||||
" --wan-iface IFACE WAN interface name (auto-detected)" \
|
||||
" --mgmt-domain DOMAIN Management domain (auto-detected)" \
|
||||
" --acme-email EMAIL ACME contact email (optional, deprecated — use WebUI)" \
|
||||
" --wan-iface IFACE WAN interface name (auto-detected)" \
|
||||
" --lan-ifaces IFC,... LAN interface names, comma-separated (auto-detected)" \
|
||||
" -h, --help Show this help" \
|
||||
"" \
|
||||
"All options also have environment variable equivalents:" \
|
||||
" USER_NAME, INSTALL_DIR, MGMT_PASS, MGMT_USER," \
|
||||
" MGMT_DOMAIN, ACME_EMAIL, WAN_IFACE, LAN_IFACES." \
|
||||
" USER_NAME, INSTALL_DIR, MGMT_PASS, MGMT_USER," \
|
||||
" MGMT_DOMAIN, WAN_IFACE, LAN_IFACES." \
|
||||
" CLI flags take precedence over env vars." \
|
||||
"" \
|
||||
"Example (dev):" \
|
||||
" ./install.sh --dev --mgmt-pass pass --acme-email me@example.com" \
|
||||
"Example (dev):" \
|
||||
" ./install.sh --dev --mgmt-pass pass" \
|
||||
"" \
|
||||
"Example (prod):" \
|
||||
" MGMT_PASS=pass ACME_EMAIL=me@example.com ./install.sh --user vacuum-wall"
|
||||
"Example (prod):" \
|
||||
" MGMT_PASS=pass ./install.sh --user vacuum-wall"
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
@@ -74,6 +74,7 @@ REPO_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
|
||||
# Required settings (no defaults — must be provided)
|
||||
MGMT_PASS="${_cli_mgmt_pass:-${MGMT_PASS:-}}"
|
||||
# ACME_EMAIL is optional — will be configured from the WebUI
|
||||
ACME_EMAIL="${_cli_acme_email:-${ACME_EMAIL:-}}"
|
||||
|
||||
# Optional settings with defaults
|
||||
@@ -110,18 +111,16 @@ LAN_IFACES="${_cli_lan_ifaces:-${LAN_IFACES:-}}"
|
||||
|
||||
# --- Validate required settings ---
|
||||
missing=()
|
||||
[[ -z "$MGMT_PASS" ]] && missing+=("MGMT_PASS (--mgmt-pass)")
|
||||
[[ -z "$ACME_EMAIL" ]] && missing+=("ACME_EMAIL (--acme-email)")
|
||||
[[ -z "$MGMT_PASS" ]] && missing+=("MGMT_PASS (--mgmt-pass)")
|
||||
|
||||
if (( ${#missing[@]} )); then
|
||||
echo -e "${RED}[!!]${NC} Missing required settings:"
|
||||
for v in "${missing[@]}"; do
|
||||
case "$v" in
|
||||
"MGMT_PASS (--mgmt-pass)") echo ' export MGMT_PASS="your-password" # or --mgmt-pass';;
|
||||
"ACME_EMAIL (--acme-email)") echo " export ACME_EMAIL=\"you@example.com\" # or --acme-email";;
|
||||
"MGMT_PASS (--mgmt-pass)") echo ' export MGMT_PASS="your-password" # or --mgmt-pass';;
|
||||
esac
|
||||
done
|
||||
printf '\nTo run: MGMT_PASS=pass ACME_EMAIL=you@example.com ./install.sh\n'
|
||||
printf '\nTo run: MGMT_PASS=pass ./install.sh\n'
|
||||
exit 1
|
||||
fi
|
||||
ACME_HOME="$PROJECT_DIR/data/acme"
|
||||
@@ -616,20 +615,23 @@ nginx -t 2>/dev/null && nginx -s reload 2>/dev/null && log "Reloaded nginx" || \
|
||||
systemctl restart nginx >/dev/null 2>&1 && log "Restarted nginx" || \
|
||||
warn "Could not restart nginx (check config)"
|
||||
|
||||
# --- 14. Configure acme.sh default email ---
|
||||
if [[ -f "$ACME_HOME/account.conf" ]] && grep -q '^ACME_LEEMAIL=' "$ACME_HOME/account.conf" 2>/dev/null; then
|
||||
log "acme.sh account already registered, skipping."
|
||||
# --- 14. Write initial ACME config (skip if user has customized it) ---
|
||||
ACME_CFG="${PROJECT_DIR}/config/acme/config.json"
|
||||
if [[ -f "$ACME_CFG" ]]; then
|
||||
log "ACME config already exists, skipping."
|
||||
else
|
||||
# acme.sh must never run as root — always as the service user via sudo -u.
|
||||
# This prevents acme.sh from running any command as root and limits its
|
||||
# ability to modify system files.
|
||||
log "Registering acme.sh account with email $ACME_EMAIL..."
|
||||
mkdir -p "$ACME_HOME/www"
|
||||
chown "$USER_DAEMON_NAME:$USER_GROUP" "$ACME_HOME/www"
|
||||
sudo -u "$USER_DAEMON_NAME" env ACME_HOME="$ACME_HOME" HOME="$PROJECT_DIR" \
|
||||
"$ACME_HOME/acme.sh" --home "$ACME_HOME" --config-home "$ACME_HOME" \
|
||||
--register-account -m "$ACME_EMAIL" 2>/dev/null || \
|
||||
warn "Could not register acme.sh account (will be done from WebUI)"
|
||||
log "Writing initial ACME configuration..."
|
||||
mkdir -p "${PROJECT_DIR}/config/acme"
|
||||
ACME_EMAIL="$ACME_EMAIL" \
|
||||
ACME_CFG="$ACME_CFG" \
|
||||
"${PROJECT_DIR}/.venv/bin/python3" -c "
|
||||
import json, os
|
||||
cfg = {'email': os.environ.get('ACME_EMAIL', '') or ''}
|
||||
with open(os.environ['ACME_CFG'], 'w') as f:
|
||||
json.dump(cfg, f, indent=4)
|
||||
f.write('\n')
|
||||
"
|
||||
log "ACME config written (register via WebUI to activate)"
|
||||
fi
|
||||
|
||||
# --- Done ---
|
||||
@@ -657,10 +659,11 @@ else
|
||||
fi
|
||||
echo ""
|
||||
echo " Next steps:"
|
||||
echo " 1. Verify zone assignments at https://$DOMAIN/interfaces"
|
||||
echo " 2. Configure DHCP ranges for your LAN"
|
||||
echo " 3. Add proxy domains with ACME certificates"
|
||||
echo " 4. Set up WireGuard tunnel (optional)"
|
||||
echo " 1. Set ACME contact email at https://$DOMAIN/certs/settings"
|
||||
echo " 2. Verify zone assignments at https://$DOMAIN/interfaces"
|
||||
echo " 3. Configure DHCP ranges for your LAN"
|
||||
echo " 4. Add proxy domains with ACME certificates"
|
||||
echo " 5. Set up WireGuard tunnel (optional)"
|
||||
echo ""
|
||||
echo " NOTE: A self-signed certificate was generated."
|
||||
echo " From the WebUI, issue a real certificate for $DOMAIN"
|
||||
|
||||
Reference in New Issue
Block a user