machine: validation fixes
Protect the sudo request with a lock. Add retries to the sudo commands. Signed-off-by: Eric Callahan <arksine.code@gmail.com>
This commit is contained in:
parent
f0e4678fab
commit
00c4846d11
|
@ -86,6 +86,7 @@ class Machine:
|
||||||
dist_info['release_info'] = distro.distro_release_info()
|
dist_info['release_info'] = distro.distro_release_info()
|
||||||
self.inside_container = False
|
self.inside_container = False
|
||||||
self.moonraker_service_info: Dict[str, Any] = {}
|
self.moonraker_service_info: Dict[str, Any] = {}
|
||||||
|
self.sudo_req_lock = asyncio.Lock()
|
||||||
self._sudo_password: Optional[str] = None
|
self._sudo_password: Optional[str] = None
|
||||||
sudo_template = config.gettemplate("sudo_password", None)
|
sudo_template = config.gettemplate("sudo_password", None)
|
||||||
if sudo_template is not None:
|
if sudo_template is not None:
|
||||||
|
@ -282,50 +283,53 @@ class Machine:
|
||||||
async def _set_sudo_password(
|
async def _set_sudo_password(
|
||||||
self, web_request: WebRequest
|
self, web_request: WebRequest
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
self._sudo_password = web_request.get_str("password")
|
async with self.sudo_req_lock:
|
||||||
if not await self.check_sudo_access():
|
self._sudo_password = web_request.get_str("password")
|
||||||
self._sudo_password = None
|
if not await self.check_sudo_access():
|
||||||
raise self.server.error("Invalid password, sudo access was denied")
|
self._sudo_password = None
|
||||||
sudo_responses = ["Sudo password successfully set."]
|
raise self.server.error(
|
||||||
restart: bool = False
|
"Invalid password, sudo access was denied"
|
||||||
failed: List[Tuple[SudoCallback, str]] = []
|
|
||||||
failed_msgs: List[str] = []
|
|
||||||
if self.sudo_requests:
|
|
||||||
while self.sudo_requests:
|
|
||||||
cb, msg = self.sudo_requests.pop(0)
|
|
||||||
try:
|
|
||||||
ret = cb()
|
|
||||||
if isinstance(ret, Awaitable):
|
|
||||||
ret = await ret
|
|
||||||
msg, need_restart = ret
|
|
||||||
sudo_responses.append(msg)
|
|
||||||
restart |= need_restart
|
|
||||||
except asyncio.CancelledError:
|
|
||||||
raise
|
|
||||||
except Exception as e:
|
|
||||||
failed.append((cb, msg))
|
|
||||||
failed_msgs.append(str(e))
|
|
||||||
restart = False if len(failed) > 0 else restart
|
|
||||||
self.sudo_requests = failed
|
|
||||||
if not restart and len(sudo_responses) > 1:
|
|
||||||
# at least one successful response and not restarting
|
|
||||||
eventloop = self.server.get_event_loop()
|
|
||||||
eventloop.delay_callback(
|
|
||||||
.05, self.server.send_event,
|
|
||||||
"machine:sudo_alert",
|
|
||||||
{
|
|
||||||
"sudo_requested": self.sudo_requested,
|
|
||||||
"request_messages": self.sudo_request_messages
|
|
||||||
}
|
|
||||||
)
|
|
||||||
if failed_msgs:
|
|
||||||
err_msg = "\n".join(failed_msgs)
|
|
||||||
raise self.server.error(err_msg, 500)
|
|
||||||
if restart:
|
|
||||||
self.restart_moonraker_service()
|
|
||||||
sudo_responses.append(
|
|
||||||
"Moonraker is currently in the process of restarting."
|
|
||||||
)
|
)
|
||||||
|
sudo_responses = ["Sudo password successfully set."]
|
||||||
|
restart: bool = False
|
||||||
|
failed: List[Tuple[SudoCallback, str]] = []
|
||||||
|
failed_msgs: List[str] = []
|
||||||
|
if self.sudo_requests:
|
||||||
|
while self.sudo_requests:
|
||||||
|
cb, msg = self.sudo_requests.pop(0)
|
||||||
|
try:
|
||||||
|
ret = cb()
|
||||||
|
if isinstance(ret, Awaitable):
|
||||||
|
ret = await ret
|
||||||
|
msg, need_restart = ret
|
||||||
|
sudo_responses.append(msg)
|
||||||
|
restart |= need_restart
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
failed.append((cb, msg))
|
||||||
|
failed_msgs.append(str(e))
|
||||||
|
restart = False if len(failed) > 0 else restart
|
||||||
|
self.sudo_requests = failed
|
||||||
|
if not restart and len(sudo_responses) > 1:
|
||||||
|
# at least one successful response and not restarting
|
||||||
|
eventloop = self.server.get_event_loop()
|
||||||
|
eventloop.delay_callback(
|
||||||
|
.05, self.server.send_event,
|
||||||
|
"machine:sudo_alert",
|
||||||
|
{
|
||||||
|
"sudo_requested": self.sudo_requested,
|
||||||
|
"request_messages": self.sudo_request_messages
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if failed_msgs:
|
||||||
|
err_msg = "\n".join(failed_msgs)
|
||||||
|
raise self.server.error(err_msg, 500)
|
||||||
|
if restart:
|
||||||
|
self.restart_moonraker_service()
|
||||||
|
sudo_responses.append(
|
||||||
|
"Moonraker is currently in the process of restarting."
|
||||||
|
)
|
||||||
return {
|
return {
|
||||||
"sudo_responses": sudo_responses,
|
"sudo_responses": sudo_responses,
|
||||||
"is_restarting": restart
|
"is_restarting": restart
|
||||||
|
@ -391,7 +395,7 @@ class Machine:
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def exec_sudo_command(self, command: str) -> str:
|
async def exec_sudo_command(self, command: str, tries: int = 1) -> str:
|
||||||
proc_input = None
|
proc_input = None
|
||||||
full_cmd = f"sudo {command}"
|
full_cmd = f"sudo {command}"
|
||||||
if self._sudo_password is not None:
|
if self._sudo_password is not None:
|
||||||
|
@ -399,7 +403,7 @@ class Machine:
|
||||||
full_cmd = f"sudo -S {command}"
|
full_cmd = f"sudo -S {command}"
|
||||||
shell_cmd: SCMDComp = self.server.lookup_component("shell_command")
|
shell_cmd: SCMDComp = self.server.lookup_component("shell_command")
|
||||||
return await shell_cmd.exec_cmd(
|
return await shell_cmd.exec_cmd(
|
||||||
full_cmd, proc_input=proc_input, log_complete=False
|
full_cmd, proc_input=proc_input, log_complete=False, retries=tries
|
||||||
)
|
)
|
||||||
|
|
||||||
def _get_sdcard_info(self) -> Dict[str, Any]:
|
def _get_sdcard_info(self) -> Dict[str, Any]:
|
||||||
|
@ -861,7 +865,7 @@ class SystemdCliProvider(BaseProvider):
|
||||||
)
|
)
|
||||||
prop_args = ",".join(properties)
|
prop_args = ",".join(properties)
|
||||||
props: str = await self.shell_cmd.exec_cmd(
|
props: str = await self.shell_cmd.exec_cmd(
|
||||||
f"systemctl show -p {prop_args} {unit_name}"
|
f"systemctl show -p {prop_args} {unit_name}", retries=5
|
||||||
)
|
)
|
||||||
raw_props: Dict[str, Any] = {}
|
raw_props: Dict[str, Any] = {}
|
||||||
lines = [p.strip() for p in props.split("\n") if p.strip]
|
lines = [p.strip() for p in props.split("\n") if p.strip]
|
||||||
|
@ -1276,6 +1280,7 @@ class InstallValidator:
|
||||||
"must be updated manually."
|
"must be updated manually."
|
||||||
)
|
)
|
||||||
if unit != "moonraker":
|
if unit != "moonraker":
|
||||||
|
logging.info(f"Custom service file detected: {unit}")
|
||||||
# Not using he default unit name
|
# Not using he default unit name
|
||||||
if app_args["is_default_data_path"]:
|
if app_args["is_default_data_path"]:
|
||||||
# No datapath set, create a new, unique data path
|
# No datapath set, create a new, unique data path
|
||||||
|
@ -1290,11 +1295,13 @@ class InstallValidator:
|
||||||
f"data path '{new_dp}' already exists. Service file "
|
f"data path '{new_dp}' already exists. Service file "
|
||||||
"must be updated manually."
|
"must be updated manually."
|
||||||
)
|
)
|
||||||
|
|
||||||
# If the current path is bare we can remove it
|
# If the current path is bare we can remove it
|
||||||
if self._check_path_bare(self.data_path):
|
if self._check_path_bare(self.data_path):
|
||||||
shutil.rmtree(self.data_path)
|
shutil.rmtree(self.data_path)
|
||||||
self.data_path = new_dp
|
self.data_path = new_dp
|
||||||
if not self.data_path.exists():
|
if not self.data_path.exists():
|
||||||
|
logging.info(f"New data path created at {self.data_path}")
|
||||||
self.data_path.mkdir()
|
self.data_path.mkdir()
|
||||||
# A non-default datapath requires successful update of the
|
# A non-default datapath requires successful update of the
|
||||||
# service
|
# service
|
||||||
|
@ -1362,8 +1369,13 @@ class InstallValidator:
|
||||||
% (SERVICE_VERSION, user, src_path, env_file, sys.executable)
|
% (SERVICE_VERSION, user, src_path, env_file, sys.executable)
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
await machine.exec_sudo_command(f"cp -f {tmp_svc} {svc_dest}")
|
# write new environment
|
||||||
await machine.exec_sudo_command("systemctl daemon-reload")
|
env_file.write_text(ENVIRONMENT % (src_path, cmd_args))
|
||||||
|
await machine.exec_sudo_command(
|
||||||
|
f"cp -f {tmp_svc} {svc_dest}", tries=5)
|
||||||
|
await machine.exec_sudo_command(
|
||||||
|
"systemctl daemon-reload", tries=5
|
||||||
|
)
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
raise
|
raise
|
||||||
except Exception:
|
except Exception:
|
||||||
|
@ -1374,9 +1386,8 @@ class InstallValidator:
|
||||||
) from None
|
) from None
|
||||||
finally:
|
finally:
|
||||||
tmp_svc.unlink()
|
tmp_svc.unlink()
|
||||||
# write new environment
|
|
||||||
env_file.write_text(ENVIRONMENT % (src_path, cmd_args))
|
|
||||||
self.data_path_valid = True
|
self.data_path_valid = True
|
||||||
|
self.sc_enabled = False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _check_path_bare(self, path: pathlib.Path) -> bool:
|
def _check_path_bare(self, path: pathlib.Path) -> bool:
|
||||||
|
|
Loading…
Reference in New Issue