fix: ACME ownership self-heal + daily timer, apply-all force, firewall baseline re-stamp
acme:
- acme.sh chmods its tree to owner-only (700/600) every run, which
broke the two-user model: a tree left owner-only by one user made
every acme.sh call of the other exit 2
- normalize_acme_home() reopens group access (sudo chmod g+rwX,
files only — setgid dirs trip RestrictSUIDSGID); _run_acme_preflight
is the choke point before every daemon acme.sh call + startup
- acme service now runs as the daemon user; --log persists the raw CA
transcript; SYS_LOG=6 journals manual issue/renew runs
- timer daily-only: two runs/day landed inside ZeroSSL's 24h
validation backoff (Retry-After: 86400) — a permanent renewal lockout
- _collect_acme no longer raises on cert-list failure; reports
status.error (AcmeState.status) so the certs page can surface it
firewall: re-stamp the applied baseline on live zone mutations
(interfaces/services/rich-rules/masquerade/forward-ports) so cancel-all
reverts to post-mutation state, not a stale install-era snapshot;
set_masquerade syncs the declarative config for existing zones;
add_forward_port records toaddr only with toport
status: apply-all accepts {"force": true} (forwarded to the firewall
apply only); ApplyConfirm force checkbox; applyResultToasts() — the
errors map wins over the 200; ActionButton checks errors before the
success toast; dashboard uses ApplyConfirm
system_import: drift re-imports carry the existing apply-meta; first
import stamps the adopted content as applied (it is the running state)
— no phantom pending changes
nginx: get_config only re-saves when migration actually changed the
config (no more owner/mtime churn on every read)
install: repair mis-owned top-level system dirs (tmpfiles
unsafe-path-transition), warn with a full-repair command for deeper
mis-ownership
daemon/server: loop.get_exception_handler() (aiohttp API fix)
tests: 888 pytest + 24 node passing; ruff clean
This commit is contained in:
@@ -15,18 +15,36 @@ from daemon.handlers.network import (
|
||||
save_interface,
|
||||
set_sysctl,
|
||||
)
|
||||
from lib import dnsmasq as _dm
|
||||
from lib import firewall as _fw
|
||||
from lib import network as _net
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def tmp_network(tmp_path):
|
||||
orig_config = _net.CONFIG_FILE
|
||||
orig_data = _net.DATA_DIR
|
||||
# Handler endpoints emit "networkd" sync events; the subscribers
|
||||
# (lib.sync.NetworkToAllSync) read/write the firewall and dnsmasq
|
||||
# configs, and apply_all re-stamps the dnsmasq config. Point all of
|
||||
# those paths at tmp so tests never touch the real config files.
|
||||
orig_net = (_net.CONFIG_FILE, _net.DATA_DIR)
|
||||
orig_dm = (_dm.CONFIG_DIR, _dm.DATA_DIR, _dm.CONFIG_PATH, _dm.FRAGMENTS_DIR)
|
||||
orig_fw = (_fw.CONFIG_DIR, _fw.CONFIG_FILE)
|
||||
_net.CONFIG_FILE = tmp_path / "config" / "network" / "config.json"
|
||||
_net.DATA_DIR = tmp_path / "data" / "networkd"
|
||||
_dm.CONFIG_DIR = tmp_path / "config" / "dnsmasq"
|
||||
_dm.DATA_DIR = tmp_path / "data" / "dnsmasq"
|
||||
_dm.CONFIG_PATH = _dm.CONFIG_DIR / "config.json"
|
||||
_dm.FRAGMENTS_DIR = _dm.DATA_DIR / "fragments"
|
||||
_dm.CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
||||
_dm.DATA_DIR.mkdir(parents=True, exist_ok=True)
|
||||
_dm.FRAGMENTS_DIR.mkdir(parents=True, exist_ok=True)
|
||||
_fw.CONFIG_DIR = tmp_path / "config" / "firewall"
|
||||
_fw.CONFIG_FILE = _fw.CONFIG_DIR / "config.json"
|
||||
_fw.CONFIG_DIR.mkdir(parents=True, exist_ok=True)
|
||||
yield tmp_path
|
||||
_net.CONFIG_FILE = orig_config
|
||||
_net.DATA_DIR = orig_data
|
||||
_net.CONFIG_FILE, _net.DATA_DIR = orig_net
|
||||
_dm.CONFIG_DIR, _dm.DATA_DIR, _dm.CONFIG_PATH, _dm.FRAGMENTS_DIR = orig_dm
|
||||
_fw.CONFIG_DIR, _fw.CONFIG_FILE = orig_fw
|
||||
|
||||
|
||||
# =================================================================
|
||||
@@ -363,7 +381,7 @@ class TestInferEndpoints:
|
||||
|
||||
|
||||
class TestSetSysctl:
|
||||
def test_set_sysctl_success(self):
|
||||
def test_set_sysctl_success(self, tmp_network):
|
||||
with (
|
||||
patch("daemon.handlers.network.run") as mock_run,
|
||||
patch.object(Path, "read_text", return_value="1"),
|
||||
|
||||
Reference in New Issue
Block a user