Fix database permissions issues by using more accessible paths

- Updated database configuration to use /app/storage/db directory
- Added robust directory and file permission handling
- Implemented fallback paths for database location
- Enhanced Alembic migration script with retry logic and clear error diagnostics
- Updated the README with the new database path information
This commit is contained in:
Automated Action 2025-05-16 12:47:17 +00:00
parent 0ceeef31a6
commit efba5e489a
6 changed files with 196 additions and 30 deletions

View File

@ -194,9 +194,10 @@ taskmanagerapi/
The application is configured to use SQLite with the following database locations (in order of priority):
1. Path defined in the `DB_PATH` environment variable (if set)
2. `/app/db/db.sqlite` (production environment)
3. `./db/db.sqlite` (local development)
4. `/tmp/taskmanager/db.sqlite` (fallback)
2. `/app/storage/db/db.sqlite` (primary production path)
3. `/app/storage/db.sqlite` (alternate storage path)
4. `./storage/db/db.sqlite` (local development)
5. `/tmp/taskmanager/db.sqlite` (fallback)
The application automatically selects the first available and writable location from this list.

View File

@ -35,7 +35,7 @@ script_location = alembic
# are written from script.py.mako
# output_encoding = utf-8
sqlalchemy.url = sqlite:////app/db/db.sqlite
sqlalchemy.url = sqlite:////app/storage/db/db.sqlite
[post_write_hooks]
# post_write_hooks defines scripts or Python functions that are run

View File

@ -2,7 +2,6 @@ from logging.config import fileConfig
import os
import sys
import logging
from pathlib import Path
import sqlite3
from sqlalchemy import engine_from_config, event
@ -24,7 +23,7 @@ fileConfig(config.config_file_name)
# Setup logger
logger = logging.getLogger("alembic.env")
# Ensure database directory exists
# Ensure database directory exists and is writable
db_url = config.get_main_option("sqlalchemy.url")
if db_url.startswith("sqlite:///"):
# Extract the path part after sqlite:///
@ -39,29 +38,115 @@ if db_url.startswith("sqlite:///"):
logger.info(f"Database path: {db_path}")
logger.info(f"Database directory: {db_dir}")
# Create directory if it doesn't exist
try:
os.makedirs(db_dir, exist_ok=True)
logger.info(f"Ensured database directory exists: {db_dir}")
# Define alternate paths to try if the primary path isn't writable
alternate_paths = [
"/app/storage/db/db.sqlite", # Primary alternate
os.path.join(os.path.expanduser("~"), "db.sqlite"), # Home directory
"/tmp/taskmanager/db.sqlite", # Fallback to tmp
]
# Try to create the main directory first
db_created = False
for attempt_path in [db_path] + alternate_paths:
if db_created:
break
# Test if we can create the database file
try:
# Try to touch the database file
Path(db_path).touch(exist_ok=True)
logger.info(f"Database file is accessible: {db_path}")
# Get directory part
current_dir = os.path.dirname(attempt_path)
# Test direct SQLite connection
# Create directory if it doesn't exist
os.makedirs(current_dir, exist_ok=True)
logger.info(f"Ensured directory exists: {current_dir}")
# Set directory permissions if possible (755 = rwxr-xr-x)
try:
conn = sqlite3.connect(db_path)
conn.execute("SELECT 1")
conn.close()
logger.info("Successfully connected to SQLite database")
import stat
dir_mode = (
stat.S_IRWXU
| stat.S_IRGRP
| stat.S_IXGRP
| stat.S_IROTH
| stat.S_IXOTH
)
os.chmod(current_dir, dir_mode)
logger.info(f"Set directory permissions on {current_dir}")
except Exception as chmod_err:
logger.warning(f"Could not set directory permissions: {chmod_err}")
# Check if the directory is writable
if not os.access(current_dir, os.W_OK):
logger.warning(
f"Directory {current_dir} exists but is not writable, trying next path"
)
continue
# Try to create/access the database file
try:
# Create an empty file if it doesn't exist
if not os.path.exists(attempt_path):
with open(attempt_path, "wb") as f:
f.write(b"") # Write empty content
logger.info(f"Created database file: {attempt_path}")
# Try to set file permissions (666 = rw-rw-rw-)
try:
file_mode = (
stat.S_IRUSR
| stat.S_IWUSR
| stat.S_IRGRP
| stat.S_IWGRP
| stat.S_IROTH
| stat.S_IWOTH
)
os.chmod(attempt_path, file_mode)
logger.info(f"Set file permissions on {attempt_path}")
except Exception as chmod_err:
logger.warning(f"Could not set file permissions: {chmod_err}")
# Verify file is writable
if not os.access(attempt_path, os.W_OK):
logger.warning(
f"File {attempt_path} exists but is not writable, trying next path"
)
continue
# Test direct SQLite connection
try:
conn = sqlite3.connect(attempt_path)
conn.execute("SELECT 1")
conn.close()
logger.info(
f"Successfully connected to SQLite database at {attempt_path}"
)
# If this isn't the original path, update the config
if attempt_path != db_path:
new_url = f"sqlite:///{attempt_path}"
logger.info(f"Updating database URL to: {new_url}")
config.set_main_option("sqlalchemy.url", new_url)
db_path = attempt_path # Update the db_path for diagnostics
db_created = True
break
except Exception as e:
logger.error(f"Direct SQLite connection failed: {e}")
raise
except Exception as e:
logger.error(f"Direct SQLite connection failed: {e}")
logger.error(f"Could not access database file: {e}")
raise
except Exception as e:
logger.error(f"Could not access database file: {e}")
except Exception as e:
logger.error(f"Could not create database directory: {e}")
logger.error(f"Could not create/access database at {attempt_path}: {e}")
# Continue to next path
if not db_created:
logger.error("Failed to create database in any of the attempted locations")
# We'll continue anyway to show more diagnostic info in errors
# add your model's MetaData object here
# for 'autogenerate' support

View File

@ -14,10 +14,11 @@ if DB_PATH:
DB_DIR = Path(DB_PATH)
print(f"Using database path from environment: {DB_DIR}")
else:
# Try production path first, then local directory
# Try production path first, then local directories
paths_to_try = [
Path("/app/db"), # Production path
Path.cwd() / "db", # Local development path
Path("/app/storage/db"), # Primary production path with proper permissions
Path("/app/storage"), # Alternate storage path
Path.cwd() / "storage/db", # Local development path
Path("/tmp/taskmanager"), # Fallback path
]
@ -25,11 +26,40 @@ else:
DB_DIR = None
for path in paths_to_try:
try:
# Create directory with parents if it doesn't exist
path.mkdir(parents=True, exist_ok=True)
# Explicitly set permissions if possible (755 = rwxr-xr-x)
try:
import stat
path_mode = (
stat.S_IRWXU
| stat.S_IRGRP
| stat.S_IXGRP
| stat.S_IROTH
| stat.S_IXOTH
)
os.chmod(path, path_mode)
except Exception as chmod_err:
print(f"Warning: Could not set permissions on {path}: {chmod_err}")
# Test if it's writable
test_file = path / ".write_test"
test_file.touch()
# Try to write to it
with open(test_file, "w") as f:
f.write("test")
# Read back to verify
with open(test_file, "r") as f:
content = f.read()
if content != "test":
raise Exception("Write verification failed")
test_file.unlink() # Remove the test file
DB_DIR = path
print(f"Using database path: {DB_DIR}")
break

View File

@ -81,6 +81,7 @@ def init_db() -> None:
now = datetime.utcnow().isoformat()
# Import get_password_hash function here to avoid F823 error
from app.core.security import get_password_hash
admin_password_hash = get_password_hash("adminpassword")
conn.execute(
"""

View File

@ -1,5 +1,6 @@
import time
import sqlite3
import os
from typing import Generator
from sqlalchemy import create_engine, event
@ -11,14 +12,62 @@ from app.core.config import settings, DB_DIR
db_file = DB_DIR / "db.sqlite"
print(f"Database file path: {db_file}")
# Try to touch the database file to ensure it exists
# Ensure database directory exists with proper permissions
try:
# Make sure directory exists
os.makedirs(DB_DIR, exist_ok=True)
# Try to set directory permissions (755 = rwxr-xr-x)
try:
import stat
dir_mode = (
stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH
)
os.chmod(DB_DIR, dir_mode)
print(f"Set directory permissions on {DB_DIR}")
except Exception as chmod_err:
print(f"Warning: Could not set directory permissions on {DB_DIR}: {chmod_err}")
# Check if we can write to the directory
dir_writable = os.access(DB_DIR, os.W_OK)
print(f"Directory {DB_DIR} is writable: {dir_writable}")
if not dir_writable:
print(
f"WARNING: Directory {DB_DIR} is not writable! Database operations may fail."
)
# Try to create or access the database file
if not db_file.exists():
# Try to create an empty database file
db_file.touch()
print(f"Database file created or verified: {db_file}")
with open(db_file, "wb") as f:
f.write(b"") # Write empty content
print(f"Database file created: {db_file}")
# Try to set file permissions (644 = rw-r--r--)
try:
file_mode = stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH
os.chmod(db_file, file_mode)
print(f"Set file permissions on {db_file}")
except Exception as chmod_err:
print(f"Warning: Could not set file permissions on {db_file}: {chmod_err}")
# Check if file is writable
file_writable = os.access(db_file, os.W_OK)
print(f"Database file {db_file} is writable: {file_writable}")
if not file_writable:
print(
f"WARNING: Database file {db_file} is not writable! Database operations may fail."
)
print(f"Database file prepared: {db_file}")
except Exception as e:
print(f"Warning: Could not create database file: {e}")
print(f"Warning: Could not prepare database file: {e}")
import traceback
print(f"Traceback: {traceback.format_exc()}")
# Configure SQLite connection with simplified, robust settings
engine = create_engine(