From e109993a3254f0a19002e9682d5be8e82f1ce560 Mon Sep 17 00:00:00 2001 From: missytake Date: Tue, 19 Aug 2025 10:46:23 +0200 Subject: [PATCH 1/2] www: introduce www_folder config item fix #529 --- CHANGELOG.md | 3 +++ README.md | 12 ++++++++++++ chatmaild/src/chatmaild/config.py | 1 + cmdeploy/src/cmdeploy/__init__.py | 26 +++++++++++++++++++------- cmdeploy/src/cmdeploy/www.py | 5 ++++- 5 files changed, 39 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4727133f..da339afc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## untagged +- Make www upload path configurable + ([#618](https://github.com/chatmail/relay/pull/618)) + - Check whether GCC is installed in initenv.sh ([#608](https://github.com/chatmail/relay/pull/608)) diff --git a/README.md b/README.md index 7ba08fdb..9f60d113 100644 --- a/README.md +++ b/README.md @@ -255,6 +255,18 @@ This starts a local live development cycle for chatmail web pages: - Starts a browser window automatically where you can "refresh" as needed. +#### Custom web pages + +You can skip uploading a web page +by setting `www_folder=disabled` in `chatmail.ini`. + +If you want to manage your web pages outside this git repository, +you can set `www_folder` in `chatmail.ini` to a custom directory on your computer. +`cmdeploy run` will upload it as the server's home page, +and if it contains a `src/index.md` file, +will build it with hugo. + + ## Mailbox directory layout Fresh chatmail addresses have a mailbox directory that contains: diff --git a/chatmaild/src/chatmaild/config.py b/chatmaild/src/chatmaild/config.py index 0cac3ea4..ec323142 100644 --- a/chatmaild/src/chatmaild/config.py +++ b/chatmaild/src/chatmaild/config.py @@ -33,6 +33,7 @@ def __init__(self, inipath, params): self.password_min_length = int(params["password_min_length"]) self.passthrough_senders = params["passthrough_senders"].split() self.passthrough_recipients = params["passthrough_recipients"].split() + self.www_folder = params.get("www_folder") self.filtermail_smtp_port = int(params["filtermail_smtp_port"]) self.filtermail_smtp_port_incoming = int( params["filtermail_smtp_port_incoming"] diff --git a/cmdeploy/src/cmdeploy/__init__.py b/cmdeploy/src/cmdeploy/__init__.py index 9a4e4229..6aa7c5b0 100644 --- a/cmdeploy/src/cmdeploy/__init__.py +++ b/cmdeploy/src/cmdeploy/__init__.py @@ -11,7 +11,7 @@ from pathlib import Path from chatmaild.config import Config, read_config -from pyinfra import facts, host +from pyinfra import facts, host, logger from pyinfra.api import FactBase from pyinfra.facts.files import File from pyinfra.facts.server import Sysctl @@ -751,12 +751,24 @@ def deploy_chatmail(config_path: Path, disable_mail: bool) -> None: packages=["fcgiwrap"], ) - www_path = importlib.resources.files(__package__).joinpath("../../../www").resolve() - - build_dir = www_path.joinpath("build") - src_dir = www_path.joinpath("src") - build_webpages(src_dir, build_dir, config) - files.rsync(f"{build_dir}/", "/var/www/html", flags=["-avz"]) + reporoot = importlib.resources.files(__package__).joinpath("../../../").resolve() + www_path = Path(config.www_folder) + # if www_folder was not set, use default directory + if not config.www_folder: + www_path = reporoot.joinpath("www") + # if www_folder was set to a non-existing folder, skip upload + if not www_path.is_dir(): + logger.warning("Building web pages is disabled in chatmail.ini, skipping") + else: + build_dir = www_path.joinpath("build") + src_dir = www_path.joinpath("src") + # if www_folder is a hugo page, build it + if src_dir.joinpath("index.md").is_file(): + build_webpages(src_dir, build_dir, config) + # if it is not a hugo page, upload it as is + else: + build_dir = www_path + files.rsync(f"{build_dir}/", "/var/www/html", flags=["-avz"]) _install_remote_venv_with_chatmaild(config) debug = False diff --git a/cmdeploy/src/cmdeploy/www.py b/cmdeploy/src/cmdeploy/www.py index 9dd404ba..253cc527 100644 --- a/cmdeploy/src/cmdeploy/www.py +++ b/cmdeploy/src/cmdeploy/www.py @@ -3,6 +3,7 @@ import time import traceback import webbrowser +from pathlib import Path import markdown from chatmaild.config import read_config @@ -106,7 +107,9 @@ def main(): config = read_config(inipath) config.webdev = True assert config.mail_domain - www_path = reporoot.joinpath("www") + www_path = Path(config.www_folder) + if not config.www_folder: + www_path = reporoot.joinpath("www") src_path = www_path.joinpath("src") stats = None build_dir = www_path.joinpath("build") From ef1c3d2ec25694c9b62e1afef6d4381781cd85de Mon Sep 17 00:00:00 2001 From: missytake Date: Tue, 19 Aug 2025 12:39:58 +0200 Subject: [PATCH 2/2] www: make www_folder behavior testable --- chatmaild/src/chatmaild/config.py | 2 +- cmdeploy/src/cmdeploy/__init__.py | 18 +++-------- cmdeploy/src/cmdeploy/tests/test_cmdeploy.py | 27 ++++++++++++++++ cmdeploy/src/cmdeploy/www.py | 34 +++++++++++++------- 4 files changed, 55 insertions(+), 26 deletions(-) diff --git a/chatmaild/src/chatmaild/config.py b/chatmaild/src/chatmaild/config.py index ec323142..ae5f4423 100644 --- a/chatmaild/src/chatmaild/config.py +++ b/chatmaild/src/chatmaild/config.py @@ -33,7 +33,7 @@ def __init__(self, inipath, params): self.password_min_length = int(params["password_min_length"]) self.passthrough_senders = params["passthrough_senders"].split() self.passthrough_recipients = params["passthrough_recipients"].split() - self.www_folder = params.get("www_folder") + self.www_folder = params.get("www_folder", "") self.filtermail_smtp_port = int(params["filtermail_smtp_port"]) self.filtermail_smtp_port_incoming = int( params["filtermail_smtp_port_incoming"] diff --git a/cmdeploy/src/cmdeploy/__init__.py b/cmdeploy/src/cmdeploy/__init__.py index 6aa7c5b0..4523be1d 100644 --- a/cmdeploy/src/cmdeploy/__init__.py +++ b/cmdeploy/src/cmdeploy/__init__.py @@ -618,7 +618,7 @@ def deploy_chatmail(config_path: Path, disable_mail: bool) -> None: check_config(config) mail_domain = config.mail_domain - from .www import build_webpages + from .www import build_webpages, get_paths server.group(name="Create vmail group", group="vmail", system=True) server.user(name="Create vmail user", user="vmail", group="vmail", system=True) @@ -751,24 +751,16 @@ def deploy_chatmail(config_path: Path, disable_mail: bool) -> None: packages=["fcgiwrap"], ) - reporoot = importlib.resources.files(__package__).joinpath("../../../").resolve() - www_path = Path(config.www_folder) - # if www_folder was not set, use default directory - if not config.www_folder: - www_path = reporoot.joinpath("www") + www_path, src_dir, build_dir = get_paths(config) # if www_folder was set to a non-existing folder, skip upload if not www_path.is_dir(): logger.warning("Building web pages is disabled in chatmail.ini, skipping") else: - build_dir = www_path.joinpath("build") - src_dir = www_path.joinpath("src") # if www_folder is a hugo page, build it - if src_dir.joinpath("index.md").is_file(): - build_webpages(src_dir, build_dir, config) + if build_dir: + www_path = build_webpages(src_dir, build_dir, config) # if it is not a hugo page, upload it as is - else: - build_dir = www_path - files.rsync(f"{build_dir}/", "/var/www/html", flags=["-avz"]) + files.rsync(f"{www_path}/", "/var/www/html", flags=["-avz"]) _install_remote_venv_with_chatmaild(config) debug = False diff --git a/cmdeploy/src/cmdeploy/tests/test_cmdeploy.py b/cmdeploy/src/cmdeploy/tests/test_cmdeploy.py index 3084c8ec..bdbc3db0 100644 --- a/cmdeploy/src/cmdeploy/tests/test_cmdeploy.py +++ b/cmdeploy/src/cmdeploy/tests/test_cmdeploy.py @@ -1,8 +1,10 @@ +import importlib import os import pytest from cmdeploy.cmdeploy import get_parser, main +from cmdeploy.www import get_paths @pytest.fixture(autouse=True) @@ -27,3 +29,28 @@ def test_init_not_overwrite(self, capsys): assert main(["init", "chat.example.org"]) == 1 out, err = capsys.readouterr() assert "path exists" in out.lower() + + +def test_www_folder(example_config, tmp_path): + reporoot = importlib.resources.files(__package__).joinpath("../../../../").resolve() + assert not example_config.www_folder + www_path, src_dir, build_dir = get_paths(example_config) + assert www_path.absolute() == reporoot.joinpath("www").absolute() + assert src_dir == reporoot.joinpath("www").joinpath("src") + assert build_dir == reporoot.joinpath("www").joinpath("build") + example_config.www_folder = "disabled" + www_path, _, _ = get_paths(example_config) + assert not www_path.is_dir() + example_config.www_folder = str(tmp_path) + www_path, src_dir, build_dir = get_paths(example_config) + assert www_path == tmp_path + assert not src_dir.exists() + assert not build_dir + src_path = tmp_path.joinpath("src") + os.mkdir(src_path) + with open(src_path / "index.md", "w") as f: + f.write("# Test") + www_path, src_dir, build_dir = get_paths(example_config) + assert www_path == tmp_path + assert src_dir == src_path + assert build_dir == tmp_path.joinpath("build") diff --git a/cmdeploy/src/cmdeploy/www.py b/cmdeploy/src/cmdeploy/www.py index 253cc527..c013d741 100644 --- a/cmdeploy/src/cmdeploy/www.py +++ b/cmdeploy/src/cmdeploy/www.py @@ -31,9 +31,25 @@ def prepare_template(source): return render_vars, page_layout -def build_webpages(src_dir, build_dir, config): +def get_paths(config) -> (Path, Path, Path): + reporoot = importlib.resources.files(__package__).joinpath("../../../").resolve() + www_path = Path(config.www_folder) + # if www_folder was not set, use default directory + if config.www_folder == "": + www_path = reporoot.joinpath("www") + src_dir = www_path.joinpath("src") + # if www_folder is a hugo page, build it + if src_dir.joinpath("index.md").is_file(): + build_dir = www_path.joinpath("build") + # if it is not a hugo page, upload it as is + else: + build_dir = None + return www_path, src_dir, build_dir + + +def build_webpages(src_dir, build_dir, config) -> Path: try: - _build_webpages(src_dir, build_dir, config) + return _build_webpages(src_dir, build_dir, config) except Exception: print(traceback.format_exc()) @@ -107,17 +123,11 @@ def main(): config = read_config(inipath) config.webdev = True assert config.mail_domain - www_path = Path(config.www_folder) - if not config.www_folder: - www_path = reporoot.joinpath("www") - src_path = www_path.joinpath("src") - stats = None - build_dir = www_path.joinpath("build") - src_dir = www_path.joinpath("src") - index_path = build_dir.joinpath("index.html") # start web page generation, open a browser and wait for changes - build_webpages(src_dir, build_dir, config) + www_path, src_path, build_dir = get_paths(config) + build_dir = build_webpages(src_path, build_dir, config) + index_path = build_dir.joinpath("index.html") webbrowser.open(str(index_path)) stats = snapshot_dir_stats(src_path) print(f"\nOpened URL: file://{index_path.resolve()}\n") @@ -138,7 +148,7 @@ def main(): changenum += 1 stats = newstats - build_webpages(src_dir, build_dir, config) + build_webpages(src_path, build_dir, config) print(f"[{changenum}] regenerated web pages at: {index_path}") print(f"URL: file://{index_path.resolve()}\n\n") count = 0