blitz_api/app/routers/setup.py
/rootzoll 2962b522b7
Fix Update Setup (#107)
* #104 fix default value
* handle update state on setup
2022-06-06 15:36:30 +02:00

343 lines
13 KiB
Python

import logging
from pickle import FALSE
from xmlrpc.client import boolean
from aioredis import Redis
from fastapi import APIRouter, HTTPException, status
from fastapi.params import Depends
from fastapi_plugins import depends_redis
from pydantic import BaseModel
from setuptools import setup
from app.auth.auth_bearer import JWTBearer
from app.auth.auth_handler import sign_jwt
from app.repositories.system import name_valid, password_valid, shutdown
from app.utils import call_script, parse_key_value_lines, redis_get
router = APIRouter(prefix="/setup", tags=["Setup"])
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 project
# 4) setup = a fresh blitz to setup
@router.get("/status")
async def get_status():
setupPhase = await redis_get("setupPhase")
state = await redis_get("state")
message = await redis_get("message")
if setupPhase == "done":
try:
btc_default_sync_initial_done = await redis_get(
"btc_default_sync_initial_done"
)
if btc_default_sync_initial_done == "1":
initialsync = "done"
else:
initialsync = "running"
except:
initialsync = ""
else:
initialsync = ""
return {
"setupPhase": setupPhase,
"state": state,
"message": message,
"initialsync": initialsync,
}
# if setupPhase!="done" && state="waitsetup" then
# 'setup/setup_start_info' should be called
# 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():
# first check that node is really in setup state
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")
return HTTPException(status.status.HTTP_405_METHOD_NOT_ALLOWED)
# get all the additional info needed to do setup dialog
hddGotMigrationData = await redis_get("hddGotMigrationData")
hddGotBlockchain = await redis_get("hddBlocksBitcoin")
migrationMode = await redis_get("migrationMode")
lan = await redis_get("internet_localip")
tor = await redis_get("tor_web_addr")
# return info as JSON
return {
"setupPhase": setupPhase,
"migrationMode": migrationMode, # 'normal', 'outdatedLightning'
"hddGotMigrationData": hddGotMigrationData, # 'umbrel', 'mynode', 'citadel'
"hddGotBlockchain": hddGotBlockchain,
"ssh_login": f"ssh admin@{lan}",
"tor_web_ui": tor,
}
def write_text_file(filename: str, arrayOfLines):
with open(filename, "w", encoding="utf-8") as f:
f.write("\n".join(arrayOfLines))
class StartDoneData(BaseModel):
hostname: str = ""
forceFreshSetup: bool = False
keepBlockchain: bool = True
lightning: str = ""
passwordA: str = ""
passwordB: str = ""
passwordC: str = ""
# 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")
async def setup_start_done(data: StartDoneData):
# first check that node is really in setup state
setupPhase = await redis_get("setupPhase")
state = await redis_get("state")
hddGotBlockchain = await redis_get("hddBlocksBitcoin")
if state != "waitsetup":
logging.warning(f"/setup-start-done can only be called when nodes awaits setup")
return HTTPException(status.HTTP_405_METHOD_NOT_ALLOWED)
# check if a fresh setup is forced
if data.forceFreshSetup:
logging.warning(f"forcing node to fresh setup")
setupPhase = "setup"
#### SETUP ####
if setupPhase == "setup":
if name_valid(data.hostname) == False:
logging.warning(f"hostname is not valid")
return HTTPException(status.HTTP_400_BAD_REQUEST)
if (
data.lightning != "lnd"
and data.lightning != "cl"
and data.lightning != "none"
):
logging.warning(f"lightning is not valid")
if password_valid(data.passwordA) == False:
logging.warning(f"passwordA is not valid")
return HTTPException(status.HTTP_400_BAD_REQUEST)
if password_valid(data.passwordB) == False:
logging.warning(f"passwordB is not valid")
return HTTPException(status.HTTP_400_BAD_REQUEST)
if data.lightning != "none" and password_valid(data.passwordC) == False:
logging.warning(f"passwordC is not valid")
return HTTPException(status.HTTP_400_BAD_REQUEST)
if hddGotBlockchain != "1" and data.keepBlockchain:
logging.warning(f"cannot keep blockchain that does not exists")
return HTTPException(status.HTTP_400_BAD_REQUEST)
if data.keepBlockchain:
formatHDD = 0
cleanHDD = 1
else:
formatHDD = 1
cleanHDD = 0
write_text_file(
setupFilePath,
[
f"formatHDD={formatHDD}",
f"cleanHDD={cleanHDD}",
"network=bitcoin",
"chain=main",
f"lightning={data.lightning}",
f"hostname={data.hostname}",
"setPasswordA=1",
"setPasswordB=1",
"setPasswordC=1",
f"passwordA='{data.passwordA}'",
f"passwordB='{data.passwordB}'",
f"passwordC='{data.passwordC}'",
"",
],
)
#### RECOVERY ####
elif setupPhase == "recovery" or setupPhase == "update":
logging.warning(f"check recovery data")
if password_valid(data.passwordA) == False:
logging.warning(f"passwordA is not valid")
return HTTPException(status.HTTP_400_BAD_REQUEST)
write_text_file(
setupFilePath, ["setPasswordA=1", f"passwordA='{data.passwordA}'"]
)
#### MIGRATION ####
elif setupPhase == "migration":
logging.warning(f"check migration data")
hddGotMigrationData = await redis_get("hddGotMigrationData")
if hddGotMigrationData == "":
logging.warning(f"hddGotMigrationData is not available")
return HTTPException(status.HTTP_400_BAD_REQUEST)
if password_valid(data.passwordA) == False:
logging.warning(f"passwordA is not valid")
return HTTPException(status.HTTP_400_BAD_REQUEST)
if password_valid(data.passwordB) == False:
logging.warning(f"passwordB is not valid")
return HTTPException(status.HTTP_400_BAD_REQUEST)
if password_valid(data.passwordC) == False:
logging.warning(f"passwordC is not valid")
return HTTPException(status.HTTP_400_BAD_REQUEST)
write_text_file(
setupFilePath,
[
f"migrationOS={hddGotMigrationData}",
"setPasswordA=1",
"setPasswordB=1",
"setPasswordC=1",
f"passwordA='{data.passwordA}'",
f"passwordB='{data.passwordB}'",
f"passwordC='{data.passwordC}'",
],
)
else:
logging.warning(f"not handled setupPhase state ({setupPhase})")
return HTTPException(status.HTTP_405_METHOD_NOT_ALLOWED)
await call_script("/home/admin/_cache.sh set state waitprovision")
# TODO: Following input parameters:
# lightning='lnd', 'cl' or 'none'
# network=bitcoin
# chain=main
# hostname=[string]
# passwordA=[string]
# passwordB=[string]
# passwordC=[string] (might be empty of no lightning was chosen)
# lndrescue=[path] (might be used later if lnd rescue file upload is offered)
# clrescue=[path] (might be used later if c-lightning rescue file upload is offered)
# seedWords= (might be used later if we offer recover from seed words)
# seedPassword= (might be used later if we offer recover from seed words)
# migrationFile=[path] (might be used later if we offer raspiblitz migration)
# 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 sign_jwt()
# WebUI now loops getting status until state=`waitfinal` then calls:
@router.get("/setup-final-info", dependencies=[Depends(JWTBearer())])
async def setup_final_info():
# TODO: return info on setup final
# during the process some data might be written to /var/cache/raspiblitz/temp/raspiblitz.setup
# seedwordsNEW='${seedwords}
# seedwords6x4NEW='${seedwords6x4}
# syncProgressFull=[percent] = (later WebUi can offer sync from another RaspiBlitz)
# first check that node is really in setup state
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})"
)
return HTTPException(status.HTTP_405_METHOD_NOT_ALLOWED)
result_lines = []
with open(setupFilePath, "r") as setup_file:
result_lines = setup_file.readlines()
data = parse_key_value_lines(result_lines)
try:
seedwordsNEW = data["seedwordsNEW"]
except:
seedwordsNEW = ""
return {"seedwordsNEW": seedwordsNEW}
# When WebUI displayed seed words & user confirmed write the calls:
@router.post("/setup-final-done", dependencies=[Depends(JWTBearer())])
async def setup_final_done():
# first check that node is really in setup state
setupPhase = await redis_get("setupPhase")
state = await redis_get("state")
if state != "waitfinal":
logging.warning(f"/setup-final-done can only be called when nodes awaits final")
return HTTPException(status.HTTP_405_METHOD_NOT_ALLOWED)
await call_script("/home/admin/_cache.sh set state donefinal")
return {"state": "donefinal"}
@router.get("/shutdown")
async def get_shutdown():
# only allow unauthorized shutdowns during setup
setupPhase = await redis_get("setupPhase")
state = await redis_get("state")
if setupPhase == "done":
logging.warning(f"can only be called when the nodes is not finalized yet")
return HTTPException(status.status.HTTP_405_METHOD_NOT_ALLOWED)
if state != "waitsetup":
logging.warning(f"can only be called when nodes awaits setup")
return HTTPException(status.status.HTTP_405_METHOD_NOT_ALLOWED)
# do the shutdown
return await shutdown(False)
# When WebUI displayed seed words & user confirmed write the calls:
@router.post("/setup-sync-info", dependencies=[Depends(JWTBearer())])
async def setup_sync_info():
# first check that node is really in setup state
setupPhase = await redis_get("setupPhase")
if setupPhase != "done":
logging.warning(f"sync info not available yet")
return HTTPException(status.HTTP_405_METHOD_NOT_ALLOWED)
try:
blitz_sync_initial_done = await redis_get("blitz_sync_initial_done")
if blitz_sync_initial_done == "1":
initialsync = "done"
else:
initialsync = "running"
btc_default_ready = await redis_get("btc_default_ready")
btc_default_sync_percentage = await redis_get("btc_default_sync_percentage")
btc_default_peers = await redis_get("btc_default_peers")
system_count_start_blockchain = await redis_get("system_count_start_blockchain")
except:
initialsync = ""
btc_default_ready = ""
btc_default_sync_percentage = ""
btc_default_peers = ""
system_count_start_blockchain = "0"
try:
ln_default = await redis_get("lightning")
ln_default_ready = await redis_get("ln_default_ready")
ln_default_locked = await redis_get("ln_default_locked")
system_count_start_lightning = await redis_get("system_count_start_lightning")
except:
ln_default = ""
ln_default_ready = ""
ln_default_locked = ""
system_count_start_lightning = "0"
return {
"initialsync": initialsync,
"btc_default": "bitcoin",
"btc_default_ready": btc_default_ready,
"btc_default_sync_percentage": btc_default_sync_percentage,
"btc_default_peers": btc_default_peers,
"system_count_start_blockchain": system_count_start_blockchain,
"ln_default": ln_default,
"ln_default_ready": ln_default_ready,
"ln_default_locked": ln_default_locked,
"system_count_start_lightning": system_count_start_lightning,
}