refactor: introduce two-user daemon architecture with socket-based communication

- Add daemon/ module with aiohttp server, sync client, and handler registry
- Add daemon/handlers/ for privileged operations (acme, dnsmasq, firewall, logs, nginx, wireguard)
- Add system/acme-deploy.py, vacuum-walld sudoers and systemd service
- Update API routes to use daemon client instead of lib/ directly
- Update lib/, tests/, and webui/ for new architecture
- Update docs and deployment scripts
This commit is contained in:
2026-05-27 23:38:23 +00:00
parent 5ac69dfa7e
commit 200e078bc5
39 changed files with 4671 additions and 1810 deletions
+6 -6
View File
@@ -23,7 +23,7 @@
<tbody id="interface-list">
{% for iface in (interfaces or []) %}
<tr>
<td><strong>{{ iface.get('name', 'unknown') }}</strong></td>
<td><strong>{{ iface.get('display_name', iface.get('name', 'unknown')) }}</strong></td>
<td class="text-muted">{{ iface.get('mac', 'N/A') }}</td>
<td>
{% for ip in iface.get('ips', []) %}
@@ -32,16 +32,16 @@
{% if not iface.get('ips') %}N/A{% endif %}
</td>
<td>
<span class="status-dot {{ 'status-up' if iface.get('state') == 'up' else 'status-down' }}"></span>
{{ 'Up' if iface.get('state') == 'up' else 'Down' }}
<span class="status-dot {{ 'status-up' if iface.get('state') == 'UP' else 'status-down' }}"></span>
{{ 'Up' if iface.get('state') == 'UP' else 'Down' }}
</td>
<td>
{% if zones %}
<select
hx-on::change="fetch('/api/firewall/zones/'+encodeURIComponent(this.value)+'/interfaces',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({interfaces:['{{ iface.name }}']})}).then(r=>{if(!r.ok)throw r}).then(r=>r.ok?(showSuccessToast('{{ iface.name }} assigned to '+this.value),refreshTable('/api/firewall/interfaces',document.getElementById('interface-list'),renderInterfaces)):r.json().then(j=>{throw new Error(j.error||r.statusText)})).catch(e=>{showErrorToast(e.message);this.selectedIndex=0})"
hx-on::change="fetch('/api/firewall/zones/'+encodeURIComponent(this.value)+'/interfaces',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({interfaces:['{{ iface.name }}']})}).then(r=>{if(!r.ok)throw r}).then(r=>r.ok?(showSuccessToast('{{ iface.display_name }} assigned to '+this.value),refreshTable('/api/firewall/interfaces',document.getElementById('interface-list'),renderInterfaces)):r.json().then(j=>{throw new Error(j.error||r.statusText)})).catch(e=>{showErrorToast(e.message);this.selectedIndex=0})"
>
{% for zone in zones %}
<option value="{{ zone.get('name', '') }}" {% if zone.get('name') == iface.get('zone') %}selected{% endif %}>{{ zone.get('name', '') }}</option>
{% for zname in zones %}
<option value="{{ zname }}" {% if zname == iface.get('zone') %}selected{% endif %}>{{ zname }}</option>
{% endfor %}
</select>
{% else %}