feat: add system config import, refactor install script and nginx auth

- lib/system_import: new module to import system configs into JSON at daemon startup
- daemon/server.py: call import_all() during startup for config reconciliation
- daemon/handlers/nginx.py: simplify add_domain auth handling, remove duplicate code
- scripts/install.sh: replace inline Python setup with curl-based daemon API calls; apply IP forwarding at runtime
- hoover: bump internal asset versions to v=8
- pages: bump asset versions to v=9
This commit is contained in:
2026-07-08 02:20:28 +00:00
parent fb39af126a
commit 5135de0921
13 changed files with 1633 additions and 144 deletions
+65 -99
View File
@@ -277,11 +277,16 @@ render_template "${PROJECT_DIR}/system/systemd/vacuum-wall-acme.service" \
install -m 0644 "${PROJECT_DIR}/system/systemd/vacuum-wall-acme.timer" /etc/systemd/system/vacuum-wall-acme.timer
systemctl daemon-reload
# --- 7. Enable IP forwarding (persistent via sysctl.conf) ---
# --- 7. Enable IP forwarding (persistent via sysctl.conf + runtime apply) ---
log "Enabling IP forwarding..."
if ! grep -q "^net.ipv4.ip_forward=1" /etc/sysctl.conf 2>/dev/null; then
echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
fi
# Apply immediately so NAT works without reboot
if [ "$(cat /proc/sys/net/ipv4/ip_forward 2>/dev/null)" != "1" ]; then
sysctl -w net.ipv4.ip_forward=1 >/dev/null 2>&1 && log "IP forwarding enabled at runtime" || \
warn "Could not enable IP forwarding at runtime"
fi
# --- 8. Detect network interfaces ---
log "Detecting network interfaces..."
@@ -348,108 +353,73 @@ else
chown "$USER_DAEMON_NAME:$USER_GROUP" "$_SOCKET" 2>/dev/null || true
chmod 0660 "$_SOCKET" 2>/dev/null || true
# --- 10. Configure subsystems via daemon API ---
log "Configuring subsystems via daemon API..."
WAN_IFACE="$WAN_IFACE" \
LAN_IFACES="$LAN_IFACES" \
MGMT_DOMAIN="$DOMAIN" \
MGMT_USER="$MGMT_USER" \
MGMT_PASS="$MGMT_PASS" \
"${PROJECT_DIR}/.venv/bin/python3" -c "
import daemon.client as c
from daemon.iface import (
POST_ACME_SELF_SIGNED, POST_NGINX_DOMAINS_ADD, POST_NGINX_APPLY,
POST_FIREWALL_CONFIG, POST_FIREWALL_CONFIG_APPLY, POST_NETWORK_SYSCTL_SET,
GET_NETWORK_INFER_DHCP_RANGES,
)
import sys
echo ""
echo " Setting up initial management configuration..."
domain = '${DOMAIN}'
mgmt_user = '${MGMT_USER}'
mgmt_pass = '${MGMT_PASS}'
wan_iface = '${WAN_IFACE}'
lan_ifaces = '${LAN_IFACES}'
# htpasswd is created by the daemon via /nginx/domains/add (writes to data/.htpasswd)
# Self-signed cert for management domain
try:
res = c.post(POST_ACME_SELF_SIGNED, {'domain': domain, 'days': 365})
print(f' [cert] Self-signed: {\"generated\" if res.get(\"generated\") else \"exists\"}')
except Exception as e:
print(f' [cert] Warning: {e}', file=sys.stderr)
# Helper: POST JSON to daemon API over Unix socket
_daemon_post() {
local endpoint="$1"
local json="$2"
local label="${3:-POST $endpoint}"
local resp
if resp=$(curl -s -f --unix-socket "$_SOCKET" \
"http://localhost${endpoint}" \
-H "Content-Type: application/json" \
-d "$json" 2>&1); then
log "$label"
return 0
else
warn "$label: $resp"
return 1
fi
}
# Management proxy domain + htpasswd
try:
c.post(POST_NGINX_DOMAINS_ADD, {
'domain': domain,
'paths': {
'/': {
'backend': {'host': '127.0.0.1', 'port': 9090, 'proto': 'http'},
'is_management': True,
},
'/ws': {
'backend': {'host': '127.0.0.1', 'port': 9091, 'proto': 'http'},
'is_websocket': True,
},
# 1A. Self-signed certificate
_daemon_post "/acme/self-signed" "{\"domain\":\"$DOMAIN\"}" "Self-signed certificate"
# 1C. Management proxy domain
_daemon_post "/nginx/domains/add" "$(jq -n \
--arg domain "$DOMAIN" \
--arg user "$MGMT_USER" \
--arg pass "$MGMT_PASS" \
'{
domain: $domain,
paths: {
"/": {
backend: {host: "127.0.0.1", port: 9090, proto: "http"},
is_management: true
},
"/ws": {
backend: {host: "127.0.0.1", port: 9091, proto: "http"},
is_websocket: true
}
},
'auth_user': mgmt_user,
'auth_pass': mgmt_pass,
})
c.post(POST_NGINX_APPLY)
print(f' [proxy] Management proxy configured for {domain}')
except Exception as e:
print(f' [proxy] Warning: {e}', file=sys.stderr)
auth_user: $user,
auth_pass: $pass
}')" "Management domain configured"
# Firewall config (interface detection done in bash above)
import json as _json
zones = {}
_daemon_post "/nginx/apply" "{}" "Nginx config applied"
if wan_iface:
zones['public'] = {
'target': 'DEFAULT',
'interfaces': [i for i in wan_iface.split(',') if i],
'services': ['http', 'https', 'ssh'],
'masquerade': True,
}
# Firewall zone assignment
if [[ -n "$WAN_IFACE" ]]; then
_daemon_post "/firewall/zones/interfaces" \
"$(jq -n --arg zone "public" --arg iface "$WAN_IFACE" \
'{zone: $zone, interfaces: [$iface]}')" \
"WAN interface assigned to public zone"
fi
if lan_ifaces:
zones['internal'] = {
'target': 'ACCEPT',
'interfaces': [i for i in lan_ifaces.split(',') if i],
'services': ['dhcp', 'dns', 'ntp'],
'masquerade': False,
}
if [[ -n "$LAN_IFACES" ]]; then
# Convert comma-separated list to JSON array
LAN_JSON=$(echo "$LAN_IFACES" | tr ',' '\n' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | jq -R . | jq -s '.')
_daemon_post "/firewall/zones/interfaces" \
"$(jq -n --arg zone "internal" --argjson ifaces "$LAN_JSON" \
'{zone: $zone, interfaces: $ifaces}')" \
"LAN interfaces assigned to internal zone"
fi
# Always create vpn zone skeleton for later WireGuard setup
zones['vpn'] = {
'target': 'ACCEPT',
'interfaces': [],
'services': [],
'masquerade': False,
}
try:
c.post(POST_FIREWALL_CONFIG, {'zones': zones})
c.post(POST_FIREWALL_CONFIG_APPLY)
print(' [firewall] Zones configured and applied')
except Exception as e:
print(f' [firewall] Warning: {e}', file=sys.stderr)
# IP forwarding
try:
c.post(POST_NETWORK_SYSCTL_SET, {'name': 'net.ipv4.ip_forward', 'value': '1'})
print(' [network] IP forwarding enabled')
except Exception as e:
print(f' [network] Warning: {e}', file=sys.stderr)
# Infer DHCP ranges (logged for user reference)
try:
ranges = c.get(GET_NETWORK_INFER_DHCP_RANGES)
for iface, rng in ranges.get('ranges', {}).items():
print(f' [suggestion] DHCP range for {iface}: {rng.get(\"start\")}-{rng.get(\"end\")}')
except Exception:
pass
"
log "Subsystem configuration complete"
unset _daemon_post
fi
systemctl start vacuum-wall >/dev/null 2>&1 && log "Started vacuum-wall WebUI" || warn "Could not start vacuum-wall WebUI"
@@ -489,7 +459,3 @@ 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"
echo " when DNS points to this appliance."
echo ""