Added an explicit data folder permissions check on startup

It will try resolve the permission issue on its own, but if not successful it will crash with a helpful message.
This commit is contained in:
Donkie
2023-11-06 22:17:30 +01:00
parent dcf9a8f393
commit a9f19774e4
2 changed files with 69 additions and 6 deletions

View File

@@ -2,6 +2,8 @@
import logging import logging
import os import os
import subprocess
import sys
from datetime import datetime from datetime import datetime
from enum import Enum from enum import Enum
from pathlib import Path from pathlib import Path
@@ -11,6 +13,8 @@ from urllib import parse
import pkg_resources import pkg_resources
from platformdirs import user_data_dir from platformdirs import user_data_dir
logger = logging.getLogger(__name__)
class DatabaseType(Enum): class DatabaseType(Enum):
"""The database type.""" """The database type."""
@@ -272,3 +276,53 @@ def get_build_date() -> Optional[datetime]:
if build_date == "unknown": if build_date == "unknown":
return None return None
return datetime.fromisoformat(build_date) return datetime.fromisoformat(build_date)
def can_write_to_data_dir() -> bool:
"""Check if the data directory is writable."""
try:
test_file = get_data_dir().joinpath("test.txt")
test_file.touch()
test_file.unlink()
except: # noqa: E722
return False
return True
def chown_dir(path: str) -> bool:
"""Try to chown the data directory to the current user."""
if os.name == "nt":
return False
try:
uid = os.getuid()
gid = os.getgid()
subprocess.run(["chown", "-R", f"{uid}:{gid}", path], check=True) # noqa: S603, S607
except: # noqa: E722
return False
return True
def check_write_permissions() -> None:
"""Verify that the data directory is writable, crash with a helpful error message if not."""
if not can_write_to_data_dir():
# If windows we can't fix the permissions, so just crash
if os.name == "nt":
logger.error("Data directory is not writable.")
sys.exit(1)
# Try fixing it by chowning the directory to the current user
logger.warning("Data directory is not writable, trying to fix it...")
if not chown_dir(get_data_dir()) or not can_write_to_data_dir():
uid = os.getuid()
gid = os.getgid()
logger.error(
(
"Data directory is not writable. "
'Please run "sudo chown -R %s:%s /path/to/spoolman/datadir" on the host OS.'
),
uid,
gid,
)
sys.exit(1)

View File

@@ -16,11 +16,6 @@ from spoolman.api.v1.router import app as v1_app
from spoolman.client import SinglePageApplication from spoolman.client import SinglePageApplication
from spoolman.database import database from spoolman.database import database
# Define a file logger with log rotation
log_file = env.get_data_dir().joinpath("spoolman.log")
file_handler = TimedRotatingFileHandler(log_file, when="midnight", backupCount=5)
file_handler.setFormatter(logging.Formatter("%(asctime)s:%(levelname)s:%(message)s", "%Y-%m-%d %H:%M:%S"))
# Define a console logger # Define a console logger
console_handler = logging.StreamHandler() console_handler = logging.StreamHandler()
console_handler.setFormatter(logging.Formatter("%(name)-26s %(levelname)-8s %(message)s")) console_handler.setFormatter(logging.Formatter("%(name)-26s %(levelname)-8s %(message)s"))
@@ -28,7 +23,6 @@ console_handler.setFormatter(logging.Formatter("%(name)-26s %(levelname)-8s %(me
# Setup the spoolman logger, which all spoolman modules will use # Setup the spoolman logger, which all spoolman modules will use
root_logger = logging.getLogger() root_logger = logging.getLogger()
root_logger.setLevel(env.get_logging_level()) root_logger.setLevel(env.get_logging_level())
root_logger.addHandler(file_handler)
root_logger.addHandler(console_handler) root_logger.addHandler(console_handler)
# Get logger instance for this module # Get logger instance for this module
@@ -60,9 +54,24 @@ if env.is_debug_mode():
) )
def add_file_logging() -> None:
"""Add file logging to the root logger."""
# Define a file logger with log rotation
log_file = env.get_data_dir().joinpath("spoolman.log")
file_handler = TimedRotatingFileHandler(log_file, when="midnight", backupCount=5)
file_handler.setFormatter(logging.Formatter("%(asctime)s:%(levelname)s:%(message)s", "%Y-%m-%d %H:%M:%S"))
root_logger.addHandler(file_handler)
@app.on_event("startup") @app.on_event("startup")
async def startup() -> None: async def startup() -> None:
"""Run the service's startup sequence.""" """Run the service's startup sequence."""
# Check that the data directory is writable
env.check_write_permissions()
# Don't add file logging until we have verified that the data directory is writable
add_file_logging()
logger.info( logger.info(
"Starting Spoolman v%s (commit: %s) (built: %s)", "Starting Spoolman v%s (commit: %s) (built: %s)",
app.version, app.version,