refactor: unify project structure, improve security, and enhance deployment

- Fix WireGuard private key leak in API responses and config updates
- Update systemd service to serve from repo root with adjusted sandbox
- Add CLI flags, idempotency, and dev mode to install.sh
- Extract common utilities to lib/common.py and webui/api/common.py
- Migrate frontend to htmx for simpler, more maintainable UI
- Update docs to reflect current architecture and deployment model
- Vendor htmx dependencies per project requirements
This commit is contained in:
2026-05-25 00:53:32 +00:00
parent 8829ac579d
commit d1ab717c0f
36 changed files with 857 additions and 626 deletions
+42 -5
View File
@@ -127,10 +127,12 @@ class TestFirewallRichRules:
assert resp.status_code == 400
@patch("webui.api.firewall.get_rich_rules")
@patch("webui.api.firewall.config_get")
@patch("webui.api.firewall.get_config")
def test_list(self, mock_cfg, mock_list, client):
mock_list.return_value = ["rule1"]
mock_cfg.return_value = {"zones": {"public": {"rich_rules": [{"id": "a1", "rule": "rule1"}]}}}
mock_cfg.return_value = {
"zones": {"public": {"rich_rules": [{"id": "a1", "rule": "rule1"}]}}
}
resp = client.get("/api/firewall/rich-rules/public")
assert resp.status_code == 200
data = resp.get_json()
@@ -250,7 +252,7 @@ class TestDhcpApply:
class TestDhcpStatus:
@patch("lib.dnsmasq.get_status")
@patch("webui.api.dhcp.dnsmasq_status")
def test_success(self, mock_status, client):
mock_status.return_value = {"service_active": True}
resp = client.get("/api/dhcp/status")
@@ -469,6 +471,38 @@ class TestWireguardConfig:
resp = client.post("/api/wireguard/config", json={"peers": {}})
assert resp.status_code == 200
@patch("webui.api.wireguard.save_config")
@patch("webui.api.wireguard.get_config")
def test_post_strips_private_key(self, mock_get, mock_save, client):
mock_get.return_value = {
"interface": {"name": "wg0", "private_key": "existing"},
"peers": {},
}
mock_save.return_value = None
resp = client.post(
"/api/wireguard/config",
json={"interface": {"name": "wg0", "private_key": "secret"}, "peers": {}},
)
assert resp.status_code == 200
saved = mock_save.call_args[0][0]
assert saved["interface"]["private_key"] == "existing"
@patch("webui.api.wireguard.save_config")
@patch("webui.api.wireguard.get_config")
def test_patch_strips_private_key(self, mock_get, mock_save, client):
mock_get.return_value = {
"interface": {"name": "wg0", "private_key": "existing"},
"peers": {},
}
mock_save.return_value = None
resp = client.patch(
"/api/wireguard/config",
json={"interface": {"name": "wg1", "private_key": "injected"}},
)
assert resp.status_code == 200
saved = mock_save.call_args[0][0]
assert saved.get("interface", {}).get("private_key") == "existing"
class TestWireguardPeers:
@patch("webui.api.wireguard.get_peers")
@@ -479,7 +513,10 @@ class TestWireguardPeers:
@patch("webui.api.wireguard.add_peer")
def test_add(self, mock_add, client):
mock_add.return_value = {"name": "client1", "public_key": "pub", "private_key": "priv"}
mock_add.return_value = {
"name": "client1",
"public_key": "pub",
}
resp = client.post(
"/api/wireguard/peers",
json={"name": "client1"},
@@ -574,4 +611,4 @@ class TestResponseHelpers:
data = resp.get_json()
assert "error" in data
assert "ok" in data
assert data["ok"] is False
assert data["ok"] is False