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:
parent
0ceeef31a6
commit
efba5e489a
@ -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.
|
||||
|
||||
|
@ -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
|
||||
|
123
alembic/env.py
123
alembic/env.py
@ -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
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
"""
|
||||
|
@ -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(
|
||||
|
Loading…
x
Reference in New Issue
Block a user