Skip to content

Commit 467354a

Browse files
Merge pull request #40 from GitTimeraider/develop
DB fixes
2 parents c6c949e + fc3ec00 commit 467354a

File tree

4 files changed

+166
-4
lines changed

4 files changed

+166
-4
lines changed

Dockerfile

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,10 @@ COPY --chown=appuser:appgroup . .
5050
RUN chmod +x /app/docker-entrypoint.sh
5151

5252
# Create writable directories for read-only filesystem compatibility
53-
RUN mkdir -p /tmp/app-runtime /var/tmp/app \
54-
&& chown -R appuser:appgroup /tmp/app-runtime /var/tmp/app \
55-
&& chmod 755 /tmp/app-runtime /var/tmp/app
53+
RUN mkdir -p /tmp/app-runtime /var/tmp/app /app/instance \
54+
&& chown -R appuser:appgroup /tmp/app-runtime /var/tmp/app /app/instance \
55+
&& chmod 755 /tmp/app-runtime /var/tmp/app \
56+
&& chmod 775 /app/instance
5657

5758
ENV FLASK_APP=run.py \
5859
USER=appuser \

config.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@
33

44
def normalize_database_url():
55
"""Normalize DATABASE_URL to use correct driver for psycopg3"""
6-
database_url = os.environ.get('DATABASE_URL') or 'sqlite:///subscriptions.db'
6+
database_url = os.environ.get('DATABASE_URL')
7+
8+
# Default SQLite path - ensure it's in the writable instance directory
9+
if not database_url:
10+
# Use absolute path to ensure database is in the mounted volume
11+
instance_dir = os.path.abspath('/app/instance')
12+
database_url = f'sqlite:///{instance_dir}/subscriptions.db'
713

814
# Convert postgresql:// or postgres:// to postgresql+psycopg:// for psycopg3
915
if database_url.startswith('postgresql://') or database_url.startswith('postgres://'):

docker-entrypoint.sh

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,19 @@ ensure_writable_dirs() {
7373
chown "$owner:$group" /app/instance 2>/dev/null || true
7474
fi
7575
fi
76+
77+
# Ensure proper permissions on instance directory
78+
chmod 755 /app/instance 2>/dev/null || true
79+
80+
# If SQLite database exists, ensure it has proper permissions
81+
if [ -f "/app/instance/subscriptions.db" ]; then
82+
chmod 664 /app/instance/subscriptions.db 2>/dev/null || true
83+
if [ "$(id -u)" = "0" ]; then
84+
local owner="${PUID:-$APP_USER}"
85+
local group="${GUID:-$APP_GROUP}"
86+
chown "$owner:$group" /app/instance/subscriptions.db 2>/dev/null || true
87+
fi
88+
fi
7689
}
7790

7891
# Set up temporary directories for application runtime
@@ -100,6 +113,38 @@ should_drop_privileges() {
100113
[ "$(id -u)" = "0" ]
101114
}
102115

116+
# Initialize database with proper permissions
117+
init_database() {
118+
# Only run database initialization if we're starting the main application
119+
if [[ "$1" == *"python"* ]] || [[ "$1" == *"gunicorn"* ]] || [[ "$1" == *"run.py"* ]]; then
120+
echo "Initializing database..."
121+
122+
# Create database directory if it doesn't exist
123+
mkdir -p /app/instance
124+
125+
# Set proper permissions for database operations
126+
if [ "$(id -u)" = "0" ]; then
127+
local owner="${PUID:-$APP_USER}"
128+
local group="${GUID:-$APP_GROUP}"
129+
chown "$owner:$group" /app/instance
130+
chmod 755 /app/instance
131+
132+
# If database file exists, fix its permissions
133+
if [ -f "/app/instance/subscriptions.db" ]; then
134+
chown "$owner:$group" /app/instance/subscriptions.db
135+
chmod 664 /app/instance/subscriptions.db
136+
fi
137+
else
138+
# Running as non-root, ensure we can write to the directory
139+
if [ ! -w "/app/instance" ]; then
140+
echo "WARNING: /app/instance is not writable by current user $(id -u):$(id -g)"
141+
echo "Please ensure the mounted volume has proper permissions:"
142+
echo " sudo chown -R $(id -u):$(id -g) ./data"
143+
fi
144+
fi
145+
fi
146+
}
147+
103148
# Main execution
104149
main() {
105150
echo "Starting Subscription Tracker..."
@@ -112,6 +157,9 @@ main() {
112157
ensure_writable_dirs
113158
setup_temp_dirs
114159

160+
# Initialize database with proper permissions
161+
init_database "$@"
162+
115163
# Drop privileges if running as root, otherwise run directly
116164
if should_drop_privileges; then
117165
echo "Dropping privileges to ${APP_USER}:${APP_GROUP}"

init_db.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Database initialization script for Subscription Tracker
4+
Ensures database is created with proper permissions and structure
5+
"""
6+
7+
import os
8+
import sys
9+
import stat
10+
from pathlib import Path
11+
12+
# Add the app directory to Python path
13+
sys.path.insert(0, '/app')
14+
15+
def check_database_permissions():
16+
"""Check and fix database file permissions"""
17+
instance_dir = Path('/app/instance')
18+
db_file = instance_dir / 'subscriptions.db'
19+
20+
print(f"Checking database permissions...")
21+
print(f"Instance directory: {instance_dir}")
22+
print(f"Database file: {db_file}")
23+
24+
# Check if instance directory exists and is writable
25+
if not instance_dir.exists():
26+
print(f"ERROR: Instance directory {instance_dir} does not exist!")
27+
return False
28+
29+
if not os.access(instance_dir, os.W_OK):
30+
print(f"ERROR: Instance directory {instance_dir} is not writable!")
31+
print(f"Current permissions: {oct(instance_dir.stat().st_mode)[-3:]}")
32+
print(f"Owner: {instance_dir.stat().st_uid}:{instance_dir.stat().st_gid}")
33+
print(f"Current user: {os.getuid()}:{os.getgid()}")
34+
return False
35+
36+
# Check database file if it exists
37+
if db_file.exists():
38+
if not os.access(db_file, os.W_OK):
39+
print(f"ERROR: Database file {db_file} is not writable!")
40+
print(f"Current permissions: {oct(db_file.stat().st_mode)[-3:]}")
41+
print(f"Owner: {db_file.stat().st_uid}:{db_file.stat().st_gid}")
42+
return False
43+
else:
44+
print(f"Database file exists and is writable")
45+
else:
46+
print(f"Database file does not exist yet (will be created)")
47+
48+
print("Database permissions check passed!")
49+
return True
50+
51+
def initialize_database():
52+
"""Initialize the database with proper Flask app context"""
53+
try:
54+
from app import create_app, db
55+
56+
# Create Flask app
57+
app = create_app()
58+
59+
with app.app_context():
60+
print("Creating database tables...")
61+
62+
# Create all tables
63+
db.create_all()
64+
65+
# Verify tables were created
66+
from sqlalchemy import inspect
67+
inspector = inspect(db.engine)
68+
tables = inspector.get_table_names()
69+
70+
print(f"Created {len(tables)} tables: {', '.join(tables)}")
71+
72+
# Test database write capability
73+
print("Testing database write capability...")
74+
result = db.engine.execute(db.text("SELECT 1 as test"))
75+
test_result = result.fetchone()
76+
print(f"Database connection test: {'PASSED' if test_result else 'FAILED'}")
77+
78+
return True
79+
80+
except Exception as e:
81+
print(f"ERROR: Failed to initialize database: {e}")
82+
import traceback
83+
traceback.print_exc()
84+
return False
85+
86+
def main():
87+
"""Main initialization function"""
88+
print("=" * 50)
89+
print("Database Initialization Script")
90+
print("=" * 50)
91+
92+
# Check permissions first
93+
if not check_database_permissions():
94+
print("Permission check failed!")
95+
sys.exit(1)
96+
97+
# Initialize database
98+
if not initialize_database():
99+
print("Database initialization failed!")
100+
sys.exit(1)
101+
102+
print("=" * 50)
103+
print("Database initialization completed successfully!")
104+
print("=" * 50)
105+
106+
if __name__ == '__main__':
107+
main()

0 commit comments

Comments
 (0)