fix: install.sh loop abort, ACME poll sudo gate, /static/ sub-paths
- install.sh: the traversal-chmod loop assigned _d but looped over the never-set $d; under set -u every fresh install aborted with "d: unbound variable" at that line. Loop over $_d. - acme collector: the self-heal normalize (sudo chmod g+rwX) now runs only when a no-sudo group-read-bit probe detects a lost bit — acme.sh re-hardens the tree 600 on every run, so the steady-state poll makes no sudo call. The group bit (not daemon readability) is what the two-user model keeps for the WebUI user. - lib.acme: new get_acme_home() accessor (ACME_HOME env, default data/acme), reused by _run_acme; _summarize_acme_output preserves a "Permission denied" line even when it is not among the final two, so the collector's actionable-error matcher keeps firing. - nginx template: emit location /static/ for any is_management path (not only '/'); the SPA references /static/... at the domain root regardless of the management backend path. - tests: probe, summarizer, and nginx-subpath cases in test_state.py, test_acme.py, test_nginx.py.
This commit is contained in:
@@ -3,9 +3,11 @@
|
||||
import logging
|
||||
import os
|
||||
from pathlib import Path
|
||||
from stat import S_IRGRP
|
||||
from typing import Any
|
||||
|
||||
from lib import schema
|
||||
from lib.acme import get_acme_home
|
||||
from lib.common import load_json
|
||||
from lib.state import PROJECT_DIR, _now_iso, register_collector
|
||||
|
||||
@@ -157,6 +159,25 @@ def _friendly_acme_error(exc: Exception) -> str:
|
||||
return text
|
||||
|
||||
|
||||
def _acme_home_needs_normalize() -> bool:
|
||||
"""Cheap no-sudo probe: has any ACME_HOME file lost its group-read bit?
|
||||
|
||||
acme.sh re-hardens its tree (``chmod 600``) on every run, so the daemon's
|
||||
self-heal (``normalize_acme_home``) is only needed after a run by another
|
||||
user (e.g. a manual run as the WebUI user) stripped group read. The probe
|
||||
checks the group bit — not the daemon's own readability — because group
|
||||
read is what the two-user model keeps for the WebUI user; a file the
|
||||
daemon can read but the group cannot must still be healed.
|
||||
"""
|
||||
try:
|
||||
for p in get_acme_home().rglob("*"):
|
||||
if p.is_file() and not (p.stat().st_mode & S_IRGRP):
|
||||
return True
|
||||
except OSError:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _collect_acme() -> schema.AcmeState:
|
||||
"""Collect ACME certificate list and email.
|
||||
|
||||
@@ -171,17 +192,19 @@ def _collect_acme() -> schema.AcmeState:
|
||||
# `status.error` so the poll diff still detects recovery.
|
||||
cert_error: str | None = None
|
||||
try:
|
||||
# Self-heal ACME_HOME permissions before listing, exactly like the
|
||||
# handler preflight (_run_acme_preflight). acme.sh dot-sources
|
||||
# account.conf on startup; a prior run by another user (e.g. a manual
|
||||
# run as the WebUI user) can leave it owner-only and make `--list`
|
||||
# exit 2. The startup normalize only covers the first collection, so
|
||||
# the poll must normalize too or a mid-lifetime ownership flip would
|
||||
# blank the cert list until the next issue/renew or daemon restart.
|
||||
# Self-heal ACME_HOME permissions before listing, but only when the
|
||||
# probe detects a lost group-read bit — the steady-state poll then
|
||||
# makes no sudo call. acme.sh dot-sources account.conf on startup; a
|
||||
# prior run by another user (e.g. a manual run as the WebUI user) can
|
||||
# leave it owner-only and make `--list` exit 2. The startup normalize
|
||||
# only covers the first collection, so the poll must probe too or a
|
||||
# mid-lifetime ownership flip would blank the cert list until the
|
||||
# next issue/renew or daemon restart.
|
||||
from daemon.handlers.acme import normalize_acme_home
|
||||
from lib.acme import list_certs
|
||||
|
||||
normalize_acme_home()
|
||||
if _acme_home_needs_normalize():
|
||||
normalize_acme_home()
|
||||
certs = list_certs()
|
||||
except Exception as exc:
|
||||
logger.warning("ACME state collection failed", exc_info=True)
|
||||
|
||||
Reference in New Issue
Block a user