Exemple #1
0
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"])
Exemple #2
0
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}",
    )
Exemple #3
0
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"])
Exemple #4
0
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}"
    )
Exemple #5
0
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!")
Exemple #6
0
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!")
Exemple #7
0
def stop_nginx() -> None:
    step("Stopping nginx service")
    stop_kolombo_service("nginx")
Exemple #8
0
def stop_auth() -> None:
    step("Stopping auth service")
    stop_kolombo_service("auth")
Exemple #9
0
def stop_receiver() -> None:
    step("Stopping receiver service")
    stop_kolombo_service("receiver")
Exemple #10
0
def run_nginx() -> None:
    client = from_env()
    build_kolombo_image(client, "nginx")

    step("Bringing up nginx service")
    up_kolombo_service("nginx")
Exemple #11
0
def run_auth() -> None:
    client = from_env()
    build_kolombo_image(client, "auth")

    step("Bringing up auth service")
    up_kolombo_service("auth")
Exemple #12
0
def run_receiver() -> None:
    client = from_env()
    build_kolombo_image(client, "receiver")

    step("Bringing up receiver service")
    up_kolombo_service("receiver")
Exemple #13
0
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!")