139 lines
5.1 KiB
Python
139 lines
5.1 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
import json
|
|
import os
|
|
import requests
|
|
import subprocess
|
|
import time
|
|
|
|
class InstallItem:
|
|
def __init__(self):
|
|
self.bytes_total = 1
|
|
self.bytes_downloaded = 0
|
|
|
|
@property
|
|
def percent_downloaded(self):
|
|
return round(self.bytes_downloaded / self.bytes_total * 100)
|
|
|
|
class AppMgr:
|
|
def __init__(self, conf):
|
|
self.conf = conf
|
|
self.online_packages = {}
|
|
|
|
def start_app(self, item):
|
|
# Start the actual app service
|
|
app = item.key
|
|
if app in self.conf['apps'] and not self.is_service_started(app):
|
|
self.start_service(app)
|
|
|
|
def start_service(self, service):
|
|
subprocess.run(['/sbin/service', service, 'start'], check=True)
|
|
|
|
def stop_app(self, item):
|
|
# Stop the actual app service
|
|
app = item.key
|
|
if app in self.conf['apps'] and self.is_service_started(app):
|
|
self.stop_service(app)
|
|
# Stop the app service's dependencies if they are not used by another running app
|
|
deps = self.get_services_deps()
|
|
for dep in self.get_service_deps(app):
|
|
if not any([self.is_service_started(d) for d in deps[dep]]):
|
|
self.stop_service(dep)
|
|
|
|
def stop_service(self, service):
|
|
subprocess.run(['/sbin/service', service, 'stop'], check=True)
|
|
|
|
def update_app_visibility(self, app, visible):
|
|
# Update visibility for the app in the configuration
|
|
if app in self.conf['apps']:
|
|
self.conf['apps'][app]['visible'] = visible
|
|
self.conf.save()
|
|
|
|
def update_app_autostart(self, app, enabled):
|
|
# Add/remove the app to OpenRC default runlevel
|
|
if app in self.conf['apps']:
|
|
subprocess.run(['/sbin/rc-update', 'add' if enabled else 'del', app])
|
|
|
|
def is_service_started(self, app):
|
|
# Check OpenRC service status without calling any binary
|
|
return os.path.exists(os.path.join('/run/openrc/started', app))
|
|
|
|
def is_service_autostarted(self, app):
|
|
# Check OpenRC service enablement
|
|
return os.path.exists(os.path.join('/etc/runlevels/default', app))
|
|
|
|
def install_app(self, item):
|
|
# Main installation function. Wrapper for installation via native package manager
|
|
item.data = InstallItem()
|
|
# Alpine apk provides machine-readable progress in bytes_downloaded/bytes_total format output to file descriptor of choice
|
|
pipe_rfd, pipe_wfd = os.pipe()
|
|
with subprocess.Popen(['apk', '--progress-fd', str(pipe_wfd), '--no-cache', 'add', 'vm-{}@vm'.format(item.key)], pass_fds=[pipe_wfd]) as p:
|
|
while p.poll() == None:
|
|
time.sleep(0.1)
|
|
# Read pipe
|
|
data = b''
|
|
while True:
|
|
chunk = os.read(pipe_rfd, 8192)
|
|
data += chunk
|
|
if len(chunk) < 8192:
|
|
break
|
|
# Parse last apk progress line
|
|
progress = data.decode().splitlines()[-1].split('/')
|
|
item.data.bytes_downloaded = progress[0]
|
|
item.data.bytes_total = progress[1]
|
|
# Close pipe
|
|
os.close(pipe_rfd)
|
|
os.close(pipe_wfd)
|
|
|
|
def uninstall_app(self, item):
|
|
# Main uninstallation function. Wrapper for uninstallation via native package manager
|
|
app = item.key
|
|
self.stop_app(item)
|
|
if self.is_service_autostarted(app):
|
|
self.update_app_autostart(app, False)
|
|
if name in self.conf['apps']:
|
|
del self.conf['apps'][name]
|
|
subprocess.run(['apk', '--no-cache', 'del', 'vm-{}@vm'.format(app)])
|
|
|
|
def fetch_online_packages(self, repo_conf):
|
|
# Fetches list of online packages
|
|
auth = (repo_conf['user'], repo_conf['pwd']) if repo_conf['user'] else None
|
|
packages = requests.get('{}/packages.json'.format(repo_conf['url']), auth=auth, timeout=5)
|
|
if packages.status_code != 200:
|
|
return packages.status_code
|
|
self.online_packages = json.loads(packages.content)
|
|
return 200
|
|
|
|
def get_services_deps(self):
|
|
# Fisrt, build a dictionary of {app: [needs]}
|
|
needs = {}
|
|
for app in self.conf['apps'].copy():
|
|
needs[app] = self.get_service_deps(app)
|
|
# Then reverse it to {need: [apps]}
|
|
deps = {}
|
|
for app, need in needs.items():
|
|
for n in need:
|
|
deps.setdefault(n, []).append(app)
|
|
return deps
|
|
|
|
def get_service_deps(self, app):
|
|
# Get "need" line from init script and split it to list
|
|
try:
|
|
with open(os.path.join('/etc/init.d', app), 'r') as f:
|
|
return [l for l in f.readlines() if l.strip().startswith('need')][0].split()[1:]
|
|
except:
|
|
pass
|
|
return []
|
|
|
|
def update_common_settings(self, email, gmaps_api_key):
|
|
# Update common configuration values
|
|
self.conf['common']['email'] = email
|
|
self.conf['common']['gmaps-api-key'] = gmaps_api_key
|
|
self.conf.save()
|
|
|
|
def shutdown_vm(self):
|
|
subprocess.run(['/sbin/poweroff'])
|
|
|
|
def reboot_vm(self):
|
|
subprocess.run(['/sbin/reboot'])
|