mirror of
https://github.com/netinvent/npbackup.git
synced 2025-11-10 06:01:39 +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…
Add table
Reference in a new issue