2018-10-02 22:13:39 +02:00
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
|
2018-10-15 15:40:40 +02:00
|
|
|
import hashlib
|
2018-10-02 22:13:39 +02:00
|
|
|
import json
|
2018-10-15 14:58:24 +02:00
|
|
|
import os
|
2018-10-02 22:13:39 +02:00
|
|
|
import requests
|
|
|
|
import shutil
|
2018-10-15 14:58:24 +02:00
|
|
|
import subprocess
|
2018-10-02 22:13:39 +02:00
|
|
|
import tempfile
|
|
|
|
|
|
|
|
from cryptography.exceptions import InvalidSignature
|
|
|
|
from cryptography.hazmat.backends import default_backend
|
|
|
|
from cryptography.hazmat.primitives import hashes
|
|
|
|
from cryptography.hazmat.primitives.asymmetric import ec
|
|
|
|
from cryptography.hazmat.primitives.serialization import load_pem_public_key
|
|
|
|
|
|
|
|
CONF_FILE = '/srv/vm/config.json'
|
2018-10-15 12:15:22 +02:00
|
|
|
PUB_FILE = '/srv/vm/packages.pub'
|
2018-10-02 22:13:39 +02:00
|
|
|
LXC_ROOT = '/var/lib/lxc'
|
|
|
|
|
|
|
|
class PackageManager:
|
|
|
|
def __init__(self):
|
|
|
|
# Load JSON configuration
|
|
|
|
with open(CONF_FILE, 'r') as f:
|
|
|
|
self.conf = json.load(f)
|
2018-10-15 14:58:24 +02:00
|
|
|
self.repo_url = self.conf['host']['repo']
|
2018-10-02 22:13:39 +02:00
|
|
|
self.online_packages = {}
|
|
|
|
|
|
|
|
def save_conf(self):
|
|
|
|
# Save a sorted JSON configuration object with indentation
|
|
|
|
with open(CONF_FILE, 'w') as f:
|
|
|
|
json.dump(self.conf, f, sort_keys=True, indent=4)
|
|
|
|
|
2018-10-15 15:40:40 +02:00
|
|
|
def fetch_online_packages(self):
|
2018-10-02 22:13:39 +02:00
|
|
|
# Fetches and verifies online packages. Can raise InvalidSignature
|
2018-10-15 14:58:24 +02:00
|
|
|
packages = requests.get('{}/packages'.format(self.repo_url)).content
|
|
|
|
packages_sig = requests.get('{}/packages.sig'.format(self.repo_url)).content
|
2018-10-15 12:15:22 +02:00
|
|
|
with open(PUB_FILE, 'rb') as f:
|
2018-10-02 22:13:39 +02:00
|
|
|
pub_key = load_pem_public_key(f.read(), default_backend())
|
|
|
|
pub_key.verify(packages_sig, packages, ec.ECDSA(hashes.SHA512()))
|
2018-10-15 15:40:40 +02:00
|
|
|
self.online_packages = json.loads(packages)
|
2018-10-02 22:13:39 +02:00
|
|
|
|
|
|
|
def install_package(self, name):
|
2018-10-15 15:40:40 +02:00
|
|
|
self.fetch_online_packages()
|
2018-10-02 22:13:39 +02:00
|
|
|
for dep in self.get_deps(name):
|
2018-10-15 12:15:22 +02:00
|
|
|
if dep not in self.conf['packages']:
|
2018-10-15 15:40:40 +02:00
|
|
|
self.download_package(dep)
|
|
|
|
self.register_package(dep)
|
2018-10-02 22:13:39 +02:00
|
|
|
self.setup_package()
|
|
|
|
|
|
|
|
def download_package(self, name):
|
|
|
|
# Downloads, verifies, unpacks and sets up a package
|
2018-10-15 14:58:24 +02:00
|
|
|
tmp_archive = tempfile.mkstemp('.tar.xz')[1]
|
2018-10-15 12:15:22 +02:00
|
|
|
r = requests.get('{}/{}.tar.xz'.format(self.repo_url, name), auth=('test', 'txUqqZLaM.Z;3E2E'), stream=True) # TODO: Remove the testing password
|
2018-10-15 14:58:24 +02:00
|
|
|
with open(tmp_archive, 'wb') as f:
|
|
|
|
for chunk in r.iter_content(chunk_size=65536):
|
2018-10-02 22:13:39 +02:00
|
|
|
if chunk:
|
|
|
|
f.write(chunk)
|
|
|
|
# Verify hash
|
2018-10-15 14:58:24 +02:00
|
|
|
if self.online_packages[name]['sha512'] != hash_file(tmp_archive):
|
2018-10-02 22:13:39 +02:00
|
|
|
raise InvalidSignature(name)
|
|
|
|
# Unpack
|
2018-10-15 14:58:24 +02:00
|
|
|
subprocess.run(['tar', 'xJf', tmp_archive], cwd=LXC_ROOT)
|
|
|
|
os.unlink(tmp_archive)
|
2018-10-02 22:13:39 +02:00
|
|
|
|
2018-10-15 14:58:24 +02:00
|
|
|
def register_package(self, name):
|
|
|
|
metadata = self.online_packages[name]
|
2018-10-15 12:15:22 +02:00
|
|
|
self.conf['packages'][name] = {
|
2018-10-02 22:13:39 +02:00
|
|
|
'version': metadata['version'],
|
|
|
|
}
|
2018-10-15 15:40:40 +02:00
|
|
|
if 'host' in metadata:
|
2018-10-15 12:15:22 +02:00
|
|
|
self.conf['apps'][name] = {
|
|
|
|
'title': metadata['title'],
|
|
|
|
'host': metadata['host'],
|
|
|
|
'login': 'N/A',
|
|
|
|
'password': 'N/A',
|
|
|
|
'visible': False
|
|
|
|
}
|
2018-10-02 22:13:39 +02:00
|
|
|
self.save_conf()
|
|
|
|
|
|
|
|
def setup_package(self):
|
|
|
|
setup_dir = os.path.join(LXC_ROOT, 'setup')
|
|
|
|
setup_script = os.path.join(LXC_ROOT, 'setup.sh')
|
|
|
|
if os.path.exists(setup_script):
|
|
|
|
subprocess.run(setup_script)
|
|
|
|
os.unlink(setup_script)
|
|
|
|
if os.path.exists(setup_dir):
|
|
|
|
shutil.rmtree(setup_dir)
|
|
|
|
|
|
|
|
def get_deps(self, name):
|
2018-10-15 14:58:24 +02:00
|
|
|
deps = self.online_packages[name]['deps'].copy()
|
2018-10-02 22:13:39 +02:00
|
|
|
for dep in deps:
|
|
|
|
deps[:0] = [d for d in self.get_deps(dep) if d not in deps]
|
2018-10-15 14:58:24 +02:00
|
|
|
deps.append(name)
|
2018-10-02 22:13:39 +02:00
|
|
|
return deps
|
|
|
|
|
|
|
|
def hash_file(file_path):
|
|
|
|
sha512 = hashlib.sha512()
|
|
|
|
with open(file_path, 'rb') as f:
|
|
|
|
while True:
|
|
|
|
data = f.read(65536)
|
|
|
|
if not data:
|
|
|
|
break
|
|
|
|
sha512.update(data)
|
|
|
|
return sha512.hexdigest()
|