Mini Shell
"""Setup script for bakmgr"""
import shlex
from argparse import ArgumentParser
import shutil
import platform
from subprocess import check_call, call, CalledProcessError
import sys
import os
import bz2
import json
from pathlib import Path
from typing import Union
import requests
from ..server_info import is_vps
from ..configs import Conf, CONF_PATH
REGISTER_URL = "https://ash-sys-pro-bakauth1.imhadmin.net:4002/register"
def _step(msg: str): # cyan
print(f"\033[96m{msg}\033[0m")
def _warn(msg: str): # yellow
print(f"\033[93;1m{msg}\033[0m")
def main():
"""Entry point for /opt/bakmgr/bin/bakmgr-setup"""
if os.getuid() != 0:
sys.exit('This tool must run as root')
args = parse_args()
# get the */site-packages/bakmgr folder
module_root = Path(__file__).resolve().parent.parent
download_restic()
try:
Path('/usr/bin/bakmgr').unlink()
except FileNotFoundError:
pass
try:
Path('/usr/bin/backup').unlink()
except FileNotFoundError:
pass
os.symlink('/opt/bakmgr/bin/backup', '/usr/bin/bakmgr')
os.symlink('/opt/bakmgr/bin/backup', '/usr/bin/backup')
for var_dir in ('.cache', 'monitoring'):
var_path = Path('/opt/bakmgr/var') / var_dir
var_path.mkdir(parents=True, exist_ok=True)
install_certs()
install_service(module_root)
install_cron(module_root)
install_config()
install_logrotate(module_root)
install_autocomplete(module_root)
if args.host == 'test':
_warn("Skipping registration due to --host 'test'")
elif args.host:
register(args.host)
def parse_args():
"""Parse CLI args to /opt/bakmgr/bin/bakmgr-setup"""
parser = ArgumentParser(__doc__)
parser.add_argument(
'--host',
required=not Path('/etc/bakmgr/.auth.json').is_file(),
help='hostname this server is known as by the billing system',
)
return parser.parse_args()
def _print_and_call(cmd, **kwargs):
print(f"Running: {' '.join(shlex.quote(arg) for arg in cmd)}")
try:
return call(cmd, **kwargs)
except OSError as exc:
print(exc)
return None
def install_service(module_root: Path):
_step("Installing dashboard")
dash_path = Path('/opt/bakmgr/dash')
if not dash_path.is_symlink():
dash_path.symlink_to(module_root / 'dash')
init = Path('/sbin/init').resolve().name
if init == 'systemd' and Path('/usr/lib/systemd/system').is_dir():
systemd_setup(module_root)
elif init == 'upstart' and Path('/etc/init').is_dir():
upstart_setup(module_root)
elif Path('/etc/init.d').is_dir():
sysv_setup(module_root)
else:
print("Could not determine init system", file=sys.stderr)
def _openssl(*args):
cmd = ['openssl', *args]
_print_and_call(cmd, cwd='/etc/bakmgr')
def install_certs():
if Path('/etc/bakmgr/dash.crt').exists():
return
_step("Setting up SSL certificates in /etc/bakmgr")
Path('/etc/bakmgr').mkdir(exist_ok=True)
subj = (
'/C=US/ST=CA/L=Los Angeles/O=InMotion Hosting/'
f'OU=InMotion Hosting/CN={platform.node()}'
)
_openssl('genrsa', '-out', 'dash.key', '2048')
# fmt: off
_openssl(
'req', '-new', '-key', 'dash.key', '-out', 'dash.csr',
'-subj', subj, '-batch'
)
_openssl(
'x509', '-req', '-in', 'dash.csr', '-signkey', 'dash.key',
'-out', 'dash.crt', '-days', '5120', '-sha256'
)
# fmt: on
def systemd_setup(module_root: Path):
_step("Setting up systemd service")
shutil.copy(
str(module_root / 'service_cfg/bakmgr.service'),
'/usr/lib/systemd/system/bakmgr.service',
)
_print_and_call(['systemctl', 'daemon-reload'])
_print_and_call(['systemctl', 'enable', 'bakmgr.service'])
if _print_and_call(['systemctl', 'status', 'bakmgr.service']):
_print_and_call(['systemctl', 'start', 'bakmgr.service'])
def upstart_setup(module_root: Path):
_step("Setting up upstart service")
shutil.copy(
str(module_root / 'service_cfg/bakmgr.upstart'),
'/etc/init/bakmgr.conf',
)
if _print_and_call(['status', 'bakmgr']):
_print_and_call(['start', 'bakmgr'])
def sysv_setup(module_root: Path):
_step("Setting up sysv service")
shutil.copy(
str(module_root / 'service_cfg/bakmgr.sysv'),
'/etc/init.d/bakmgr',
)
Path('/etc/init.d/bakmgr').chmod(0o755)
_print_and_call(['chkconfig', 'bakmgr', 'on'])
if _print_and_call(['service', 'bakmgr', 'status']):
_print_and_call(['service', 'bakmgr', 'start'])
def install_autocomplete(module_root: Path):
"""Setup /etc/bash_completion.d/bakmgr"""
if not Path('/etc/bash_completion.d').is_dir():
return
if not Path('/etc/profile.d/bash_completion.sh').is_file():
return
dest = '/etc/bash_completion.d/bakmgr'
_step(f'Setting up autocomplete in {dest}')
src = str(module_root / 'service_cfg/bakmgr-autocomplete.sh')
shutil.copyfile(src, dest)
def install_logrotate(module_root: Path):
"""Setup /etc/logrotate.d/bakmgr"""
dest = '/etc/logrotate.d/bakmgr'
if Path(dest).exists() or not Path('/etc/logrotate.d').is_dir():
return
_step(f'Setting up logrotate in {dest}')
src = str(module_root / 'service_cfg/bakmgr.logrotate')
shutil.copyfile(src, dest)
def install_config():
"""Setup /etc/bakmgr/*.yaml"""
Path('/etc/bakmgr').mkdir(exist_ok=True)
try:
# no longer used
os.remove('/etc/bakmgr/example.yaml')
except OSError:
pass
if CONF_PATH.exists():
return
_step(f'Setting up config in {CONF_PATH}')
Conf().save()
def download_restic():
"""Download and install restic"""
_step("Setting up restic")
print("Running: /usr/bin/restic self-update")
try:
call(['/usr/bin/restic', 'self-update'])
return
except FileNotFoundError:
pass
latest = requests.get(
'https://api.github.com/repos/restic/restic/releases/latest',
timeout=240,
).json()['tag_name'][1:]
print(f'Installing restic v{latest} to /usr/bin/restic')
url = (
f'https://github.com/restic/restic/releases/download/v{latest}/'
f'restic_{latest}_linux_amd64.bz2'
)
with requests.get(url, timeout=240, stream=True) as req:
unzip = bz2.BZ2Decompressor()
with open('/usr/bin/restic', 'wb') as restic:
os.chown('/usr/bin/restic', 0, 0)
os.chmod('/usr/bin/restic', 0o755)
for chunk in req.iter_content(chunk_size=512):
restic.write(unzip.decompress(chunk))
def install_cron(module_root: Path):
"""Setup /etc/cron.d/bakmgr"""
_step("Checking crond")
if not os.path.isdir('/etc/cron.d'):
_warn(
"crond is not installed or this crond "
"implementation does not read /etc/cron.d"
)
return
print("Setting up cron at /etc/cron.d/bakmgr")
src = str(module_root / 'service_cfg/bakmgr.cron')
shutil.copyfile(src, '/etc/cron.d/bakmgr')
try:
check_call(['killall', '-HUP', '-r', '^crond?$'])
except (OSError, CalledProcessError):
_warn('Failed to restart crond')
def register(host: str) -> Union[str, None]:
"""Setup /etc/bakmgr/.auth.json"""
_step("Checking registration")
auth_file = Path('/etc/bakmgr/.auth.json')
if auth_file.is_file():
print('Already registered:', auth_file, 'exists')
return
svr_class = 'sm_vps' if is_vps() else 'sm_ded'
print(f"Registering as {host!r}...")
try:
response = requests.post(
REGISTER_URL,
timeout=180.0,
data={
'host': host,
'svr_class': svr_class,
},
).json()
except (requests.RequestException, ValueError) as exc:
print(exc, file=sys.stderr)
return
if response['status'] != 0: # error code from backup authority
print(response['data'], file=sys.stderr)
return
data = {
'apiuser': response['data']['apiuser'],
'authkey': response['data']['authkey'],
}
with auth_file.open('w', encoding='ascii') as handle:
os.chown(str(auth_file), 0, 0)
auth_file.chmod(0o600)
json.dump(data, handle)
if __name__ == '__main__':
main()
Zerion Mini Shell 1.0