blis-cloud-cli 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
blis_cli/__init__.py ADDED
File without changes
blis_cli/blis.py ADDED
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env python3
2
+
3
+ import click
4
+ import os
5
+ import sys
6
+ import psutil
7
+
8
+ from blis_cli.util import config as blis_config
9
+ from blis_cli.util import docker_util as blis_docker_util
10
+ from blis_cli.util import environment as blis_env
11
+
12
+ from blis_cli.util import bash
13
+ from blis_cli.commands import docker as cmd_docker_grp
14
+ from blis_cli.commands import install as cmd_install
15
+ from blis_cli.commands import logs as cmd_logs_grp
16
+ from blis_cli.commands import start as cmd_start
17
+ from blis_cli.commands import status as cmd_status
18
+ from blis_cli.commands import stop as cmd_stop
19
+ from blis_cli.commands import update as cmd_update
20
+ from blis_cli.commands import domain as cmd_domain_grp
21
+ from blis_cli.commands import swap as cmd_swap_grp
22
+
23
+
24
+ @click.command()
25
+ def status():
26
+ exit(cmd_status.run())
27
+
28
+
29
+ @click.command()
30
+ def install():
31
+ exit(cmd_install.run())
32
+
33
+
34
+ @click.command()
35
+ def start():
36
+ exit(cmd_start.run())
37
+
38
+
39
+ @click.command()
40
+ def stop():
41
+ exit(cmd_stop.run())
42
+
43
+
44
+ @click.command()
45
+ def update():
46
+ exit(cmd_update.run())
47
+
48
+
49
+ @click.group()
50
+ def entry_point():
51
+ pass
52
+
53
+
54
+ def main():
55
+ entry_point.add_command(install)
56
+ entry_point.add_command(start)
57
+ entry_point.add_command(status)
58
+ entry_point.add_command(stop)
59
+ entry_point.add_command(update)
60
+
61
+ entry_point.add_command(cmd_logs_grp.entrypoint, "logs")
62
+ entry_point.add_command(cmd_docker_grp.entrypoint, "docker")
63
+ entry_point.add_command(cmd_domain_grp.entrypoint, "domain")
64
+ entry_point.add_command(cmd_swap_grp.entrypoint, "swap")
65
+
66
+ entry_point()
67
+
File without changes
@@ -0,0 +1,139 @@
1
+ import click
2
+ import docker as lib_docker
3
+ import getpass
4
+ import os
5
+ import requests
6
+ import shutil
7
+
8
+ from blis_cli.util import bash
9
+ from blis_cli.util import config
10
+ from blis_cli.util import docker_util
11
+ from blis_cli.util import environment as env
12
+ from blis_cli.util import packages
13
+
14
+
15
+ @click.group()
16
+ def entrypoint():
17
+ pass
18
+
19
+
20
+ @click.command
21
+ def install():
22
+ # If Docker is installed, and we are in the docker group, we will not need root privileges.
23
+ if not (env.in_docker_grp() and docker_util.installed()):
24
+ if not (os.geteuid() == 0 or env.can_sudo()):
25
+ click.secho(
26
+ "Docker must be installed. You must run this script as root or have passwordless sudo privileges.",
27
+ fg="red",
28
+ )
29
+ exit(1)
30
+
31
+ install_docker()
32
+
33
+ # Returns a success code because we probably succeeded and installed Docker, but we need to log out and log back in.
34
+ exit(0)
35
+
36
+ # We might have Docker installed, but not be in the docker group.
37
+ if docker_util.installed() and not env.in_docker_grp():
38
+ click.secho(
39
+ "You have Docker installed, but you are not in the docker group.",
40
+ fg="yellow",
41
+ )
42
+ # Try to fix the problem...
43
+ if env.can_sudo():
44
+ bash.sudo(f"usermod -aG docker {getpass.getuser()}")
45
+ click.echo("Please log out and log back in, and run this command again.")
46
+ exit(0)
47
+ else:
48
+ click.secho(
49
+ "You must run this script as root or have passwordless sudo privileges.",
50
+ fg="red",
51
+ )
52
+ exit(1)
53
+
54
+ click.secho("Docker is installed!", fg="green")
55
+
56
+
57
+ def install_docker():
58
+ click.echo("Setting up Docker... ")
59
+ packages.remove(["docker", "docker-engine", "docker.io", "containerd", "runc"])
60
+ packages.install(["ca-certificates", "curl", "gnupg", "lsb-release"])
61
+ bash.sudo("mkdir -p /etc/apt/keyrings")
62
+ bash.run(
63
+ "curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o - | sudo tee /etc/apt/keyrings/docker.gpg >/dev/null"
64
+ )
65
+ bash.run(
66
+ 'echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null'
67
+ )
68
+ packages.apt_update()
69
+ packages.install(
70
+ [
71
+ "docker-ce",
72
+ "docker-ce-cli",
73
+ "containerd.io",
74
+ "docker-compose-plugin",
75
+ "docker-buildx-plugin",
76
+ ]
77
+ )
78
+ bash.sudo(f"usermod -aG docker {getpass.getuser()}")
79
+ bash.sudo("systemctl enable docker.service")
80
+ bash.sudo("systemctl start docker.service")
81
+ click.secho("Success!", fg="green")
82
+ click.echo("Please log out and log back in, and run this command again.")
83
+
84
+
85
+ @click.command
86
+ def purge():
87
+ if not env.can_sudo():
88
+ click.secho("You must have root privileges to run this.", fg="red")
89
+ exit(1)
90
+
91
+ packages.remove(["docker", "docker-engine", "docker.io", "containerd", "runc"])
92
+ packages.remove(
93
+ [
94
+ "docker-ce",
95
+ "docker-ce-cli",
96
+ "containerd.io",
97
+ "docker-compose-plugin",
98
+ "docker-buildx-plugin",
99
+ ]
100
+ )
101
+ packages.apt_update()
102
+
103
+
104
+ @click.command
105
+ def status():
106
+ docker_ok = True
107
+ click.echo("Docker is accessible? ", nl=False)
108
+ try:
109
+ client = lib_docker.from_env()
110
+ client.containers.list()
111
+ click.secho("Yes", fg="green")
112
+ except Exception as e:
113
+ docker_ok = False
114
+ click.secho("No", fg="red")
115
+
116
+ cmd = "blis docker install"
117
+ if not env.can_sudo():
118
+ cmd = "sudo " + cmd
119
+
120
+ if not docker_ok:
121
+ click.echo("Docker is not accessible. Please run:")
122
+ click.echo(" " + cmd)
123
+ return 0
124
+
125
+ click.echo("Docker Compose is installed? ", nl=False)
126
+ if docker_util.compose_v2_installed():
127
+ click.secho("v2", fg="green")
128
+ elif docker_util.compose_v1_installed():
129
+ click.secho("v1", fg="green")
130
+ else:
131
+ click.secho("No", fg="red")
132
+ click.echo("Docker Compose is not installed. Please run:")
133
+ click.echo(" " + cmd)
134
+ return 0
135
+
136
+
137
+ entrypoint.add_command(install)
138
+ entrypoint.add_command(purge)
139
+ entrypoint.add_command(status)
@@ -0,0 +1,144 @@
1
+ import click
2
+ import psutil
3
+ import socket
4
+
5
+ from blis_cli.util import caddy
6
+ from blis_cli.util import docker_util
7
+
8
+
9
+ @click.group()
10
+ def entrypoint():
11
+ pass
12
+
13
+
14
+ @click.command()
15
+ def status():
16
+ click.secho("Caddy is configured: ", nl=False)
17
+ if caddy.installed():
18
+ click.secho("Yes!", fg="green")
19
+ else:
20
+ click.secho("No", fg="red")
21
+ click.echo("\nThere are no domains configured. Please run:")
22
+ click.echo(" blis domain add [mydomain.example.com]")
23
+ exit(0)
24
+
25
+ domains = caddy.get_domains()
26
+ if len(domains) < 1:
27
+ click.echo("\nThere are no domains configured. Please run:")
28
+ click.echo(" blis domain add [mydomain.example.com]")
29
+ exit(0)
30
+
31
+ # These are only "possible" IPs because the computer
32
+ # might be behind a firewall/NAT and these will only contain
33
+ # the local IP addresses.
34
+ possible_ipv4_addrs = get_interfaces()
35
+
36
+ for domain in domains:
37
+ click.echo()
38
+ click.secho(domain, fg="green")
39
+ click.echo("IP Addresses: ", nl=False)
40
+ ip_addrs = get_ipv4_by_hostname(domain)
41
+ click.secho(", ".join(ip_addrs), fg="green")
42
+
43
+ for ip in ip_addrs:
44
+ if ip in possible_ipv4_addrs:
45
+ # This is the 'happiest' path since the computer/VM has an IP that is facing the Internet.
46
+ click.secho("Domain points to computer!")
47
+ break
48
+
49
+ exit(0)
50
+
51
+
52
+ @click.command()
53
+ @click.argument("name")
54
+ def add(name: str):
55
+ current_domains = caddy.get_domains()
56
+ if name in current_domains:
57
+ click.echo(f"{name} is already configured as a domain.")
58
+ return 0
59
+
60
+ click.confirm(f"Do you want to add {name} as a domain?", abort=True)
61
+
62
+ restart_blis = False
63
+ if docker_util.blis_is_running():
64
+ if not docker_util.stop_blis_with_confirm():
65
+ return 1
66
+ restart_blis = True
67
+
68
+ if not caddy.installed():
69
+ click.echo("Installing Caddy... ", nl=False)
70
+ try:
71
+ caddy.install()
72
+ click.secho("Success!", fg="green")
73
+ except Exception as e:
74
+ click.secho(e, fg="red")
75
+ return 1
76
+
77
+ click.echo(f"Adding {name} to Caddyfile... ", nl=False)
78
+
79
+ current_domains.append(name)
80
+ caddy.set_domains(current_domains)
81
+
82
+ click.secho("Success!", fg="green")
83
+
84
+ if restart_blis:
85
+ docker_util.start_blis("app")
86
+ docker_util.restart_caddy()
87
+
88
+
89
+ @click.command()
90
+ def clear():
91
+ click.secho("This will remove all of the domains from the configuration.", fg="red")
92
+ click.confirm(f"Do you want to continue?", abort=True)
93
+
94
+ restart_blis = False
95
+ if docker_util.blis_is_running():
96
+ if not docker_util.stop_blis_with_confirm():
97
+ return 1
98
+ restart_blis = True
99
+
100
+ click.echo(f"Removing all domains from Caddyfile... ", nl=False)
101
+
102
+ caddy.set_domains([])
103
+
104
+ click.secho("Success!", fg="green")
105
+
106
+ if restart_blis:
107
+ docker_util.start_blis("app")
108
+ docker_util.restart_caddy()
109
+
110
+
111
+ # Source:
112
+ # https://stackoverflow.com/questions/2805231/how-can-i-do-dns-lookups-in-python-including-referring-to-etc-hosts/66000439#66000439
113
+ # Licensed: CC BY-SA 4.0
114
+ def get_ipv4_by_hostname(hostname):
115
+ # see `man getent` `/ hosts `
116
+ # see `man getaddrinfo`
117
+ try:
118
+ return list(
119
+ i[4][0] # raw socket structure # internet protocol info # address
120
+ for i in socket.getaddrinfo(hostname, 0) # port, required
121
+ if i[0] is socket.AddressFamily.AF_INET # ipv4
122
+ # ignore duplicate addresses with other socket types
123
+ and i[1] is socket.SocketKind.SOCK_RAW
124
+ )
125
+ except Exception as e:
126
+ click.secho(str(e), fg="red", nl=False)
127
+ return []
128
+
129
+
130
+ def get_interfaces():
131
+ addrs = psutil.net_if_addrs()
132
+ ip_addrs = []
133
+ for key in addrs:
134
+ addr = addrs[key]
135
+ for a in addr:
136
+ if a.family == socket.AddressFamily.AF_INET:
137
+ ip_addrs.append(a.address)
138
+
139
+ return ip_addrs
140
+
141
+
142
+ entrypoint.add_command(add)
143
+ entrypoint.add_command(clear)
144
+ entrypoint.add_command(status)
@@ -0,0 +1,92 @@
1
+ import click
2
+ import glob
3
+ import os
4
+ import docker as lib_docker
5
+ import shutil
6
+
7
+ from blis_cli.util import bash
8
+ from blis_cli.util import caddy
9
+ from blis_cli.util import config
10
+ from blis_cli.util import docker_util as docker
11
+ from blis_cli.util import environment as env
12
+ from blis_cli.util import packages
13
+
14
+
15
+ def run():
16
+ try:
17
+ version = lib_docker.from_env().version()
18
+ click.echo(f"Docker version: {click.style(version['Version'], fg='green')}")
19
+ except Exception as e:
20
+ click.secho("There was a problem accessing Docker.", fg="red")
21
+ click.echo("Please run `blis docker status` and follow the instructions.")
22
+ return 1
23
+
24
+ if os.path.exists(config.compose_file()):
25
+ if not click.confirm(
26
+ "BLIS has already been installed in ~/.blis. Do you want to overwrite the configuration?"
27
+ ):
28
+ return 0
29
+
30
+ config.make_basedir()
31
+ copy_docker_files()
32
+
33
+ if config.validate_compose():
34
+ click.echo("docker-compose.yml is valid.")
35
+ else:
36
+ click.secho("docker-compose.yml is not valid.", fg="red")
37
+ return 1
38
+
39
+ run_blis_and_setup_db()
40
+
41
+ click.secho("BLIS has been successfully installed!", fg="green")
42
+
43
+
44
+ def copy_docker_files():
45
+ click.echo("Copying docker-compose.yml to ~/.blis/...")
46
+ shutil.copy(
47
+ f"{os.path.dirname(__file__)}/../extra/docker-compose.yml",
48
+ config.compose_file(),
49
+ )
50
+ click.echo("Creating ~/.blis/Caddyfile...")
51
+ caddy.set_domains([])
52
+
53
+
54
+ def run_blis_and_setup_db():
55
+ click.echo("Starting BLIS database... ", nl=False)
56
+ out, err = bash.run(
57
+ f"{docker.compose()} -f {config.compose_file()} up -d --wait db"
58
+ )
59
+ if err:
60
+ click.secho("Failed", fg="red")
61
+ click.echo(err, err=True)
62
+ return False
63
+
64
+ click.secho("Success!", fg="green")
65
+
66
+ db_password = config.compose_key("services.db.environment.MYSQL_ROOT_PASSWORD")
67
+ seed_failed = False
68
+
69
+ for file in glob.glob(f"{os.path.dirname(__file__)}/../extra/*.sql"):
70
+ click.echo(f"Seeding {os.path.basename(file)}... ", nl=False)
71
+ out, err = bash.run(
72
+ f"{docker.compose()} -f {config.compose_file()} exec -T db mysql -hdb -uroot -p{db_password} < {file}"
73
+ )
74
+ if err:
75
+ click.secho("Failed", fg="red")
76
+ click.echo(err, err=True, nl=False)
77
+ seed_failed = True
78
+ else:
79
+ click.secho("Success!", fg="green")
80
+
81
+ if seed_failed:
82
+ click.secho("Seeding database failed.", fg="yellow")
83
+ click.echo("BLIS might still start. Please check the errors for details.")
84
+
85
+ click.echo("Starting BLIS... ", nl=False)
86
+ out, err = bash.run(f"{docker.compose()} -f {config.compose_file()} up -d app")
87
+ if err:
88
+ click.secho("Failed", fg="red")
89
+ click.echo(err, err=True)
90
+ return False
91
+
92
+ click.secho("Success!", fg="green")
@@ -0,0 +1,73 @@
1
+ import click
2
+ import docker
3
+ import tarfile
4
+ import tempfile
5
+
6
+ from blis_cli.util import config
7
+ from blis_cli.util import environment
8
+ from blis_cli.util import docker_util
9
+
10
+
11
+ @click.group
12
+ def entrypoint():
13
+ pass
14
+
15
+
16
+ def get_container_log_file(filename):
17
+ try:
18
+ c = docker_util.blis_container()
19
+
20
+ # This is technically not required. We could probably extract logs from
21
+ # a stopped BLIS since the /log folder is mounted as its own volume.
22
+ # However... I don't know how to reliably do that right now.
23
+ # And it would definitely need root.
24
+ if c == None:
25
+ click.secho("BLIS is not running.", fg="red")
26
+ click.echo("Please start BLIS to see logs.")
27
+ return 0
28
+
29
+ with tempfile.TemporaryFile() as f:
30
+ bits, stat = c.get_archive(filename)
31
+
32
+ for chunk in bits:
33
+ f.write(chunk)
34
+
35
+ f.seek(0)
36
+ tf = tarfile.open(fileobj=f)
37
+ m = tf.extractfile(tf.getnames()[0])
38
+ for chunk in m:
39
+ click.echo(chunk, nl=False)
40
+
41
+ except docker.errors.NotFound as e:
42
+ click.echo("The log file was not found.", err=True)
43
+ return 1
44
+ except Exception as e:
45
+ click.echo("There was a problem getting the logs for BLIS!", err=True)
46
+ click.echo(e)
47
+ return 1
48
+
49
+
50
+ @click.command()
51
+ def application():
52
+ exit(get_container_log_file("/var/www/blis/log/application.log"))
53
+
54
+
55
+ @click.command()
56
+ def database():
57
+ exit(get_container_log_file("/var/www/blis/log/database.log"))
58
+
59
+
60
+ @click.command(name="apache2/access")
61
+ def apache2_access():
62
+ exit(get_container_log_file("/var/log/apache2/access.log"))
63
+
64
+
65
+ @click.command(name="apache2/error")
66
+ def apache2_error():
67
+ exit(get_container_log_file("/var/log/apache2/error.log"))
68
+
69
+
70
+ entrypoint.add_command(application)
71
+ entrypoint.add_command(database)
72
+ entrypoint.add_command(apache2_access)
73
+ entrypoint.add_command(apache2_error)
@@ -0,0 +1,13 @@
1
+ import click
2
+ import os
3
+
4
+ from blis_cli.util import config
5
+ from blis_cli.util import docker_util
6
+
7
+
8
+ def run():
9
+ if not os.path.exists(config.compose_file()):
10
+ click.secho("Please run `blis install` and run this command again.", fg="red")
11
+ return 1
12
+
13
+ return docker_util.start_blis()
@@ -0,0 +1,78 @@
1
+ import click
2
+ import psutil
3
+
4
+ from blis_cli.util import caddy
5
+ from blis_cli.util import config
6
+ from blis_cli.util import environment as blis_env
7
+ from blis_cli.util import docker_util as blis_docker_util
8
+
9
+
10
+ def run():
11
+ try:
12
+ blis_installed = False
13
+ click.echo("BLIS is installed: ", nl=False)
14
+ if config.validate_compose():
15
+ blis_installed = True
16
+ click.secho("Yes!", fg="green")
17
+ else:
18
+ click.secho("No", fg="red")
19
+
20
+ click.echo("BLIS is running: ", nl=False)
21
+ if blis_docker_util.blis_is_running():
22
+ click.secho("Yes!", fg="green")
23
+ else:
24
+ click.secho("No", fg="red")
25
+
26
+ click.echo("Caddy is enabled: ", nl=False)
27
+ if caddy.installed():
28
+ click.secho("Yes!", fg="green")
29
+ else:
30
+ click.secho("No", fg="red")
31
+ if blis_installed:
32
+ click.secho("Custom domain support is disabled. Please run `blis domain add` to setup.", fg="red")
33
+
34
+ total_ram = psutil.virtual_memory().total / (1024.0**3)
35
+ swap_space = psutil.swap_memory().total / (1024.0**3)
36
+ click.echo("Total RAM: ", nl=False)
37
+ click.secho(f"{total_ram:.2f} GiB", fg="green", nl=False)
38
+ if swap_space > 0:
39
+ click.secho(f" (swap: {swap_space:.2f} GiB)", fg="green")
40
+ else:
41
+ click.echo()
42
+
43
+ if total_ram < 0.9:
44
+ click.secho(
45
+ "1GB of RAM is recommended to run BLIS. Things might not work as expected!",
46
+ fg="red",
47
+ )
48
+
49
+ click.echo("Supported Ubuntu distribution: ", nl=False)
50
+ if blis_env.supported_distro():
51
+ click.secho("Yes!", fg="green")
52
+ else:
53
+ click.secho("No", fg="red")
54
+ click.echo("BLIS is supported on these Ubuntu distributions: ", nl=False)
55
+ click.secho(", ".join(blis_env.SUPPORTED_DISTROS), fg="green")
56
+ click.echo("You have: ", nl=False)
57
+ click.secho(blis_env.distro(), fg="green")
58
+
59
+ click.echo("Passwordless sudo: ", nl=False)
60
+ if blis_env.can_sudo():
61
+ click.secho("Yes!", fg="green")
62
+ else:
63
+ click.secho("No", fg="red")
64
+
65
+ if (
66
+ blis_docker_util.blis_container() == None
67
+ and not blis_docker_util.installed()
68
+ ):
69
+ click.secho(
70
+ "Please run `blis docker status` to check the status of Docker on your machine.",
71
+ fg="yellow",
72
+ )
73
+
74
+ return 0
75
+ except Exception as e:
76
+ click.echo("There was a problem getting the status of BLIS!")
77
+ click.echo(e)
78
+ return 1
@@ -0,0 +1,18 @@
1
+ import click
2
+
3
+ from blis_cli.util import bash
4
+ from blis_cli.util import config
5
+ from blis_cli.util import docker_util as docker
6
+
7
+
8
+ def run():
9
+ click.echo("Stopping BLIS... ", nl=False)
10
+ out, err = bash.run(f"{docker.compose()} -f {config.compose_file()} down")
11
+ if err:
12
+ click.secho("Failed", fg="red")
13
+ click.echo(err, err=True)
14
+ return False
15
+
16
+ click.secho("Success!", fg="green")
17
+
18
+ return 0
@@ -0,0 +1,50 @@
1
+ import click
2
+ import os
3
+ import psutil
4
+ import socket
5
+
6
+ from blis_cli.util import bash
7
+
8
+
9
+ @click.group()
10
+ def entrypoint():
11
+ pass
12
+
13
+
14
+ @click.command()
15
+ def status():
16
+ swap_space = psutil.swap_memory().total / (1024.0**3)
17
+ swap_enabled = (swap_space > 0)
18
+
19
+ if swap_enabled:
20
+ click.echo("Swap space: ", nl=False)
21
+ click.secho(f"{swap_space:.2f} GB", fg="green")
22
+ else:
23
+ click.secho("Swap space is not configured.", fg="red")
24
+
25
+ exit(0)
26
+
27
+
28
+ # Creates a 1 GB swap file
29
+ # TODO: make it more configurable
30
+ @click.command()
31
+ def create():
32
+ if os.path.exists("/swapfile"):
33
+ click.echo("Swapfile already exists.")
34
+ exit(1)
35
+
36
+ if psutil.swap_memory().total > 0:
37
+ click.echo("A swap partition or swap file is already configured.")
38
+ exit(1)
39
+
40
+ bash.run("dd if=/dev/zero of=/swapfile bs=1024 count=1048576", root=True)
41
+ bash.run("mkswap /swapfile", root=True)
42
+ bash.run("chmod 600 /swapfile", root=True)
43
+ bash.run("swapon /swapfile", root=True)
44
+ bash.run("echo -e \"# Added by blis-cloud-cli\\n/swapfile\\tnone\\tswap\\tsw\\t0\\t0\" | tee -a /etc/fstab", root=True)
45
+
46
+ click.secho("Swapfile created!", fg="green")
47
+ exit(0)
48
+
49
+ entrypoint.add_command(status)
50
+ entrypoint.add_command(create)