diff --git a/cmdeploy/src/cmdeploy/acmetool/__init__.py b/cmdeploy/src/cmdeploy/acmetool/__init__.py index 16f6369f..37fd6f31 100644 --- a/cmdeploy/src/cmdeploy/acmetool/__init__.py +++ b/cmdeploy/src/cmdeploy/acmetool/__init__.py @@ -63,7 +63,7 @@ class AcmetoolDeployer(Deployer): server.shell( name=f"Remove old acmetool desired files for {self.domains[0]}", - commands=[f"rm -f /var/lib/acme/desired/{self.domains[0]}-*"], + commands=[f"rm -f /var/lib/acme/desired/'{self.domains[0]}'-*"], ) files.template( src=importlib.resources.files(__package__).joinpath("desired.yaml.j2"), diff --git a/cmdeploy/src/cmdeploy/dovecot/deployer.py b/cmdeploy/src/cmdeploy/dovecot/deployer.py index 84d42e5f..2fa04351 100644 --- a/cmdeploy/src/cmdeploy/dovecot/deployer.py +++ b/cmdeploy/src/cmdeploy/dovecot/deployer.py @@ -173,6 +173,7 @@ def _configure_dovecot(config: Config, debug: bool = False) -> (bool, bool): key=key, value=65535, persist=True, + _ignore_errors=True, ) timezone_env = files.line( diff --git a/doc/tool/lxc_test_en.md b/doc/tool/lxc_test_en.md new file mode 100644 index 00000000..2d21ecd6 --- /dev/null +++ b/doc/tool/lxc_test_en.md @@ -0,0 +1,42 @@ +# Testing with LXC + +To test the relay setup in a local LXC container (tested on Arch Linux host): + +### 1. Host Preparation +Install the necessary packages: +```bash +sudo pacman -S lxc arch-install-scripts dnsmasq +``` + +### 2. Network Configuration +If your host system has IPv6 disabled, you must disable it for LXC as well to avoid service failures: +Edit `/etc/default/lxc-net` and ensure these lines exist: +```bash +USE_LXC_BRIDGE="true" +LXC_IPV6_ENABLE="false" +LXC_IPV6_NAT="false" +``` +*Note: If port 53 is occupied (e.g., by dnscrypt-proxy), you may need to configure your DNS service to listen only on `127.0.0.1` so LXC's dnsmasq can bind to the bridge.* + +Restart the network: +```bash +sudo systemctl enable --now lxc-net.service +``` + +### 3. Create and Prepare Container +Create a Debian 12 (bookworm) container: +```bash +sudo lxc-create -n test -t download -- -d debian -r bookworm -a amd64 +sudo lxc-start -n test +``` + +Sync your local repository to the container: +```bash +sudo rsync -av --exclude=.git ./ /var/lib/lxc/test/rootfs/relay-ir/ +``` + +### 4. Run Deployment +Attach to the container and run the deployment locally: +```bash +sudo lxc-attach -n test -- bash -c "cd /relay-ir && ./scripts/initenv.sh && ./scripts/cmdeploy run --ssh-host @local --skip-dns-check" +``` diff --git a/doc/tool/lxc_test_fa.md b/doc/tool/lxc_test_fa.md new file mode 100644 index 00000000..b62c62a5 --- /dev/null +++ b/doc/tool/lxc_test_fa.md @@ -0,0 +1,42 @@ +# تست با LXC + +برای تست تنظیمات رله در یک کانتینر محلی LXC (تست شده روی میزبان آرچ لینوکس): + +### ۱. آماده‌سازی میزبان +بسته‌های مورد نیاز را نصب کنید: +```bash +sudo pacman -S lxc arch-install-scripts dnsmasq +``` + +### ۲. پیکربندی شبکه +اگر IPv6 در سیستم میزبان شما غیرفعال است، باید آن را برای LXC نیز غیرفعال کنید تا از بروز خطا جلوگیری شود: +فایل `/etc/default/lxc-net` را ویرایش کرده و مطمین شوید این خطوط وجود دارند: +```bash +USE_LXC_BRIDGE="true" +LXC_IPV6_ENABLE="false" +LXC_IPV6_NAT="false" +``` +*نکته: اگر پورت ۵۳ اشغال است (مثلاً توسط dnscrypt-proxy)، ممکن است لازم باشد سرویس DNS خود را طوری تنظیم کنید که فقط روی `127.0.0.1` گوش دهد تا dnsmasq مربوط به LXC بتواند به بریج متصل شود.* + +شبکه را راه‌اندازی کنید: +```bash +sudo systemctl enable --now lxc-net.service +``` + +### ۳. ساخت و آماده‌سازی کانتینر +یک کانتینر دبیان ۱۲ (bookworm) بسازید: +```bash +sudo lxc-create -n test -t download -- -d debian -r bookworm -a amd64 +sudo lxc-start -n test +``` + +کد محلی خود را به کانتینر منتقل کنید: +```bash +sudo rsync -av --exclude=.git ./ /var/lib/lxc/test/rootfs/relay-ir/ +``` + +### ۴. اجرای استقرار (Deployment) +به کانتینر متصل شده و استقرار را به صورت محلی اجرا کنید: +```bash +sudo lxc-attach -n test -- bash -c "cd /relay-ir && ./scripts/initenv.sh && ./scripts/cmdeploy run --ssh-host @local --skip-dns-check" +``` diff --git a/init.sh b/init.sh index cccdf657..8af36258 100644 --- a/init.sh +++ b/init.sh @@ -38,8 +38,12 @@ echo "--- Initializing environment ---" ./scripts/initenv.sh # 4. Ask for domain and email -read -p "Enter your mail domain (e.g. example.com): " MAIL_DOMAIN -read -p "Enter your email for ACME/Let's Encrypt: " ACME_EMAIL +if [ -z "$MAIL_DOMAIN" ]; then + read -p "Enter your mail domain (e.g. example.com): " MAIL_DOMAIN < /dev/tty +fi +if [ -z "$ACME_EMAIL" ]; then + read -p "Enter your email for ACME/Let's Encrypt: " ACME_EMAIL < /dev/tty +fi # 5. Initialize configuration echo "--- Initializing chatmail configuration ---" diff --git a/test/lxc_runner.py b/test/lxc_runner.py new file mode 100644 index 00000000..7b5f79e7 --- /dev/null +++ b/test/lxc_runner.py @@ -0,0 +1,97 @@ +import subprocess +import sys +import time +import os +import shutil + +# This script automates LXC container creation and relay deployment testing. +# It cleans up old containers, creates a new one, and runs the deployment. + +CONTAINER_PREFIX = "testrelay" +CONTAINER_NAME = f"{CONTAINER_PREFIX}-auto" +RELAY_SRC = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) + +def run_cmd(cmd, check=True, shell=True): + print(f"Executing: {cmd}") + try: + result = subprocess.run(cmd, shell=shell, check=check, capture_output=True, text=True) + return result.stdout.strip() + except subprocess.CalledProcessError as e: + print(f"Error executing command: {e}") + print(f"Stdout: {e.stdout}") + print(f"Stderr: {e.stderr}") + if check: + sys.exit(1) + return None + +def cleanup(): + print("--- Cleaning up old containers ---") + containers = run_cmd("sudo lxc-ls -1").splitlines() + for c in containers: + if c.startswith(CONTAINER_PREFIX): + print(f"Destroying container: {c}") + run_cmd(f"sudo lxc-destroy -n {c} -f", check=False) + +def create_container(): + print(f"--- Creating container: {CONTAINER_NAME} ---") + run_cmd(f"sudo lxc-create -n {CONTAINER_NAME} -t download -- -d debian -r bookworm -a amd64") + +def run_in_container(name, command): + return run_cmd(f'sudo lxc-attach -n {name} -- bash -c "{command}"') + +def prepare_container(): + print("--- Preparing container ---") + run_cmd(f"sudo lxc-start -n {CONTAINER_NAME}") + + # Wait for IP + print("Waiting for container IP...") + for _ in range(30): + ip = run_cmd(f"sudo lxc-info -n {CONTAINER_NAME} -i").split() + if ip and len(ip) > 1: + print(f"Container IP: {ip[1]}") + break + time.sleep(1) + else: + print("Failed to get container IP.") + sys.exit(1) + + # Disable systemd-resolved to free port 53 + run_in_container(CONTAINER_NAME, "systemctl disable --now systemd-resolved || true") + run_in_container(CONTAINER_NAME, "echo 'nameserver 8.8.8.8' > /etc/resolv.conf") + + # Sync code + print("Syncing code to container...") + target_path = f"/var/lib/lxc/{CONTAINER_NAME}/rootfs/relay-ir" + run_cmd(f"sudo mkdir -p {target_path}") + run_cmd(f"sudo rsync -av --exclude=.git --exclude='*.log' --exclude='test' {RELAY_SRC}/ {target_path}/") + + # Install dependencies + print("Installing dependencies inside container...") + run_in_container(CONTAINER_NAME, "apt-get update && apt-get install -y curl sudo python3-dev gcc") + run_in_container(CONTAINER_NAME, "apt-get remove --purge -y exim4*") + run_in_container(CONTAINER_NAME, "curl -LsSf https://astral.sh/uv/install.sh | sh") + +def perform_deployment(): + print("--- Running deployment test ---") + # Using the PATH where uv is installed + path_env = "export PATH='/root/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'" + deploy_cmd = f"cd /relay-ir && {path_env} && ./scripts/initenv.sh && ./scripts/cmdeploy run --ssh-host @local --skip-dns-check" + + try: + output = run_in_container(CONTAINER_NAME, deploy_cmd) + print("Deployment Output Summary:") + print(output[-1000:] if output else "No output") # Show last bit of output + print("\nSUCCESS: Deployment completed.") + except Exception: + print("\nFAILURE: Deployment failed.") + sys.exit(1) + +if __name__ == "__main__": + if os.geteuid() != 0: + print("This script must be run as root/sudo.") + # We don't exit here because the inner run_cmd uses sudo, but it's better to be root. + + cleanup() + create_container() + prepare_container() + perform_deployment()