#!/usr/bin/python3 import subprocess # nosec 404 import sys from argparse import ArgumentParser from pathlib import Path # Variables # You might want to change some. # Volumes VOLUMES_DIR = Path.home() / "volumes" # AdvLabDB ADVLABDB_REPO_LINK = "https://gitlab.rlp.net/mobitar/advlabdb.git" ADVLABDB_VOLUMES_DIR = VOLUMES_DIR / "advlabdb" ADVLABDB_REPO_DIR = ADVLABDB_VOLUMES_DIR / "repo" ADVLABDB_LOGS_DIR = ADVLABDB_VOLUMES_DIR / "logs" ADVLABDB_DATA_DIR = ADVLABDB_VOLUMES_DIR / "data" # Traefik TRAEFIK_VOLUMES_DIR = VOLUMES_DIR / "traefik" TRAEFIK_ETC_DIR = TRAEFIK_VOLUMES_DIR / "etc" TRAEFIK_LOGS_DIR = TRAEFIK_VOLUMES_DIR / "logs" TRAEFIK_CERTS_DIR = TRAEFIK_VOLUMES_DIR / "certs" # Nginx NGINX_CONF_D_DIR = VOLUMES_DIR / "nginx/conf.d" # Systemd SYSTEMD_USER_DIR = Path.home() / ".config/systemd/user" # Parse script arguments parser = ArgumentParser( description="Build the AdvLabDB image and deploy related containers.", ) parser.add_argument( "--skip-traefik", action="store_true", help="Do not deploy the Traefik container.", ) parser.add_argument( "--skip-nginx", action="store_true", help="Do not deploy the Nginx container.", ) parser.add_argument( "-n", "--network", default="traefik", help="Traefik's network. Default: traefik.", ) parser.add_argument( "-v", "--verbose", action="store_true", help="Show more information while running.", ) args = parser.parse_args() # Functions def run(command: str, **kwargs): print("\n\nRunning command:") print(f"\t{command}\n") return subprocess.run(command, shell=True, **kwargs) # nosec B602 # Needed for create_container if args.verbose: print(f"Making sure that the systemd directory for user services {SYSTEMD_USER_DIR} exists.") SYSTEMD_USER_DIR.mkdir(parents=True, exist_ok=True) def create_container(container_name: str, podman_args: str): """ Create a container with a systemd service. """ print(f"Creating container: {container_name}.") run( f"""podman create \ --name {container_name} \ --tz local \ {podman_args}""", check=True, ) print(f"Generating a systemd service then enabling and starting it for the container {container_name}.") print(f"The service is a user service named container-{container_name}.") print("You can check its status with the following command:") print(f"\tsystemctl --user status container-{container_name}") run( f"podman generate systemd --new --files --name {container_name}", cwd=SYSTEMD_USER_DIR, check=True, ) run( f"systemctl --user enable --now container-{container_name}", check=True, ) def pull_or_clone_repo(): if ADVLABDB_REPO_DIR.is_dir(): print("Pulling AdvLabDB repository.") run( "git pull --rebase origin main", cwd=ADVLABDB_REPO_DIR, check=True, ) else: if args.verbose: print(f"Making sure that the volumes directory {ADVLABDB_VOLUMES_DIR} exists.") ADVLABDB_VOLUMES_DIR.mkdir(parents=True, exist_ok=True) print("Cloning AdvLabDB repository") print(f"From:\t{ADVLABDB_REPO_LINK}") print(f"Into:\t{ADVLABDB_REPO_DIR}") run( f"git clone {ADVLABDB_REPO_LINK} {ADVLABDB_REPO_DIR}", check=True, ) def check_requirements(): settings_file = ADVLABDB_DATA_DIR / "settings.ini" if not settings_file.is_file(): sys.exit(f"{settings_file} missing!") if not args.skip_traefik: if not TRAEFIK_ETC_DIR.is_dir(): sys.exit(f"{TRAEFIK_ETC_DIR} missing!") if not args.skip_nginx: if not NGINX_CONF_D_DIR.is_dir(): sys.exit(f"{NGINX_CONF_D_DIR} missing!") if run(f"podman network exists {args.network}").returncode != 0: if args.skip_traefik: sys.exit(f"Skipped Traefik's deployment although Traefik's network {args.network} does not exist!") else: print(f"Creating network {args.network}.") run( f"podman network create {args.network}", # Commented out because old buildah versions do not support "network exists" # check=True, ) def create_traefik_container(): if args.verbose: print(f"Making sure that the logs directory {TRAEFIK_LOGS_DIR} exists.") TRAEFIK_LOGS_DIR.mkdir(parents=True, exist_ok=True) if args.verbose: print(f"Making sure that the certificates directory {TRAEFIK_CERTS_DIR} exists.") TRAEFIK_CERTS_DIR.mkdir(parents=True, exist_ok=True) print("Creating container traefik.") create_container( "traefik", f"""--network {args.network} \ -p 80:80 \ -p 443:443 \ -v {TRAEFIK_ETC_DIR}:/etc/traefik:Z,ro \ -v {TRAEFIK_LOGS_DIR}:/volumes/logs:Z \ -v {TRAEFIK_CERTS_DIR}:/volumes/certs:Z \ --label "io.containers.autoupdate=registry" \ docker.io/library/traefik:latest""", ) def create_nginx_container(): print("Creating container nginx.") create_container( "nginx", f"""--network {args.network} \ -v {NGINX_CONF_D_DIR}:/etc/nginx/conf.d:Z,ro \ --label "io.containers.autoupdate=registry" \ docker.io/library/nginx:alpine""", ) def main(): pull_or_clone_repo() # Checking requirements check_requirements() # Create/update the AdvLabDB image and container # Make sure that the builder container does not exist. run( "buildah rm builder", stderr=subprocess.DEVNULL, ) requirements_file = ADVLABDB_REPO_DIR / "requirements.txt" container_cmd = ( "\"gunicorn --bind 0.0.0.0:80 --workers 5 --log-file /volumes/logs/gunicorn.log 'run:create_app()'\"" ) print("Creating AdvLabDB image.") commands = ( # Start building from a base image "buildah from --pull --name builder docker.io/library/python:3.10-slim", # Install Python requirements in the container f"buildah copy builder {requirements_file} /root/requirements.txt", "buildah run builder -- pip3 install -r /root/requirements.txt", "buildah run builder -- rm /root/requirements.txt", # Set the working directory of the container "buildah config --workingdir /volumes/repo builder", # Set the command that will run after starting the container f"buildah config --cmd {container_cmd} builder", ) for command in commands: run(command, check=True) if run("systemctl --user is-enabled container-advlabdb").returncode == 0: print("Disabling and deleting existing container advlabdb.") run( "systemctl --user disable --now container-advlabdb", check=True, ) if run("podman image exists localhost/advlabdb:latest").returncode == 0: print("Deleting existing image advlabdb.") run( "podman rmi localhost/advlabdb:latest", check=True, ) # Save new image run( "buildah commit --rm builder advlabdb", check=True, ) if args.verbose: print(f"Making sure that the logs directory {ADVLABDB_LOGS_DIR} exists.") ADVLABDB_LOGS_DIR.mkdir(parents=True, exist_ok=True) container_args = f"""--network {args.network} \ -e ADVLABDB_DATA_DIR=/volumes/data \ -v {ADVLABDB_REPO_DIR}:/volumes/repo:Z \ -v {ADVLABDB_DATA_DIR}:/volumes/data:Z \ -v {ADVLABDB_LOGS_DIR}:/volumes/logs:Z""" # Running setup commands (if needed) run_manage = f"podman run -it --rm {container_args} localhost/advlabdb:latest python3 manage.py" commands = ( # Generate secret keys if secrets.ini does not exist yet f"{run_manage} setup generate-secrets", # Initialize a database if none exists f"{run_manage} setup init-db", ) for command in commands: run(command, check=True) print("Creating container advlabdb.") create_container( "advlabdb", f"{container_args} localhost/advlabdb:latest", ) # Create Traefik container if needed if not args.skip_traefik and run("systemctl --user is-enabled container-traefik").returncode != 0: create_traefik_container() # Create Nginx container if needed if not args.skip_nginx and run("systemctl --user is-enabled container-nginx").returncode != 0: create_nginx_container() print("\nDone!\n") if __name__ == "__main__": main()