Upgrade server - initial work

This commit is contained in:
Orsiris de Jong 2023-01-31 21:04:04 +01:00
parent fcf5675356
commit 23e7184340
7 changed files with 306 additions and 0 deletions

View file

@ -0,0 +1,4 @@
fastapi
fastapi-offline>=1.5.0
uvicorn
pydantic

View file

@ -0,0 +1,66 @@
#! /usr/bin/env python
# -*- coding: utf-8 -*-
#
# This file is part of npbackup
__intname__ = "npbackup.upgrade_server.upgrade_server"
__author__ = "Orsiris de Jong"
__copyright__ = "Copyright (C) 2023 NetInvent"
__license__ = "GPL-3.0-only"
__build__ = "202303101"
__version__ = "0.0.1"
DEVEL=True
import sys
import os
from upgrade_server import configuration
from ofunctions.logger_utils import logger_get_logger
#ROOT_DIR = os.path.abspath(os.path.dirname(__file__))
#config_file = 'upgrade_server.conf'
#config_dict = configuration.load_config(os.path.join(ROOT_DIR, config_file)
config_dict = configuration.load_config()
try:
listen = config_dict['http_server']['listen']
except KeyError:
listen = None
try:
port = config_dict['http_server']['port']
except KeyError:
listen = None
if DEVEL:
import uvicorn as server
server_args = {
'workers': 1,
'log_level': "debug",
'reload': True,
'host': listen if listen else '0.0.0.0',
'port': port if port else 8080
}
else:
import gunicorn as server
server_args = {
'workers': 8,
'reload': False,
'host': listen if listen else '0.0.0.0',
'port': port if port else 8080
}
logger = logger_get_logger()
if __name__ == "__main__":
try:
server.run("upgrade_server.api:app", **server_args)
except KeyboardInterrupt as exc:
logger.error("Program interrupted by keyoard: {}".format(exc))
sys.exit(200)
except Exception as exc:
logger.error("Program interrupted by error: {}".format(exc))
logger.critical('Trace:', exc_info=True)
sys.exit(201)

View file

@ -0,0 +1,104 @@
#! /usr/bin/env python3
# -*- coding: utf-8 -*-
__appname__ = "npbackup.upgrader"
__author__ = "Alan Smithee"
__build__ = "2022112201"
__version__ = "1.0-beta"
from typing import Literal
import logging
import secrets
from fastapi import FastAPI, HTTPException, Response, Depends, status
from fastapi.security import HTTPBasic, HTTPBasicCredentials
from fastapi_offline import FastAPIOffline
from upgrade_server.models.files import FileGet, FileSend, Platform, Arch
import upgrade_server.crud as crud
import upgrade_server.configuration as configuration
config_dict = configuration.load_config()
logger = logging.getLogger()
#### Create app
#app = FastAPI() # standard FastAPI initialization
app = FastAPIOffline() # Offline FastAPI initialization, allows /docs to not use online CDN
security = HTTPBasic()
def get_current_username(credentials: HTTPBasicCredentials = Depends(security)):
current_username_bytes = credentials.username.encode("utf8")
correct_username_bytes = config_dict['http_server']['username'].encode('utf-8')
is_correct_username = secrets.compare_digest(
current_username_bytes, correct_username_bytes
)
current_password_bytes = credentials.password.encode("utf8")
correct_password_bytes = config_dict['http_server']['password'].encode('utf-8')
is_correct_password = secrets.compare_digest(
current_password_bytes, correct_password_bytes
)
if not (is_correct_username and is_correct_password):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect email or password",
headers={"WWW-Authenticate": "Basic"},
)
return credentials.username
@app.get("/")
async def api_root():
if crud.is_enabled():
return {
"app": __appname__,
}
else:
return {
"app": "Currently under maintenance"
}
@app.get("/upgrades/{platform}/{arch}", response_model=FileSend, status_code=200)
async def upgrades(platform: Platform, arch: Arch, auth = Depends(get_current_username)):
file = FileGet(platform=platform, arch=arch)
try:
result = crud.get_file(file)
if not result:
raise HTTPException(
status_code=404,
detail="Not found"
)
return result
except HTTPException:
raise
except Exception as exc:
logger.debug("Cannot get file: {}".format(exc), exc_info=True)
raise HTTPException(
status_code=400,
detail="Cannot get file: {}".format(exc),
)
@app.get("/upgrades/{platform}/{arch}/data", status_code=200)
async def download(platform: Platform, arch: Arch, auth = Depends(get_current_username)):
file = FileGet(platform=platform, arch=arch)
try:
result = crud.get_file(file, content=True)
if not result:
raise HTTPException(
status_code=404,
detail="Not found"
)
headers = {
"Content-Disposition": 'attachment; filename="npbackup"'
}
return Response(content=result, media_type="application/dat", headers=headers)
except HTTPException:
raise
except Exception as exc:
logger.debug("Cannot get file: {}".format(exc), exc_info=True)
raise HTTPException(
status_code=400,
detail="Cannot get file: {}".format(exc),
)

View file

@ -0,0 +1,39 @@
#! /usr/bin/env python
# -*- coding: utf-8 -*-
#
# This file is part of npbackup
__intname__ = "npbackup.upgrade_server.configuration"
__author__ = "Orsiris de Jong"
__copyright__ = "Copyright (C) 2023 NetInvent"
__license__ = "GPL-3.0-only"
__build__ = "202303101"
__version__ = "0.0.1"
import os
from ruamel.yaml import YAML
from logging import getLogger
ROOT_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
config_file = "upgrade_server.conf"
default_config_path = os.path.join(ROOT_DIR, config_file)
logger = getLogger(__intname__)
def load_config(config_file: str = default_config_path):
"""
Using ruamel.yaml preserves comments and order of yaml files
"""
logger.debug("Using configuration file {}".format(config_file))
with open(config_file, "r", encoding="utf-8") as file_handle:
# RoundTrip loader is default and preserves comments and ordering
yaml = YAML(typ="rt")
config_dict = yaml.load(file_handle)
return config_dict
def save_config(config_file, config_dict):
with open(config_file, "w", encoding="utf-8") as file_handle:
yaml = YAML(typ="rt")
yaml.dump(config_dict, file_handle)

View file

@ -0,0 +1,54 @@
#! /usr/bin/env python
# -*- coding: utf-8 -*-
#
# This file is part of npbackup
__intname__ = "npbackup.upgrade_server.crud"
__author__ = "Orsiris de Jong"
__copyright__ = "Copyright (C) 2023 NetInvent"
__license__ = "GPL-3.0-only"
__build__ = "202303101"
__version__ = "0.0.1"
import os
from typing import Optional, Union
from logging import getLogger
import hashlib
from upgrade_server.models.files import FileGet, FileSend
import upgrade_server.configuration as configuration
config_dict = configuration.load_config()
logger = getLogger(__intname__)
def sha256sum_data(data):
# type: (bytes) -> str
"""
Returns sha256sum of some data
"""
sha256 = hashlib.sha256()
sha256.update(data)
return sha256.hexdigest()
def is_enabled() -> bool:
return not os.path.isfile("DISABLED")
def get_file(file: FileGet, content: bool = False) -> Optional[Union[FileSend, bytes]]:
possible_filename = 'npbackup{}'.format(
'.exe' if file.platform.value == 'windows' else ''
)
path = os.path.join(config_dict['upgrades']['data_root'], file.platform.value, file.arch.value, possible_filename)
logger.info("Searching for %s", path)
if not os.path.isfile(path):
return None
with open(path, 'rb') as fh:
bytes = fh.read()
if content:
return bytes
length = len(bytes)
sha256 = sha256sum_data(bytes)
file_send = FileSend(arch=file.arch.value, platform=file.platform.value, sha256sum=sha256, filename=possible_filename, file_length=length)
return file_send

View file

@ -0,0 +1,39 @@
#! /usr/bin/env python
# -*- coding: utf-8 -*-
#
# This file is part of npbackup
__intname__ = "npbackup.upgrade_server.models.files"
__author__ = "Orsiris de Jong"
__copyright__ = "Copyright (C) 2023 NetInvent"
__license__ = "GPL-3.0-only"
__build__ = "202303101"
__version__ = "0.0.1"
from enum import Enum
from pydantic import BaseModel, constr
class Platform(Enum):
windows = "windows"
linux = "Linux"
class Arch(Enum):
x86 = "x86"
x64 = "x64"
class FileBase(BaseModel):
arch: Arch
platform: Platform
class FileGet(FileBase):
pass
class FileSend(FileBase):
sha256sum: constr(min_length=64, max_length=64)
filename: str
file_length: int