Files
TrafegoDNS/docker-s6/scripts/fix-sqlite.sh

400 lines
13 KiB
Bash
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/bash
# Script to fix SQLite issues, including transaction handling and database locks
echo "====================================="
echo "TrafegoDNS SQLite Fix Script"
echo "====================================="
# Set directory paths
APP_DIR="/app"
CONFIG_DIR=${CONFIG_DIR:-"/config"}
DATA_DIR="$CONFIG_DIR/data"
DB_FILE="$DATA_DIR/trafegodns.db"
LOCK_FILE="$DATA_DIR/.migration.lock"
# 1. Create and fix data directory permissions
echo "Fixing data directory permissions..."
mkdir -p "$DATA_DIR"
chmod 755 "$DATA_DIR"
chown -R abc:abc "$DATA_DIR"
echo "✅ Data directory permissions fixed"
# 2. Fix database file permissions if it exists
if [ -f "$DB_FILE" ]; then
echo "Fixing database file permissions..."
chmod 644 "$DB_FILE"
chown abc:abc "$DB_FILE"
echo "✅ Database file permissions fixed"
else
echo " Database file does not exist yet, will be created on first run"
fi
# 3. Check and remove migration lock file if it exists
if [ -f "$LOCK_FILE" ]; then
echo "Found migration lock file, removing to prevent initialization conflicts..."
rm -f "$LOCK_FILE"
echo "✅ Migration lock file removed"
fi
# 4. Install SQLite packages
echo "Installing SQLite packages..."
cd "$APP_DIR"
# Install required build dependencies if not already installed
echo "- Installing required build dependencies..."
if command -v apk > /dev/null; then
apk add --no-cache python3 py3-pip make g++ sqlite-dev python3-dev build-base py3-setuptools > /dev/null 2>&1
elif command -v apt-get > /dev/null; then
apt-get update > /dev/null 2>&1
apt-get install -y python3 python3-pip build-essential python3-dev sqlite3 libsqlite3-dev python3-setuptools > /dev/null 2>&1
fi
echo "- Installing better-sqlite3 (this may take a moment)..."
npm install --build-from-source better-sqlite3 --save
if [ $? -ne 0 ]; then
echo "⚠️ Failed to install better-sqlite3, trying sqlite3..."
npm install --build-from-source sqlite3 --save
if [ $? -ne 0 ]; then
echo "❌ Failed to install any SQLite package. Manual intervention required."
echo "Try running the following commands:"
echo " apk add --no-cache python3 py3-pip make g++ sqlite-dev python3-dev build-base py3-setuptools"
echo " npm install --build-from-source better-sqlite3 --save"
else
echo "✅ sqlite3 package installed successfully"
fi
else
echo "✅ better-sqlite3 package installed successfully"
fi
# 5. Apply the fixed files
echo "Applying fixed database files..."
# 5.1 User.js fix
if [ -f "$APP_DIR/src/api/v1/models/User.js.fix" ]; then
echo "- Backing up and replacing User.js..."
cp "$APP_DIR/src/api/v1/models/User.js" "$APP_DIR/src/api/v1/models/User.js.bak"
cp "$APP_DIR/src/api/v1/models/User.js.fix" "$APP_DIR/src/api/v1/models/User.js"
echo "✅ User.js replaced"
else
echo "❌ User.js.fix not found, fix was not applied"
fi
# 5.2 database/index.js fix
if [ -f "$APP_DIR/src/database/index.js.fix" ]; then
echo "- Backing up and replacing database/index.js..."
cp "$APP_DIR/src/database/index.js" "$APP_DIR/src/database/index.js.bak"
cp "$APP_DIR/src/database/index.js.fix" "$APP_DIR/src/database/index.js"
echo "✅ database/index.js replaced"
else
echo "❌ database/index.js.fix not found, fix was not applied"
fi
# 5.3 Backup and update better-sqlite.js with transaction fixes
if [ -f "$APP_DIR/src/database/better-sqlite.js" ]; then
echo "- Backing up current better-sqlite.js..."
cp "$APP_DIR/src/database/better-sqlite.js" "$APP_DIR/src/database/better-sqlite.js.bak"
echo "✅ better-sqlite.js backed up"
# Check if we have the fixed version available
if [ -f "$APP_DIR/src/database/better-sqlite.js.fix" ]; then
echo "- Found better-sqlite.js.fix with improved transaction handling"
echo "- Applying enhanced transaction fixes..."
cp "$APP_DIR/src/database/better-sqlite.js.fix" "$APP_DIR/src/database/better-sqlite.js"
echo "✅ Applied enhanced transaction fixes to better-sqlite.js"
else
echo "- Checking for transaction tracking improvements..."
grep -q "inTransaction" "$APP_DIR/src/database/better-sqlite.js"
if [ $? -ne 0 ]; then
echo "⚠️ Transaction tracking not found in better-sqlite.js"
echo "Please manually update better-sqlite.js with transaction fixes."
echo "See SQLITE_FIX.md for details on the necessary changes."
else
echo "✅ Transaction tracking found in better-sqlite.js"
fi
fi
fi
# 5.4 Check for lock manager
if [ ! -f "$APP_DIR/src/database/lockManager.js" ]; then
echo "- Lock manager file not found, creating it..."
cat > "$APP_DIR/src/database/lockManager.js" << 'EOF'
/**
* Database lock manager
* Provides file-based advisory locking to coordinate database operations
* between multiple processes.
*/
const fs = require('fs');
const path = require('path');
const logger = require('../utils/logger');
class LockManager {
constructor() {
this.dataDir = path.join(process.env.CONFIG_DIR || '/config', 'data');
this.migrationLockFile = path.join(this.dataDir, '.migration.lock');
this.lockFileDescriptor = null;
this.lockOwner = false;
// Ensure data directory exists
if (!fs.existsSync(this.dataDir)) {
try {
fs.mkdirSync(this.dataDir, { recursive: true });
} catch (error) {
logger.error(`Failed to create data directory: ${error.message}`);
}
}
}
/**
* Acquire an exclusive lock for database initialization
* Uses OS-level file locking for reliability across processes
* @param {number} timeout - Maximum time to wait for lock in milliseconds
* @returns {Promise<boolean>} Whether lock was acquired
*/
async acquireLock(timeout = 10000) {
if (this.lockOwner) {
logger.debug('Already own the lock, no need to acquire again');
return true;
}
const startTime = Date.now();
// First, clear any stale lock files
try {
if (fs.existsSync(this.migrationLockFile)) {
const stats = fs.statSync(this.migrationLockFile);
const lockAge = Date.now() - stats.mtimeMs;
// If lock is older than 2 minutes, consider it stale
if (lockAge > 120000) {
logger.warn('Removing stale migration lock file');
fs.unlinkSync(this.migrationLockFile);
}
}
} catch (error) {
logger.warn(`Error checking stale lock: ${error.message}`);
}
// Create lock file if it doesn't exist
if (!fs.existsSync(this.migrationLockFile)) {
try {
// Create an empty file
fs.writeFileSync(this.migrationLockFile, process.pid.toString(), { flag: 'wx' });
} catch (error) {
if (error.code !== 'EEXIST') {
logger.error(`Failed to create lock file: ${error.message}`);
return false;
}
// If EEXIST, file was created by another process, continue to lock attempt
}
}
// Try to open and lock the file
while (true) {
try {
// Open file for read/write with exclusive lock
this.lockFileDescriptor = fs.openSync(this.migrationLockFile, 'r+');
// Try to get exclusive lock
try {
// For Windows and WSL compatibility, use existence check
const lockContent = fs.readFileSync(this.migrationLockFile, 'utf8').trim();
// If the file is empty or contains our PID, we can claim it
if (!lockContent || lockContent === process.pid.toString()) {
// Write our PID to the file
fs.writeFileSync(this.migrationLockFile, process.pid.toString());
this.lockOwner = true;
logger.debug(`Database lock acquired by process ${process.pid}`);
return true;
}
// If it contains another PID, check if that process still exists
const otherPid = parseInt(lockContent, 10);
if (isNaN(otherPid)) {
// Invalid PID, overwrite with ours
fs.writeFileSync(this.migrationLockFile, process.pid.toString());
this.lockOwner = true;
logger.debug(`Database lock acquired by process ${process.pid} (invalid previous owner)`);
return true;
}
// Check if process exists (can't do this reliably cross-platform)
// Just assume it does exist and keep trying
logger.debug(`Lock owned by process ${otherPid}, waiting...`);
} catch (lockError) {
logger.warn(`Error during lock attempt: ${lockError.message}`);
}
// Close file and try again later
if (this.lockFileDescriptor !== null) {
fs.closeSync(this.lockFileDescriptor);
this.lockFileDescriptor = null;
}
} catch (error) {
logger.warn(`Failed to open lock file: ${error.message}`);
}
// Check if we've timed out
if (Date.now() - startTime > timeout) {
logger.error(`Failed to acquire lock after ${timeout}ms timeout`);
return false;
}
// Wait before trying again
await new Promise(resolve => setTimeout(resolve, 200));
}
}
/**
* Release the lock if we own it
* @returns {boolean} Whether release was successful
*/
releaseLock() {
if (!this.lockOwner) {
logger.debug('Not the lock owner, nothing to release');
return true;
}
try {
// Read current lock content to ensure we still own it
if (fs.existsSync(this.migrationLockFile)) {
const lockContent = fs.readFileSync(this.migrationLockFile, 'utf8').trim();
if (lockContent !== process.pid.toString()) {
logger.warn(`Lock was stolen by process ${lockContent}, not releasing`);
this.lockOwner = false;
return false;
}
// Delete the lock file
fs.unlinkSync(this.migrationLockFile);
}
// Close file descriptor if open
if (this.lockFileDescriptor !== null) {
fs.closeSync(this.lockFileDescriptor);
this.lockFileDescriptor = null;
}
this.lockOwner = false;
logger.debug(`Database lock released by process ${process.pid}`);
return true;
} catch (error) {
logger.error(`Failed to release lock: ${error.message}`);
return false;
}
}
/**
* Check if a lock is currently held by any process
* @returns {boolean} Whether lock exists
*/
isLocked() {
try {
if (!fs.existsSync(this.migrationLockFile)) {
return false;
}
const lockContent = fs.readFileSync(this.migrationLockFile, 'utf8').trim();
return !!lockContent;
} catch (error) {
logger.error(`Error checking lock status: ${error.message}`);
return false;
}
}
/**
* Check if we are the current lock owner
* @returns {boolean} Whether we own the lock
*/
isLockOwner() {
if (!this.lockOwner) {
return false;
}
try {
if (!fs.existsSync(this.migrationLockFile)) {
this.lockOwner = false;
return false;
}
const lockContent = fs.readFileSync(this.migrationLockFile, 'utf8').trim();
const isOwner = lockContent === process.pid.toString();
if (!isOwner) {
this.lockOwner = false;
}
return isOwner;
} catch (error) {
logger.error(`Error checking lock ownership: ${error.message}`);
this.lockOwner = false;
return false;
}
}
}
module.exports = new LockManager();
EOF
echo "✅ Created lockManager.js for better process coordination"
else
echo "✅ Lock manager file already exists"
fi
# 6. Check if DB is in a broken state
if [ -f "$DB_FILE" ]; then
# Try to query the database to see if it's functional
echo "Checking database functionality..."
sqlite3 "$DB_FILE" "SELECT count(*) FROM sqlite_master;" > /dev/null 2>&1
if [ $? -ne 0 ]; then
echo "⚠️ Database appears to be in a broken state."
echo "Consider running the reset script to create a clean database:"
echo " /app/docker-s6/scripts/reset-sqlite.sh"
else
echo "✅ Database appears functional"
fi
# Check for stuck transaction
sqlite3 "$DB_FILE" "PRAGMA quick_check;" > /dev/null 2>&1
if [ $? -ne 0 ]; then
echo "⚠️ Database integrity check failed. Database may be corrupted."
echo "Consider running the reset script to create a clean database:"
echo " /app/docker-s6/scripts/reset-sqlite.sh"
fi
fi
# 7. Check for WAL mode in database file
if [ -f "$DB_FILE" ]; then
echo "Checking if database is in WAL mode..."
# Create a temp file to run the pragma command
TEMP_SQL=$(mktemp)
echo "PRAGMA journal_mode;" > "$TEMP_SQL"
# Check if sqlite3 command is available
if command -v sqlite3 >/dev/null 2>&1; then
JOURNAL_MODE=$(sqlite3 "$DB_FILE" < "$TEMP_SQL")
if [ "$JOURNAL_MODE" != "wal" ]; then
echo "⚠️ Database is not in WAL mode. Setting WAL mode for better concurrency..."
echo "PRAGMA journal_mode=WAL;" > "$TEMP_SQL"
sqlite3 "$DB_FILE" < "$TEMP_SQL"
echo "✅ Database set to WAL mode"
else
echo "✅ Database already in WAL mode"
fi
else
echo "⚠️ sqlite3 command not available, skipping WAL mode check"
fi
# Clean up temp file
rm -f "$TEMP_SQL"
fi
echo "====================================="
echo "Fix completed. Restart TrafegoDNS to apply changes."
echo ""
echo "If you continue to experience database issues after restart:"
echo " 1. Run the database reset script: /app/docker-s6/scripts/reset-sqlite.sh"
echo " 2. This will completely reset your database and start fresh"
echo "====================================="