mirror of
https://github.com/fusion44/blitz_api.git
synced 2026-08-15 12:10:12 +02:00
Merge branch 'main' of https://github.com/fusion44/blitz_api
This commit is contained in:
commit
6ceaacfc23
11 changed files with 132 additions and 74 deletions
|
|
@ -11,7 +11,7 @@ JWT_ALGORITHM = config("algorithm")
|
|||
JWT_EXPIRY_TIME = config("jwt_expiry_time", default=300, cast=int)
|
||||
|
||||
|
||||
def signJWT() -> Dict[str, str]:
|
||||
def sign_jwt() -> Dict[str, str]:
|
||||
payload = {
|
||||
"user_id": "admin",
|
||||
"expires": int(round(time.time() * 1000) + JWT_EXPIRY_TIME),
|
||||
|
|
@ -45,7 +45,7 @@ def handle_local_cookie():
|
|||
|
||||
if enabled:
|
||||
f = open(full_cookie_file_path, "w")
|
||||
f.write(signJWT()["access_token"])
|
||||
f.write(sign_jwt()["access_token"])
|
||||
f.close()
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ from app.auth.auth_handler import (
|
|||
remove_local_cookie,
|
||||
)
|
||||
from app.external.fastapi_versioning import VersionedFastAPI
|
||||
from app.external.sse_startlette import EventSourceResponse
|
||||
from app.external.sse_starlette import EventSourceResponse
|
||||
from app.models.system import APIPlatform
|
||||
from app.repositories.bitcoin import (
|
||||
register_bitcoin_status_gatherer,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import asyncio
|
||||
from os import path
|
||||
import logging
|
||||
import re
|
||||
from os import path
|
||||
|
||||
from decouple import config
|
||||
from fastapi import HTTPException, Request, status
|
||||
|
|
@ -39,7 +39,8 @@ def _check_shell_scripts_status():
|
|||
|
||||
_check_shell_scripts_status()
|
||||
|
||||
async def callScript(scriptPath) -> str:
|
||||
|
||||
async def call_script(scriptPath) -> str:
|
||||
cmd = f"bash {scriptPath}"
|
||||
logging.warning(f"running script: {cmd}")
|
||||
proc = await asyncio.create_subprocess_shell(
|
||||
|
|
@ -54,30 +55,42 @@ async def callScript(scriptPath) -> str:
|
|||
logging.error(stderr.decode())
|
||||
return ""
|
||||
|
||||
def parseKeyValueLines(lines:list) -> dict:
|
||||
|
||||
def parse_key_value_lines(lines: list) -> dict:
|
||||
Dict = {}
|
||||
for line in lines:
|
||||
logging.warning(f"line({line})")
|
||||
if len(line.strip()) == 0: continue
|
||||
if line.strip().startswith('#'): continue
|
||||
if line.find('=') <=0: continue
|
||||
key, value = line.strip().split('=',1)
|
||||
if len(line.strip()) == 0:
|
||||
continue
|
||||
if line.strip().startswith("#"):
|
||||
continue
|
||||
if line.find("=") <= 0:
|
||||
continue
|
||||
key, value = line.strip().split("=", 1)
|
||||
Dict[key] = value.strip('"').strip("'")
|
||||
return Dict
|
||||
|
||||
def parseKeyValueText(text:str) -> dict:
|
||||
return parseKeyValueLines(text.splitlines())
|
||||
|
||||
def passwordValid(password : str):
|
||||
if len(password) < 8: return False
|
||||
if password.find(' ') >= 0: return False
|
||||
return re.match('^[\.a-zA-Z0-9-]*$', password)
|
||||
def parse_key_value_text(text: str) -> dict:
|
||||
return parse_key_value_lines(text.splitlines())
|
||||
|
||||
def nameValid(password : str):
|
||||
if len(password) < 3: return False
|
||||
if password.find(' ') >= 0: return False
|
||||
|
||||
def password_valid(password: str):
|
||||
if len(password) < 8:
|
||||
return False
|
||||
if password.find(" ") >= 0:
|
||||
return False
|
||||
return re.match("^[a-zA-Z0-9]*$", password)
|
||||
|
||||
|
||||
def name_valid(password : str):
|
||||
if len(password) < 3:
|
||||
return False
|
||||
if password.find(' ') >= 0:
|
||||
return False
|
||||
return re.match('^[\.a-zA-Z0-9-_]*$', password)
|
||||
|
||||
|
||||
async def get_system_info() -> SystemInfo:
|
||||
try:
|
||||
return await get_system_info_impl()
|
||||
|
|
|
|||
|
|
@ -1,3 +1,8 @@
|
|||
import asyncio
|
||||
import os
|
||||
|
||||
from decouple import config
|
||||
|
||||
from app.constants import API_VERSION
|
||||
from app.models.system import (
|
||||
APIPlatform,
|
||||
|
|
@ -9,6 +14,8 @@ from app.models.system import (
|
|||
from app.repositories.lightning import get_ln_info
|
||||
from app.utils import redis_get
|
||||
|
||||
SHELL_SCRIPT_PATH = config("shell_script_path")
|
||||
|
||||
|
||||
async def get_system_info_impl() -> SystemInfo:
|
||||
lninfo = await get_ln_info()
|
||||
|
|
@ -34,3 +41,26 @@ async def get_system_info_impl() -> SystemInfo:
|
|||
ssh_address=f"admin@{lan}",
|
||||
chain=lninfo.chains[0].network,
|
||||
)
|
||||
|
||||
|
||||
async def shutdown(reboot: bool):
|
||||
params = ""
|
||||
if reboot:
|
||||
params = "reboot"
|
||||
|
||||
script = os.path.join(SHELL_SCRIPT_PATH, "config.scripts", "blitz.shutdown.sh")
|
||||
cmd = f"sudo bash {script} {params}"
|
||||
|
||||
proc = await asyncio.create_subprocess_shell(
|
||||
cmd,
|
||||
stdout=asyncio.subprocess.PIPE,
|
||||
stderr=asyncio.subprocess.PIPE,
|
||||
)
|
||||
|
||||
stdout, stderr = await proc.communicate()
|
||||
|
||||
print(f"[{cmd!r} exited with {proc.returncode}]")
|
||||
if stdout:
|
||||
print(f"[stdout]\n{stdout.decode()}")
|
||||
if stderr:
|
||||
print(f"[stderr]\n{stderr.decode()}")
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from fastapi.params import Depends
|
|||
import app.repositories.apps as repo
|
||||
import app.routers.apps_docs as docs
|
||||
from app.auth.auth_bearer import JWTBearer
|
||||
from app.external.sse_startlette import EventSourceResponse
|
||||
from app.external.sse_starlette import EventSourceResponse
|
||||
|
||||
_PREFIX = "apps"
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ from fastapi import APIRouter, HTTPException, Request, status
|
|||
from fastapi.params import Depends, Query
|
||||
|
||||
from app.auth.auth_bearer import JWTBearer
|
||||
from app.external.sse_startlette import EventSourceResponse
|
||||
from app.external.sse_starlette import EventSourceResponse
|
||||
from app.models.bitcoind import BlockchainInfo, BtcInfo, FeeEstimationMode, NetworkInfo
|
||||
from app.repositories.bitcoin import (
|
||||
estimate_fee,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import asyncio
|
||||
#from asyncio.windows_events import NULL
|
||||
|
||||
# from asyncio.windows_events import NULL
|
||||
import logging
|
||||
from pickle import FALSE
|
||||
import re
|
||||
|
|
@ -10,28 +11,22 @@ from fastapi.params import Depends
|
|||
from fastapi_plugins import depends_redis
|
||||
from setuptools import setup
|
||||
|
||||
from app.repositories.system import (
|
||||
callScript,
|
||||
parseKeyValueLines,
|
||||
nameValid,
|
||||
passwordValid
|
||||
)
|
||||
|
||||
from app.auth.auth_bearer import JWTBearer
|
||||
from app.auth.auth_handler import signJWT
|
||||
from app.auth.auth_handler import sign_jwt
|
||||
from app.repositories.system import call_script, parse_key_value_lines, password_valid, name_valid, parse_key_value_lines
|
||||
from app.utils import redis_get
|
||||
|
||||
router = APIRouter(prefix="/setup", tags=["Setup"])
|
||||
|
||||
setupFilePath="/var/cache/raspiblitz/temp/raspiblitz.setup"
|
||||
configFilePath="/mnt/hdd/raspiblitz.conf"
|
||||
setupFilePath = "/var/cache/raspiblitz/temp/raspiblitz.setup"
|
||||
configFilePath = "/mnt/hdd/raspiblitz.conf"
|
||||
|
||||
# can always be called without credentials to check if
|
||||
# the system needs or is in setup (setupPhase!="done")
|
||||
# for example in the beginning setupPhase can be (see controlSetupDialog.sh)
|
||||
# 1) recovery = same version on fresh sd card
|
||||
# 2) update = updated version on fresh sd card
|
||||
# 3) migration = hdd got data from another node projcect
|
||||
# 3) migration = hdd got data from another node project
|
||||
# 4) setup = a fresh blitz to setup
|
||||
@router.get("/status")
|
||||
async def get_status():
|
||||
|
|
@ -46,6 +41,7 @@ async def get_status():
|
|||
# We can do the "MIGRATION" option later - because it would need an additional step after formatting hdd
|
||||
# People that need to migrate can do for now by SSH option
|
||||
|
||||
|
||||
@router.get("/setup-start-info")
|
||||
async def setup_start_info():
|
||||
|
||||
|
|
@ -53,9 +49,7 @@ async def setup_start_info():
|
|||
setupPhase = await redis_get("setupPhase")
|
||||
state = await redis_get("state")
|
||||
if state != "waitsetup":
|
||||
logging.warning(
|
||||
f"/setup-start-info can only be called when nodes awaits setup"
|
||||
)
|
||||
logging.warning(f"/setup-start-info can only be called when nodes awaits setup")
|
||||
return HTTPException(status.status.HTTP_405_METHOD_NOT_ALLOWED)
|
||||
|
||||
# get all the additional info needed to do setup dialog
|
||||
|
|
@ -75,10 +69,11 @@ async def setup_start_info():
|
|||
}
|
||||
|
||||
|
||||
def writeTextFile(filename: str, arrayOfLines):
|
||||
def write_text_file(filename: str, arrayOfLines):
|
||||
logging.warning(f"writing {filename}")
|
||||
with open(filename, 'w', encoding='utf-8') as f:
|
||||
f.write('\n'.join(arrayOfLines))
|
||||
with open(filename, "w", encoding="utf-8") as f:
|
||||
f.write("\n".join(arrayOfLines))
|
||||
|
||||
|
||||
# With all this info the WebUi can run its own runs its dialogs and in the end makes a call to
|
||||
@router.post("/setup-start-done")
|
||||
|
|
@ -102,18 +97,18 @@ async def setup_start_done(
|
|||
|
||||
#### SETUP ####
|
||||
if setupPhase == "setup":
|
||||
if nameValid(hostname) == False:
|
||||
if name_valid(hostname) == False:
|
||||
logging.warning(f"hostname is not valid")
|
||||
return HTTPException(status.HTTP_400_BAD_REQUEST)
|
||||
if lightning!="lnd" and lightning!="cl" and lightning!="none":
|
||||
logging.warning(f"lightning is not valid")
|
||||
if passwordValid(passwordA) == False:
|
||||
if password_valid(passwordA) == False:
|
||||
logging.warning(f"passwordA is not valid")
|
||||
return HTTPException(status.HTTP_400_BAD_REQUEST)
|
||||
if passwordValid(passwordB) == False:
|
||||
if password_valid(passwordB) == False:
|
||||
logging.warning(f"passwordB is not valid")
|
||||
return HTTPException(status.HTTP_400_BAD_REQUEST)
|
||||
if lightning!="none" and passwordValid(passwordC)==False:
|
||||
if lightning!="none" and password_valid(passwordC)==False:
|
||||
logging.warning(f"passwordC is not valid")
|
||||
return HTTPException(status.HTTP_400_BAD_REQUEST)
|
||||
if hddGotBlockchain!="1" and keepBlockchain:
|
||||
|
|
@ -125,7 +120,7 @@ async def setup_start_done(
|
|||
else:
|
||||
formatHDD=1
|
||||
cleanHDD=0
|
||||
writeTextFile(setupFilePath,[
|
||||
write_text_file(setupFilePath,[
|
||||
f"formatHDD={formatHDD}",
|
||||
f"cleanHDD={cleanHDD}",
|
||||
"network=bitcoin",
|
||||
|
|
@ -143,10 +138,10 @@ async def setup_start_done(
|
|||
#### RECOVERY ####
|
||||
elif setupPhase == "recovery":
|
||||
logging.warning(f"check recovery data")
|
||||
if passwordValid(passwordA) == False:
|
||||
if password_valid(passwordA) == False:
|
||||
logging.warning(f"passwordA is not valid")
|
||||
return HTTPException(status.HTTP_400_BAD_REQUEST)
|
||||
writeTextFile(setupFilePath,[
|
||||
write_text_file(setupFilePath,[
|
||||
"setPasswordA=1",
|
||||
f"passwordA='{passwordA}'"
|
||||
])
|
||||
|
|
@ -156,7 +151,7 @@ async def setup_start_done(
|
|||
return HTTPException(status.HTTP_405_METHOD_NOT_ALLOWED)
|
||||
|
||||
logging.warning(f"kicking off recovery")
|
||||
await callScript("/home/admin/_cache.sh set state waitprovision")
|
||||
await call_script("/home/admin/_cache.sh set state waitprovision")
|
||||
|
||||
# TODO: Following input parameters:
|
||||
# lightning='lnd', 'cl' or 'none'
|
||||
|
|
@ -174,7 +169,8 @@ async def setup_start_done(
|
|||
# those values get stored in: /var/cache/raspiblitz/temp/raspiblitz.setup
|
||||
# also a skeleton raspiblitz.conf gets created (see controlSetupDialog.sh Line 318)
|
||||
# and then API sets state to `waitprovision` to kick-off provision
|
||||
return signJWT()
|
||||
|
||||
return sign_jwt()
|
||||
|
||||
|
||||
# WebUI now loops getting status until state=`waitfinal` then calls:
|
||||
|
|
@ -190,13 +186,15 @@ async def setup_final_info():
|
|||
setupPhase = await redis_get("setupPhase")
|
||||
state = await redis_get("state")
|
||||
if state != "waitfinal":
|
||||
logging.warning(f"/setup-final-info can only be called when nodes awaits final ({state})")
|
||||
logging.warning(
|
||||
f"/setup-final-info can only be called when nodes awaits final ({state})"
|
||||
)
|
||||
return HTTPException(status.HTTP_405_METHOD_NOT_ALLOWED)
|
||||
|
||||
resultlines=[]
|
||||
with open (setupFilePath, "r") as setupfile:
|
||||
resultlines=setupfile.readlines()
|
||||
data=parseKeyValueLines(resultlines)
|
||||
resultlines = []
|
||||
with open(setupFilePath, "r") as setupfile:
|
||||
resultlines = setupfile.readlines()
|
||||
data = parse_key_value_lines(resultlines)
|
||||
logging.warning(f"data({data})")
|
||||
try:
|
||||
seedwordsNEW = data["seedwordsNEW"]
|
||||
|
|
@ -218,7 +216,5 @@ async def setup_final_done():
|
|||
logging.warning(f"/setup-final-done can only be called when nodes awaits final")
|
||||
return HTTPException(status.HTTP_405_METHOD_NOT_ALLOWED)
|
||||
|
||||
await callScript("/home/admin/_cache.sh set state donefinal")
|
||||
return {
|
||||
"state": "donefinal"
|
||||
}
|
||||
await call_script("/home/admin/_cache.sh set state donefinal")
|
||||
return {"state": "donefinal"}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,26 @@
|
|||
import secrets
|
||||
import logging
|
||||
import secrets
|
||||
|
||||
from decouple import config
|
||||
from fastapi import APIRouter, HTTPException, Request, status
|
||||
from fastapi.params import Depends
|
||||
|
||||
from app.auth.auth_bearer import JWTBearer
|
||||
from app.auth.auth_handler import signJWT
|
||||
from app.external.sse_startlette import EventSourceResponse
|
||||
from app.models.system import LoginInput, RawDebugLogData, SystemInfo
|
||||
from app.auth.auth_handler import sign_jwt
|
||||
from app.external.sse_starlette import EventSourceResponse
|
||||
from app.models.system import APIPlatform, LoginInput, RawDebugLogData, SystemInfo
|
||||
from app.repositories.system import (
|
||||
HW_INFO_YIELD_TIME,
|
||||
PLATFORM,
|
||||
call_script,
|
||||
get_debug_logs_raw,
|
||||
get_hardware_info,
|
||||
get_system_info,
|
||||
parse_key_value_text,
|
||||
password_valid,
|
||||
subscribe_hardware_info,
|
||||
callScript,
|
||||
parseKeyValueText,
|
||||
passwordValid
|
||||
)
|
||||
from app.repositories.system_impl.raspiblitz import shutdown
|
||||
from app.routers.system_docs import (
|
||||
get_debug_logs_raw_desc,
|
||||
get_debug_logs_raw_resp_desc,
|
||||
|
|
@ -48,16 +50,21 @@ async def login(i: LoginInput):
|
|||
|
||||
if platform == "raspiblitz":
|
||||
# script does not work when called from api yet
|
||||
if passwordValid(i.password):
|
||||
result = await callScript(f"/home/admin/config.scripts/blitz.setpassword.sh check-a {i.password}")
|
||||
data = parseKeyValueText(result)
|
||||
if data["correct"] == "1": return signJWT()
|
||||
if password_valid(i.password):
|
||||
result = await call_script(
|
||||
f"/home/admin/config.scripts/blitz.setpassword.sh check-a {i.password}"
|
||||
)
|
||||
data = parse_key_value_text(result)
|
||||
if data["correct"] == "1":
|
||||
return sign_jwt()
|
||||
raise HTTPException(status.HTTP_401_UNAUTHORIZED, detail="Password is wrong")
|
||||
else:
|
||||
match = secrets.compare_digest(i.password, config("login_password", cast=str))
|
||||
if match: return signJWT()
|
||||
if match:
|
||||
return sign_jwt()
|
||||
raise HTTPException(status.HTTP_401_UNAUTHORIZED, detail="Password is wrong")
|
||||
|
||||
|
||||
@router.post(
|
||||
"/refresh-token",
|
||||
name=f"{_PREFIX}.refresh-token",
|
||||
|
|
@ -66,7 +73,7 @@ async def login(i: LoginInput):
|
|||
dependencies=[Depends(JWTBearer())],
|
||||
)
|
||||
def refresh_token():
|
||||
return signJWT()
|
||||
return sign_jwt()
|
||||
|
||||
|
||||
@router.get(
|
||||
|
|
@ -132,8 +139,14 @@ async def hw_info_sub(request: Request):
|
|||
summary="Reboots the system",
|
||||
dependencies=[Depends(JWTBearer())],
|
||||
)
|
||||
def reboot_system():
|
||||
return HTTPException(status.HTTP_501_NOT_IMPLEMENTED)
|
||||
async def reboot_system() -> bool:
|
||||
if PLATFORM == APIPlatform.RASPIBLITZ:
|
||||
await shutdown(True)
|
||||
return True
|
||||
else:
|
||||
raise HTTPException(
|
||||
status.HTTP_501_NOT_IMPLEMENTED, detail="Not implemented on native"
|
||||
)
|
||||
|
||||
|
||||
@router.post(
|
||||
|
|
@ -142,5 +155,11 @@ def reboot_system():
|
|||
summary="Shuts the system down",
|
||||
dependencies=[Depends(JWTBearer())],
|
||||
)
|
||||
def reboot_system():
|
||||
return HTTPException(status.HTTP_501_NOT_IMPLEMENTED)
|
||||
async def shutdown() -> bool:
|
||||
if PLATFORM == APIPlatform.RASPIBLITZ:
|
||||
await shutdown(False)
|
||||
return True
|
||||
else:
|
||||
raise HTTPException(
|
||||
status.HTTP_501_NOT_IMPLEMENTED, detail="Not implemented on native"
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue