mirror of
https://github.com/netinvent/npbackup.git
synced 2025-02-24 06:25:17 +08:00
Upgrade server - initial work
This commit is contained in:
parent
fcf5675356
commit
23e7184340
7 changed files with 306 additions and 0 deletions
4
upgrade_server/requrements.txt
Normal file
4
upgrade_server/requrements.txt
Normal file
|
@ -0,0 +1,4 @@
|
|||
fastapi
|
||||
fastapi-offline>=1.5.0
|
||||
uvicorn
|
||||
pydantic
|
66
upgrade_server/upgrade_server.py
Normal file
66
upgrade_server/upgrade_server.py
Normal 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)
|
0
upgrade_server/upgrade_server/__init__.py
Normal file
0
upgrade_server/upgrade_server/__init__.py
Normal file
104
upgrade_server/upgrade_server/api.py
Normal file
104
upgrade_server/upgrade_server/api.py
Normal 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),
|
||||
)
|
||||
|
39
upgrade_server/upgrade_server/configuration.py
Normal file
39
upgrade_server/upgrade_server/configuration.py
Normal 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)
|
54
upgrade_server/upgrade_server/crud.py
Normal file
54
upgrade_server/upgrade_server/crud.py
Normal 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
|
39
upgrade_server/upgrade_server/models/files.py
Normal file
39
upgrade_server/upgrade_server/models/files.py
Normal 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
|
||||
|
Loading…
Reference in a new issue