Migrate declarative configs to config/ dir and remove hardcoded paths

Replace all hardcoded /home/wall/vacuum-wall paths in lib/ with Path(__file__).resolve()
auto-discovery. Move config files from data/ to config/<subsystem>/config.json.
ACME now uses ACME_HOME env var and data/acme/ for cert storage. Systemd units
and sudoers use {{ USER_NAME }}, {{ PROJECT_DIR }}, {{ ACME_HOME }} Jinja2
template variables for install-time substitution. Remove sys.path.insert boot
strap from test files.
This commit is contained in:
2026-05-14 03:31:13 +00:00
parent 817b7c409c
commit d9797b6dac
16 changed files with 131 additions and 136 deletions
+20 -13
View File
@@ -2,7 +2,7 @@
ACME certificate manager for Vacuum Wall.
Wraps acme.sh to issue, renew, and manage SSL/TLS certificates
from Let's Encrypt (or other ACME providers). acme.sh runs as the
from ACME providers such as ZeroSSL or Let's Encrypt. acme.sh runs as the
vacuum-wall system user; nginx is reloaded via a deploy hook script.
"""
@@ -16,16 +16,17 @@ from pathlib import Path
logger = logging.getLogger(__name__)
PROJECT_DIR = Path(__file__).resolve().parent.parent
_ACME_HOME = PROJECT_DIR / "data" / "acme"
_DEPLOY_HOOK = str(PROJECT_DIR / "system" / "acme-deploy.sh")
_ACME_ENVIRON = {
"HOME": str(Path.home()),
"HOME": str(PROJECT_DIR),
"PATH": os.environ.get(
"PATH", "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
),
}
PROJECT_DIR = Path("/home/wall/vacuum-wall")
_DEPLOY_HOOK = str(PROJECT_DIR / "system" / "acme-deploy.sh")
def _find_acme() -> str:
"""Locate the acme.sh binary on the system.
@@ -41,7 +42,7 @@ def _find_acme() -> str:
FileNotFoundError: If acme.sh cannot be found.
"""
candidates = [
Path.home() / ".acme.sh" / "acme.sh",
_ACME_HOME / "acme.sh",
Path("/usr/local/bin/acme.sh"),
]
@@ -81,12 +82,15 @@ def _run_acme(args: list[str]) -> str:
"""
acme_bin = _find_acme()
# Check for ACME_HOME env var (set by systemd in production)
acme_home_env = os.environ.get("ACME_HOME", str(_ACME_HOME))
cmd: list[str] = [
acme_bin,
"--home",
str(Path.home() / ".acme.sh"),
acme_home_env,
"--config-home",
str(Path.home() / ".acme.sh"),
acme_home_env,
*args,
]
@@ -131,7 +135,8 @@ def set_email(email: str) -> None:
def get_email() -> str:
"""Return the ACME contact email, or '' if none is configured."""
try:
account_conf = Path.home() / ".acme.sh" / "account.conf"
acme_home = Path(os.environ.get("ACME_HOME", str(_ACME_HOME)))
account_conf = acme_home / "account.conf"
if account_conf.is_file():
text = account_conf.read_text()
match = re.search(r"^ACME_LEEMAIL=(.+)$", text, re.MULTILINE)
@@ -246,7 +251,8 @@ def list_certs() -> list[dict]:
certs: list[dict] = []
entries = _parse_list_output(raw)
acme_home = Path.home() / ".acme.sh"
acme_home_env = os.environ.get("ACME_HOME", str(_ACME_HOME))
acme_home = Path(acme_home_env)
for entry in entries:
main = entry["main_domain"]
@@ -393,7 +399,8 @@ def get_cert_paths(domain: str) -> dict:
Returns:
Dict with keys 'cert', 'key', 'ca', 'fullchain' mapped to paths.
"""
acme_home = str(Path.home() / ".acme.sh" / domain)
acme_home_env = os.environ.get("ACME_HOME", str(_ACME_HOME))
acme_home = str(Path(acme_home_env) / domain)
return {
"cert": f"{acme_home}/{domain}.cert",
"key": f"{acme_home}/{domain}.key",
@@ -484,6 +491,6 @@ def _has_auto_renew(domain: str) -> bool:
under ``~/.acme.sh/``; existence of this file means the systemd
timer's ``--cron`` run will pick it up.
"""
acme_home = Path.home() / ".acme.sh"
domain_conf = acme_home / f"{domain}.conf"
acme_home_env = os.environ.get("ACME_HOME", str(_ACME_HOME))
domain_conf = Path(acme_home_env) / f"{domain}.conf"
return bool(domain_conf.is_file())
+4 -2
View File
@@ -15,9 +15,10 @@ from typing import Any
from jinja2 import Environment, FileSystemLoader
PROJECT_DIR = Path("/home/wall/vacuum-wall")
PROJECT_DIR = Path(__file__).resolve().parent.parent
CONFIG_DIR = PROJECT_DIR / "config" / "dnsmasq"
DATA_DIR = PROJECT_DIR / "data" / "dnsmasq"
CONFIG_PATH = DATA_DIR / "config.json"
CONFIG_PATH = CONFIG_DIR / "config.json"
FRAGMENTS_DIR = DATA_DIR / "fragments"
DNSMASQ_CONF = "/etc/dnsmasq.d/vacuum-wall.conf"
LEASE_FILE = "/var/lib/dnsmasq/dnsmasq.leases"
@@ -46,6 +47,7 @@ DEFAULT_CFG: dict[str, Any] = {
def _ensure_dirs() -> None:
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
DATA_DIR.mkdir(parents=True, exist_ok=True)
FRAGMENTS_DIR.mkdir(parents=True, exist_ok=True)
+9 -3
View File
@@ -12,10 +12,11 @@ from pathlib import Path
from jinja2 import Environment, FileSystemLoader
PROJECT_DIR = Path("/home/wall/vacuum-wall")
PROJECT_DIR = Path(__file__).resolve().parent.parent
CONFIG_DIR = PROJECT_DIR / "config" / "nginx"
DATA_DIR = PROJECT_DIR / "data" / "nginx"
SITES_DIR = DATA_DIR / "sites-enabled"
CONFIG_FILE = DATA_DIR / "config.json"
CONFIG_FILE = CONFIG_DIR / "config.json"
INCLUDE_FILE = Path("/etc/nginx/conf.d/vacuum-wall.conf")
SSL_SNIPPET = Path("/etc/nginx/snippets/vacuum-wall-ssl.conf")
HTPASSWD_FILE = DATA_DIR / ".htpasswd"
@@ -53,6 +54,7 @@ DEFAULT_CONFIG = {
def _ensure_dirs():
CONFIG_DIR.mkdir(parents=True, exist_ok=True)
SITES_DIR.mkdir(parents=True, exist_ok=True)
@@ -178,13 +180,15 @@ def generate_server_conf(domain_cfg: dict) -> str:
cert=domain_cfg.get("cert"),
auth=domain_cfg.get("auth"),
is_management=False,
acme_home=str(PROJECT_DIR / "data" / "acme"),
certs_dir=str(PROJECT_DIR / "data" / "certs"),
)
def _generate_management_conf(management: dict) -> str:
tmpl = ENV.get_template("nginx/server_block.conf")
return tmpl.render(
domain=management.get("domain", "wall.lan"),
domain=management.get("domain"),
backend=dict(
management.get("backend", {}), host="127.0.0.1", port=9090, proto="http"
),
@@ -193,6 +197,8 @@ def _generate_management_conf(management: dict) -> str:
cert=None,
auth=management.get("auth"),
is_management=True,
acme_home=str(PROJECT_DIR / "data" / "acme"),
certs_dir=str(PROJECT_DIR / "data" / "certs"),
)
+3 -3
View File
@@ -13,8 +13,8 @@ from pathlib import Path
from jinja2 import Environment, FileSystemLoader
PROJECT_DIR = Path("/home/wall/vacuum-wall")
CONFIG_PATH = str(PROJECT_DIR / "data" / "wireguard" / "config.json")
PROJECT_DIR = Path(__file__).resolve().parent.parent
CONFIG_PATH = str(PROJECT_DIR / "config" / "wireguard" / "config.json")
WG_CONF_PATH = "/etc/wireguard/wg0.conf"
WG_QUICK_BIN = "wg-quick"
WG_BIN = "wg"
@@ -141,7 +141,7 @@ def apply() -> None:
conf_text = generate_conf(cfg)
save_config(cfg) # ensure latest state persisted
local_dir = Path("/home/wall/vacuum-wall/data/wireguard")
local_dir = PROJECT_DIR / "data" / "wireguard"
local_dir.mkdir(parents=True, exist_ok=True)
local_tmp = local_dir / "wg0.conf.tmp"
with open(local_tmp, "w") as f: