async def run_all() -> None: from kolombo.models import Domain client = from_env() build_kolombo_image(client, "receiver") build_kolombo_image(client, "auth") build_kolombo_image(client, "nginx") build_kolombo_image(client, "sender") step("Deploying all Kolombo services") up_all_kolombo_services() domains = [domain.actual for domain in await Domain.all_active()] # Remove duplicates preserving order domains = list(dict.fromkeys(domains)) if len(domains) < 1: error("No active domains to run senders for") return senders_compose_config = generate_senders_compose_config(domains) project_name = "kolombo_senders" file_path = "/etc/kolombo/senders-compose.yml" with open(file_path, mode="w") as compose_file: compose_file.write(senders_compose_config) step(f"Deploying senders for domains: {', '.join(domains)}") compose_command = [ "docker-compose", "-p", project_name, "-f", file_path, "up" ] run([*compose_command, "--force-recreate", "--remove-orphans", "-d"])
def build_kolombo_image(client: DockerClient, service: str) -> None: kolombo_folder = files("kolombo") step(f"Building [code]kolombo-{service}[/] image") client.images.build( tag=f"kolombo-{service}", path=str(kolombo_folder), pull=True, nocache=True, rm=True, dockerfile=f"docker/Dockerfile.{service}", )
async def stop_senders() -> None: from kolombo.models import Domain domains = [domain.actual for domain in await Domain.all_active()] # Remove duplicates domains = list(dict.fromkeys(domains)) senders_compose_config = generate_senders_compose_config(domains) project_name = "kolombo_senders" file_path = "/etc/kolombo/senders-compose.yml" with open(file_path, mode="w") as compose_file: compose_file.write(senders_compose_config) step("Stopping all running senders") compose_command = ["docker-compose", "-p", project_name, "-f", file_path, "down"] run([*compose_command, "--remove-orphans"])
def generate_keys( domain: str = Argument( ..., help="Domain to generate DKIM keys for"), # noqa: B008 ) -> None: client = from_env() build_kolombo_image(client, "dkim-gen") step(f"Generating DKIM keys for domain: {domain}") client.containers.run( "kolombo-dkim-gen", domain, stderr=True, auto_remove=True, volumes=["/etc/kolombo/dkim_keys:/etc/opendkim/keys"], ) dkim_txt = read_dkim_txt_record(domain) info( f"[b]TXT[/] record for [b u]mail._domainkey.{domain}[/] is: {dkim_txt}" )
async def add_user( email: str = Argument(..., help="Email for new user"), # noqa: B008 ) -> None: from kolombo.models import Domain, User if "@" not in email: error(f"Email '{email}' does not contain '@'!") exit(1) domain = email.split("@", maxsplit=1)[1].strip() if not domain: error("Domain part MUST NOT be empty string!") exit(1) elif not await Domain.objects.filter(active=True, actual=domain).exists(): error(f"Domain '{domain}' is not added (or inactive)!") warning( f"You can add it via [code]kolombo domain add {domain} mx.{domain}[/code]" ) exit(1) elif await User.objects.filter(email=email).exists(): error(f"User with email '{email}' already exists!") exit(1) started(f"Adding [code]{email}[/] user") password = prompt(f"{email} password", hide_input=True, confirmation_prompt=True) step("Saving to database") await _save_user(email, password, domain) step("Updating virtual files (addresses and mailbox map)") active_users = await User.all_active() update_virtual_files(active_users) warning("Run command [code]kolombo run[/] to reload Kolombo") finished(f"User '{email}' added!")
async def add_domain( domain: str = Argument( # noqa: B008 ..., help="Domain that comes after @ in email" ), mx: str = Argument(None, help="Domain from DNS MX record if exists"), # noqa: B008 ) -> None: from kolombo import conf from kolombo.models import Domain if mx is None: mx = domain if not domain or not mx: error("Arguments MUST NOT be empty strings!") exit(1) elif await Domain.objects.filter(actual=domain, mx=mx).exists(): error(f"Pair [code]{mx} -> {domain}[/] already exists!") exit(1) started(f"Adding [code]{mx} -> {domain}[/] pair") step("Adding configuration to [code]mail-enabled[/]") nginx_config = generate_nginx_config(mx, domain, conf.NGINX_SECRET_KEY) with open(f"/etc/kolombo/mail-enabled/{mx}.conf", mode="w") as nginx_file: nginx_file.write(nginx_config) step("Saving to database") await Domain.objects.create(actual=domain, mx=mx) step("Updating virtual files (domains and SSL map)") active_domains = await Domain.all_active() update_virtual_files(active_domains) warning( f"Run command [code]kolombo dkim generate {domain}[/] to generate DKIM keys" ) warning("Run command [code]kolombo run[/] to reload Kolombo") finished(f"Pair [code]{mx} -> {domain}[/] added!")
def stop_nginx() -> None: step("Stopping nginx service") stop_kolombo_service("nginx")
def stop_auth() -> None: step("Stopping auth service") stop_kolombo_service("auth")
def stop_receiver() -> None: step("Stopping receiver service") stop_kolombo_service("receiver")
def run_nginx() -> None: client = from_env() build_kolombo_image(client, "nginx") step("Bringing up nginx service") up_kolombo_service("nginx")
def run_auth() -> None: client = from_env() build_kolombo_image(client, "auth") step("Bringing up auth service") up_kolombo_service("auth")
def run_receiver() -> None: client = from_env() build_kolombo_image(client, "receiver") step("Bringing up receiver service") up_kolombo_service("receiver")
def init() -> None: from kolombo import __version__ as version username = getpwuid(getuid()).pw_name started(f"Setting up Kolombo for current user [b]{username}[/]") step("Creating /etc/kolombo folder ([u]need root privileges[/])") info("Creating /etc/kolombo folder (as root)") run(["mkdir", "-p", "-m", "750", "/etc/kolombo"], as_root=True) info(f"Changing /etc/kolombo owner to {username} (as root)") run(["chown", f"{getuid()}:{getgid()}", "/etc/kolombo"], as_root=True) step("Writing configuration to /etc/kolombo/kolombo.conf") debug_mode = confirm("Enable debug mode?", default=False, show_default=True) if debug_mode: enable_debug() nginx_secret_key: str = prompt( "Enter secret key for communication between NginX and auth API", default="changeme", show_default=True, hide_input=True, confirmation_prompt=True, ) max_auth_attempts: int = prompt( "Enter maximum auth attempts per one session", default="3", show_default=True, type=INT, ) passwords_salt: str = prompt( "Enter secret key to be used as salt for passwords hashing", default="changeme", show_default=True, hide_input=True, confirmation_prompt=True, ) configuration = ( f"### Generated by kolombo v{version}\n\n" "# Whether debug mode is enabled (0 - disabled, 1 - enabled)\n" f"DEBUG={int(debug_mode)}\n" "# Secret key that is used to determine that nginx is using API\n" f"NGINX_SECRET_KEY={nginx_secret_key}\n" "# Maximum auth attempts per one session\n" f"MAX_ATTEMPTS={max_auth_attempts}\n" "# Salt used for passwords hashing\n" f"SALT={passwords_salt}\n") with open("/etc/kolombo/kolombo.conf", "w") as config_file: config_file.write(configuration) step("Populating /etc/kolombo with default folders and files") debug("Creating /etc/kolombo folders for volumes") folders = ("maildirs", "mail-enabled", "virtual", "dkim_keys") for folder in folders: Path(f"/etc/kolombo/{folder}").mkdir(mode=0o770, exist_ok=True) for file in ("addresses", "domains", "mailbox", "ssl_map"): debug(f"Writing default file to /etc/kolombo/virtual/{file}") with open(f"/etc/kolombo/virtual/{file}", "w") as virtual_file: virtual_file.write(f"# {_virtual_configs[file]}\n") step( "Installing auto-completion ([u]restart current shell session to use[/])" ) run([sys.argv[0], "--install-completion"]) finished("Kolombo is set up!")