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:
@@ -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)
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user