diff --git a/README.md b/README.md
old mode 100755
new mode 100644
index ac028f6..a99ba8c
--- a/README.md
+++ b/README.md
@@ -2,6 +2,16 @@
A comprehensive web-based tool to automate and manage your Transmission torrent downloads with RSS feed integration, intelligent media organization, and enhanced security features. Now with automatic updates and easy installation!
+## Update System Requirements
+
+To use the automatic update system, the following requirements must be met:
+
+1. **Git must be installed:** The update system uses Git to fetch the latest version.
+2. **Installation must be a Git repository:** Your installation directory must be a Git repository clone.
+3. **Internet connectivity:** The server must be able to connect to the Git repository.
+
+If you installed using the bootstrap installer, these requirements should be met automatically. If you experience issues with the update system, please ensure Git is properly installed and accessible to the application.
+
## Changelog
### v2.0.9 (2025-03-07)
@@ -104,8 +114,13 @@ A comprehensive web-based tool to automate and manage your Transmission torrent
### Prerequisites
- Ubuntu/Debian-based system (may work on other Linux distributions)
-- Git (will be automatically installed by the bootstrap installer if needed)
+- Git 2.25.0 or later (will be automatically installed by the bootstrap installer if needed)
+ - Required for the automatic update system
+ - Must be available in the PATH of the user running the application
+ - Must have proper permissions to access the installation directory
- Internet connection (for downloading and updates)
+ - Required for Git operations during updates (fetching latest code)
+ - Outbound access to git.powerdata.dk repository server
### System Requirements
@@ -338,6 +353,38 @@ The system will:
- Restore your configuration
- Restart the service automatically
+### Troubleshooting Update Issues
+
+If you encounter "Failed to connect to server" or similar errors when checking for updates:
+
+1. **Verify Git Installation**: Ensure Git is properly installed
+ ```bash
+ which git
+ git --version # Should be 2.25.0 or higher
+ ```
+
+2. **Check Repository Status**: Verify the installation directory is a Git repository
+ ```bash
+ cd /opt/transmission-rss-manager # Or your installation directory
+ git status
+ ```
+
+3. **Check Internet Connectivity**: Make sure the server can connect to git.powerdata.dk
+ ```bash
+ ping git.powerdata.dk
+ curl -I https://git.powerdata.dk
+ ```
+
+4. **Check Permissions**: Ensure the application user has access to the Git repository
+ ```bash
+ # Check ownership of the .git directory
+ ls -la /opt/transmission-rss-manager/.git
+ # If needed, fix permissions
+ sudo chown -R www-data:www-data /opt/transmission-rss-manager # Adjust user as needed
+ ```
+
+5. **Manual Update**: If the web update still fails, try the manual update method from the command line
+
### Using the Installer
You can also update by running the installer again:
diff --git a/install-script.sh b/install-script.sh
index ab8e847..e689adb 100755
--- a/install-script.sh
+++ b/install-script.sh
@@ -28,14 +28,44 @@ SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
# Create modules directory if it doesn't exist
mkdir -p "${SCRIPT_DIR}/modules"
-# Check for installation type
+# Check for installation type in multiple locations
IS_UPDATE=false
-if [ -f "${SCRIPT_DIR}/config.json" ]; then
- IS_UPDATE=true
- echo -e "${YELLOW}Existing installation detected. Running in update mode.${NC}"
- echo -e "${GREEN}Your existing configuration will be preserved.${NC}"
-else
- echo -e "${GREEN}Fresh installation. Will create new configuration.${NC}"
+POSSIBLE_CONFIG_LOCATIONS=(
+ "${SCRIPT_DIR}/config.json"
+ "/opt/transmission-rss-manager/config.json"
+ "/etc/transmission-rss-manager/config.json"
+)
+
+# Also check for service file - secondary indicator
+if [ -f "/etc/systemd/system/transmission-rss-manager.service" ]; then
+ # Extract install directory from service file if it exists
+ SERVICE_INSTALL_DIR=$(grep "WorkingDirectory=" "/etc/systemd/system/transmission-rss-manager.service" | cut -d'=' -f2)
+ if [ -n "$SERVICE_INSTALL_DIR" ]; then
+ echo -e "${YELLOW}Found existing service at: $SERVICE_INSTALL_DIR${NC}"
+ POSSIBLE_CONFIG_LOCATIONS+=("$SERVICE_INSTALL_DIR/config.json")
+ fi
+fi
+
+# Check all possible locations
+for CONFIG_PATH in "${POSSIBLE_CONFIG_LOCATIONS[@]}"; do
+ if [ -f "$CONFIG_PATH" ]; then
+ IS_UPDATE=true
+ echo -e "${YELLOW}Existing installation detected at: $CONFIG_PATH${NC}"
+ echo -e "${YELLOW}Running in update mode.${NC}"
+ echo -e "${GREEN}Your existing configuration will be preserved.${NC}"
+
+ # If the config is not in the current directory, store its location
+ if [ "$CONFIG_PATH" != "${SCRIPT_DIR}/config.json" ]; then
+ export EXISTING_CONFIG_PATH="$CONFIG_PATH"
+ export EXISTING_INSTALL_DIR="$(dirname "$CONFIG_PATH")"
+ echo -e "${YELLOW}Will update installation at: $EXISTING_INSTALL_DIR${NC}"
+ fi
+ break
+ fi
+done
+
+if [ "$IS_UPDATE" = "false" ]; then
+ echo -e "${GREEN}No existing installation detected. Will create new configuration.${NC}"
fi
# Check if modules exist, if not, extract them
diff --git a/main-installer.sh b/main-installer.sh
index b080b8c..c48bf0f 100755
--- a/main-installer.sh
+++ b/main-installer.sh
@@ -28,6 +28,10 @@ source "${SCRIPT_DIR}/modules/utils-module.sh"
echo -e "${BOLD}==================================================${NC}"
echo -e "${BOLD} Transmission RSS Manager Installer ${NC}"
VERSION=$(grep -oP '"version": "\K[^"]+' "${SCRIPT_DIR}/package.json" 2>/dev/null || echo "Unknown")
+# Check if package.json exists, if not suggest creating it
+if [ ! -f "${SCRIPT_DIR}/package.json" ]; then
+ echo -e "${YELLOW}Warning: package.json not found. You may need to run 'npm init' first.${NC}"
+fi
echo -e "${BOLD} Version ${VERSION} - Git Edition ${NC}"
echo -e "${BOLD}==================================================${NC}"
echo
@@ -42,35 +46,74 @@ fi
IS_UPDATE=false
INSTALLATION_DETECTED=false
-# Check for config.json file (primary indicator)
-if [ -f "${SCRIPT_DIR}/config.json" ]; then
- INSTALLATION_DETECTED=true
-fi
-
-# Check for service file (secondary indicator)
-if [ -f "/etc/systemd/system/transmission-rss-manager.service" ]; then
+# Check if we have existing config info from install-script.sh
+if [ -n "$EXISTING_INSTALL_DIR" ] && [ -n "$EXISTING_CONFIG_PATH" ]; then
INSTALLATION_DETECTED=true
+ IS_UPDATE=true
+ # Use the existing installation directory as our target
+ INSTALL_DIR="$EXISTING_INSTALL_DIR"
+ CONFIG_FILE="$EXISTING_CONFIG_PATH"
+ log "INFO" "Using existing installation at $INSTALL_DIR detected by install-script.sh"
+ export INSTALL_DIR
+else
+ # Check for config.json file (primary indicator)
+ POSSIBLE_CONFIG_LOCATIONS=(
+ "${SCRIPT_DIR}/config.json"
+ "/opt/transmission-rss-manager/config.json"
+ "/etc/transmission-rss-manager/config.json"
+ )
- # Check if the service points to /opt/trans-install
- if grep -q "/opt/trans-install" "/etc/systemd/system/transmission-rss-manager.service"; then
- # Set INSTALL_DIR to match existing service file
- INSTALL_DIR="/opt/trans-install"
- log "INFO" "Found existing installation at /opt/trans-install - using this as installation directory"
- export INSTALL_DIR
+ for CONFIG_PATH in "${POSSIBLE_CONFIG_LOCATIONS[@]}"; do
+ if [ -f "$CONFIG_PATH" ]; then
+ INSTALLATION_DETECTED=true
+ IS_UPDATE=true
+ INSTALL_DIR="$(dirname "$CONFIG_PATH")"
+ CONFIG_FILE="$CONFIG_PATH"
+ log "INFO" "Found existing installation at $INSTALL_DIR"
+ export INSTALL_DIR
+ break
+ fi
+ done
+
+ # Check for service file (secondary indicator) if no config file found
+ if [ "$INSTALLATION_DETECTED" = "false" ] && [ -f "/etc/systemd/system/transmission-rss-manager.service" ]; then
+ INSTALLATION_DETECTED=true
+ IS_UPDATE=true
+
+ # Extract the installation directory from the service file
+ SERVICE_INSTALL_DIR=$(grep "WorkingDirectory=" "/etc/systemd/system/transmission-rss-manager.service" | cut -d'=' -f2)
+ if [ -n "$SERVICE_INSTALL_DIR" ]; then
+ INSTALL_DIR="$SERVICE_INSTALL_DIR"
+ log "INFO" "Found existing installation at $INSTALL_DIR from service file"
+ export INSTALL_DIR
+
+ # Check for config file in the detected installation directory
+ if [ -f "$INSTALL_DIR/config.json" ]; then
+ CONFIG_FILE="$INSTALL_DIR/config.json"
+ fi
+ fi
+ fi
+
+ # Check for data directory (tertiary indicator)
+ if [ "$INSTALLATION_DETECTED" = "false" ] && [ -d "${SCRIPT_DIR}/data" ] && [ "$(ls -A "${SCRIPT_DIR}/data" 2>/dev/null)" ]; then
+ INSTALLATION_DETECTED=true
fi
fi
-# Check for data directory (tertiary indicator)
-if [ -d "${SCRIPT_DIR}/data" ] && [ "$(ls -A "${SCRIPT_DIR}/data" 2>/dev/null)" ]; then
- INSTALLATION_DETECTED=true
-fi
-
-if [ "$INSTALLATION_DETECTED" = true ]; then
- IS_UPDATE=true
+# Provide clear feedback about the installation type
+if [ "$IS_UPDATE" = "true" ]; then
+ log "INFO" "Running in UPDATE mode - will preserve existing configuration"
+ log "INFO" "Target installation directory: $INSTALL_DIR"
+ if [ -n "$CONFIG_FILE" ]; then
+ log "INFO" "Using configuration file: $CONFIG_FILE"
+ fi
+
+ # Make sure the variables are set correctly
echo -e "${YELLOW}Existing installation detected. Running in update mode.${NC}"
echo -e "${GREEN}Your existing configuration will be preserved.${NC}"
echo -e "${GREEN}Only application files will be updated.${NC}"
else
+ log "INFO" "Running in FRESH INSTALL mode"
echo -e "${GREEN}Fresh installation. Will create new configuration.${NC}"
fi
export IS_UPDATE
diff --git a/modules/file-creator-module.sh b/modules/file-creator-module.sh
old mode 100644
new mode 100755
index 79ead3a..fc4c8a4
--- a/modules/file-creator-module.sh
+++ b/modules/file-creator-module.sh
@@ -38,8 +38,42 @@ function create_directories() {
function create_config_files() {
echo -e "${YELLOW}Creating configuration files...${NC}"
- # Create initial config.json
- echo "Creating config.json..."
+ # Check if we're updating an existing installation
+ if [ "$IS_UPDATE" = "true" ] && [ -n "$CONFIG_FILE" ] && [ -f "$CONFIG_FILE" ]; then
+ log "INFO" "Preserving existing configuration file: $CONFIG_FILE"
+
+ # Get version from package.json dynamically
+ VERSION=$(grep -oP '"version": "\K[^"]+' "${SCRIPT_DIR}/package.json" 2>/dev/null || echo "2.0.9")
+
+ # Update only the version number in the config file
+ if [ -f "$CONFIG_FILE" ]; then
+ # Save a backup of the config file
+ BACKUP_FILE="${CONFIG_FILE}.bak.$(date +%Y%m%d%H%M%S)"
+ cp "$CONFIG_FILE" "$BACKUP_FILE"
+ log "INFO" "Backed up config to: $BACKUP_FILE"
+
+ # Update version field only
+ if command -v jq &> /dev/null; then
+ # If jq is available, use it to safely update the version
+ jq ".version = \"$VERSION\"" "$CONFIG_FILE" > "${CONFIG_FILE}.new"
+ if [ $? -eq 0 ]; then
+ mv "${CONFIG_FILE}.new" "$CONFIG_FILE"
+ log "INFO" "Updated config version to: $VERSION"
+ else
+ log "WARN" "Failed to update version with jq, keeping original config unchanged"
+ rm -f "${CONFIG_FILE}.new"
+ fi
+ else
+ log "INFO" "jq not available, keeping original config file"
+ fi
+ fi
+
+ # Return early - no need to create a new config
+ return 0
+ fi
+
+ # For fresh installations, create initial config.json
+ echo "Creating new config.json..."
mkdir -p "$CONFIG_DIR"
# Get version from package.json dynamically
diff --git a/public/js/system-status.js b/public/js/system-status.js
index 1c881ea..89e569a 100644
--- a/public/js/system-status.js
+++ b/public/js/system-status.js
@@ -50,13 +50,35 @@ function initSystemStatus() {
const testMode = localStorage.getItem('showUpdateButton') === 'true';
const url = testMode ? '/api/system/check-updates?test=true' : '/api/system/check-updates';
+ // Set a timeout to detect network issues
+ const timeoutId = setTimeout(() => {
+ updateStatusElement.innerHTML = ' Check timed out';
+ showNotification('Update check timed out. Please try again later.', 'warning');
+ }, 10000); // 10 second timeout
+
fetch(url, {
- headers: authHeaders()
+ headers: authHeaders(),
+ // Add a fetch timeout as well
+ signal: AbortSignal.timeout(15000) // 15 second timeout (available in modern browsers)
})
- .then(handleResponse)
+ .then(response => {
+ clearTimeout(timeoutId);
+ // Better error checking
+ if (!response.ok) {
+ return response.json().then(data => {
+ throw new Error(data.message || `Server error: ${response.status}`);
+ }).catch(e => {
+ if (e instanceof SyntaxError) {
+ throw new Error(`Server error: ${response.status}`);
+ }
+ throw e;
+ });
+ }
+ return response.json();
+ })
.then(data => {
if (data.status === 'success') {
- if (data.data.updateAvailable) {
+ if (data.data && data.data.updateAvailable) {
updateStatusElement.innerHTML = ' Update available';
updateAvailableDiv.classList.remove('d-none');
updateAvailableDiv.querySelector('span').textContent =
@@ -65,14 +87,24 @@ function initSystemStatus() {
updateStatusElement.innerHTML = ' Up to date';
}
} else {
+ // Error status but with a response
updateStatusElement.innerHTML = ' Check failed';
showNotification(data.message || 'Failed to check for updates', 'danger');
}
})
.catch(error => {
+ clearTimeout(timeoutId);
console.error('Error checking for updates:', error);
updateStatusElement.innerHTML = ' Check failed';
- showNotification('Failed to connect to server', 'danger');
+
+ // More specific error message based on the error type
+ if (error.name === 'AbortError') {
+ showNotification('Update check timed out. Please try again later.', 'warning');
+ } else if (error.message.includes('Failed to fetch') || error.message.includes('NetworkError')) {
+ showNotification('Network error. Please check your connection and try again.', 'danger');
+ } else {
+ showNotification(error.message || 'Failed to connect to server', 'danger');
+ }
});
}
@@ -88,19 +120,58 @@ function initSystemStatus() {
updateButton.innerHTML = ' Updating...';
showNotification('Applying update. Please wait...', 'info');
+ // Set a timeout for the update process
+ const updateTimeoutId = setTimeout(() => {
+ updateButton.disabled = false;
+ updateButton.innerHTML = ' Update Now';
+ showNotification('Update process timed out. Please try again or check server logs.', 'warning');
+ }, 60000); // 60 second timeout for the entire update process
+
fetch('/api/system/update', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
...authHeaders()
- }
+ },
+ signal: AbortSignal.timeout(45000) // 45 second timeout
})
- .then(handleResponse)
+ .then(response => {
+ // Better error checking
+ if (!response.ok) {
+ return response.json().then(data => {
+ throw new Error(data.message || `Server error: ${response.status}`);
+ }).catch(e => {
+ if (e instanceof SyntaxError) {
+ throw new Error(`Server error: ${response.status}`);
+ }
+ throw e;
+ });
+ }
+ return response.json();
+ })
.then(data => {
+ clearTimeout(updateTimeoutId);
+
if (data.status === 'success') {
showNotification('Update applied successfully. The page will reload in 30 seconds.', 'success');
+
+ // Show a countdown to reload
+ let secondsLeft = 30;
+ updateButton.innerHTML = ` Reloading in ${secondsLeft}s...`;
+
+ const countdownInterval = setInterval(() => {
+ secondsLeft--;
+ updateButton.innerHTML = ` Reloading in ${secondsLeft}s...`;
+
+ if (secondsLeft <= 0) {
+ clearInterval(countdownInterval);
+ window.location.reload();
+ }
+ }, 1000);
+
// Set a timer to reload the page after the service has time to restart
setTimeout(() => {
+ clearInterval(countdownInterval);
window.location.reload();
}, 30000);
} else {
@@ -110,10 +181,19 @@ function initSystemStatus() {
}
})
.catch(error => {
+ clearTimeout(updateTimeoutId);
console.error('Error applying update:', error);
updateButton.disabled = false;
updateButton.innerHTML = ' Update Now';
- showNotification('Failed to connect to server', 'danger');
+
+ // More specific error message based on the error type
+ if (error.name === 'AbortError') {
+ showNotification('Update request timed out. The server might still be processing the update.', 'warning');
+ } else if (error.message.includes('Failed to fetch') || error.message.includes('NetworkError')) {
+ showNotification('Network error. Please check your connection and try again.', 'danger');
+ } else {
+ showNotification(error.message || 'Failed to connect to server', 'danger');
+ }
});
}
diff --git a/scripts/create-module-links.sh b/scripts/create-module-links.sh
index 01cd36f..c7099c7 100755
--- a/scripts/create-module-links.sh
+++ b/scripts/create-module-links.sh
@@ -10,69 +10,203 @@ echo "Module directory: $MODULE_DIR"
# Create a function to make bidirectional symlinks
create_module_symlinks() {
- if [ -d "$MODULE_DIR" ]; then
- # Create symlinks for hyphenated modules
- for module in "$MODULE_DIR"/*-*.js; do
- if [ -f "$module" ]; then
- # Convert hyphenated to camelCase
- BASE_NAME=$(basename "$module")
- CAMEL_NAME=$(echo "$BASE_NAME" | sed -E 's/-([a-z])/\U\1/g')
-
- # Create camelCase symlink if needed
- if [ ! -f "$MODULE_DIR/$CAMEL_NAME" ] && [ ! -L "$MODULE_DIR/$CAMEL_NAME" ]; then
- ln -sf "$BASE_NAME" "$MODULE_DIR/$CAMEL_NAME"
- echo "Created symlink: $CAMEL_NAME -> $BASE_NAME"
- fi
-
- # Create extension-less symlink for both versions
- NO_EXT_BASE="${BASE_NAME%.js}"
- if [ ! -f "$MODULE_DIR/$NO_EXT_BASE" ] && [ ! -L "$MODULE_DIR/$NO_EXT_BASE" ]; then
- ln -sf "$BASE_NAME" "$MODULE_DIR/$NO_EXT_BASE"
- echo "Created symlink: $NO_EXT_BASE -> $BASE_NAME"
- fi
-
- NO_EXT_CAMEL="${CAMEL_NAME%.js}"
- if [ ! -f "$MODULE_DIR/$NO_EXT_CAMEL" ] && [ ! -L "$MODULE_DIR/$NO_EXT_CAMEL" ]; then
- ln -sf "$BASE_NAME" "$MODULE_DIR/$NO_EXT_CAMEL"
- echo "Created symlink: $NO_EXT_CAMEL -> $BASE_NAME"
- fi
- fi
- done
-
- # Create symlinks for camelCase modules (only non-symlinked files)
- for module in "$MODULE_DIR"/[a-z]*[A-Z]*.js; do
- if [ -f "$module" ] && [ ! -L "$module" ]; then
- # Convert camelCase to hyphenated
- BASE_NAME=$(basename "$module")
- HYPHEN_NAME=$(echo "$BASE_NAME" | sed -E 's/([a-z])([A-Z])/\1-\L\2/g')
-
- # Create hyphenated symlink if needed
- if [ ! -f "$MODULE_DIR/$HYPHEN_NAME" ] && [ ! -L "$MODULE_DIR/$HYPHEN_NAME" ]; then
- ln -sf "$BASE_NAME" "$MODULE_DIR/$HYPHEN_NAME"
- echo "Created symlink: $HYPHEN_NAME -> $BASE_NAME"
- fi
-
- # Create extension-less symlink for both versions
- NO_EXT_BASE="${BASE_NAME%.js}"
- if [ ! -f "$MODULE_DIR/$NO_EXT_BASE" ] && [ ! -L "$MODULE_DIR/$NO_EXT_BASE" ]; then
- ln -sf "$BASE_NAME" "$MODULE_DIR/$NO_EXT_BASE"
- echo "Created symlink: $NO_EXT_BASE -> $BASE_NAME"
- fi
-
- NO_EXT_HYPHEN="${HYPHEN_NAME%.js}"
- if [ ! -f "$MODULE_DIR/$NO_EXT_HYPHEN" ] && [ ! -L "$MODULE_DIR/$NO_EXT_HYPHEN" ]; then
- ln -sf "$BASE_NAME" "$MODULE_DIR/$NO_EXT_HYPHEN"
- echo "Created symlink: $NO_EXT_HYPHEN -> $BASE_NAME"
- fi
- fi
- done
-
- echo "Module symlinks created successfully"
- else
+ if [ ! -d "$MODULE_DIR" ]; then
echo "Error: Module directory not found at $MODULE_DIR"
- exit 1
+ mkdir -p "$MODULE_DIR"
+ echo "Created module directory: $MODULE_DIR"
+ fi
+
+ # Check if any .js files exist in the module directory
+ js_file_count=$(find "$MODULE_DIR" -maxdepth 1 -name "*.js" -type f | wc -l)
+ if [ "$js_file_count" -eq 0 ]; then
+ echo "Warning: No JavaScript module files found in $MODULE_DIR"
+ echo "Skipping symlink creation as there are no modules to link"
+ return 0
+ fi
+
+ # Create symlinks for hyphenated modules
+ for module in "$MODULE_DIR"/*-*.js; do
+ if [ -f "$module" ]; then
+ # Convert hyphenated to camelCase
+ BASE_NAME=$(basename "$module")
+ CAMEL_NAME=$(echo "$BASE_NAME" | sed -E 's/-([a-z])/\U\1/g')
+
+ # Create camelCase symlink if needed
+ if [ ! -f "$MODULE_DIR/$CAMEL_NAME" ] && [ ! -L "$MODULE_DIR/$CAMEL_NAME" ]; then
+ if ln -sf "$BASE_NAME" "$MODULE_DIR/$CAMEL_NAME"; then
+ echo "Created symlink: $CAMEL_NAME -> $BASE_NAME"
+ else
+ echo "Error: Failed to create symlink $CAMEL_NAME"
+ fi
+ fi
+
+ # Create extension-less symlink for both versions
+ NO_EXT_BASE="${BASE_NAME%.js}"
+ if [ ! -f "$MODULE_DIR/$NO_EXT_BASE" ] && [ ! -L "$MODULE_DIR/$NO_EXT_BASE" ]; then
+ if ln -sf "$BASE_NAME" "$MODULE_DIR/$NO_EXT_BASE"; then
+ echo "Created symlink: $NO_EXT_BASE -> $BASE_NAME"
+ else
+ echo "Error: Failed to create symlink $NO_EXT_BASE"
+ fi
+ fi
+
+ NO_EXT_CAMEL="${CAMEL_NAME%.js}"
+ if [ ! -f "$MODULE_DIR/$NO_EXT_CAMEL" ] && [ ! -L "$MODULE_DIR/$NO_EXT_CAMEL" ]; then
+ if ln -sf "$BASE_NAME" "$MODULE_DIR/$NO_EXT_CAMEL"; then
+ echo "Created symlink: $NO_EXT_CAMEL -> $BASE_NAME"
+ else
+ echo "Error: Failed to create symlink $NO_EXT_CAMEL"
+ fi
+ fi
+ fi
+ done
+
+ # Create symlinks for camelCase modules (only non-symlinked files)
+ for module in "$MODULE_DIR"/[a-z]*[A-Z]*.js; do
+ if [ -f "$module" ] && [ ! -L "$module" ]; then
+ # Convert camelCase to hyphenated
+ BASE_NAME=$(basename "$module")
+ HYPHEN_NAME=$(echo "$BASE_NAME" | sed -E 's/([a-z])([A-Z])/\1-\L\2/g')
+
+ # Create hyphenated symlink if needed
+ if [ ! -f "$MODULE_DIR/$HYPHEN_NAME" ] && [ ! -L "$MODULE_DIR/$HYPHEN_NAME" ]; then
+ if ln -sf "$BASE_NAME" "$MODULE_DIR/$HYPHEN_NAME"; then
+ echo "Created symlink: $HYPHEN_NAME -> $BASE_NAME"
+ else
+ echo "Error: Failed to create symlink $HYPHEN_NAME"
+ fi
+ fi
+
+ # Create extension-less symlink for both versions
+ NO_EXT_BASE="${BASE_NAME%.js}"
+ if [ ! -f "$MODULE_DIR/$NO_EXT_BASE" ] && [ ! -L "$MODULE_DIR/$NO_EXT_BASE" ]; then
+ if ln -sf "$BASE_NAME" "$MODULE_DIR/$NO_EXT_BASE"; then
+ echo "Created symlink: $NO_EXT_BASE -> $BASE_NAME"
+ else
+ echo "Error: Failed to create symlink $NO_EXT_BASE"
+ fi
+ fi
+
+ NO_EXT_HYPHEN="${HYPHEN_NAME%.js}"
+ if [ ! -f "$MODULE_DIR/$NO_EXT_HYPHEN" ] && [ ! -L "$MODULE_DIR/$NO_EXT_HYPHEN" ]; then
+ if ln -sf "$BASE_NAME" "$MODULE_DIR/$NO_EXT_HYPHEN"; then
+ echo "Created symlink: $NO_EXT_HYPHEN -> $BASE_NAME"
+ else
+ echo "Error: Failed to create symlink $NO_EXT_HYPHEN"
+ fi
+ fi
+ fi
+ done
+
+ echo "Module symlinks created successfully"
+}
+
+# Setup production directory if needed
+setup_production_dir() {
+ # Check if this is running in development environment
+ DEV_DIR="/opt/develop/transmission-rss-manager"
+
+ # Check systemd service file to determine the correct production directory
+ PROD_DIR="/opt/transmission-rss-manager"
+ SERVICE_FILE="/etc/systemd/system/transmission-rss-manager.service"
+
+ if [ -f "$SERVICE_FILE" ]; then
+ # Extract the WorkingDirectory from the service file
+ WORKING_DIR=$(grep "WorkingDirectory=" "$SERVICE_FILE" | cut -d'=' -f2)
+ if [ -n "$WORKING_DIR" ]; then
+ PROD_DIR="$WORKING_DIR"
+ echo "Found production directory from service file: $PROD_DIR"
+ fi
+ fi
+
+ if [ "$APP_DIR" == "$DEV_DIR" ] && [ -d "$DEV_DIR" ]; then
+ echo "Setting up production directory symlinks at $PROD_DIR..."
+
+ # Create the production directory if it doesn't exist
+ if [ ! -d "$PROD_DIR" ]; then
+ if mkdir -p "$PROD_DIR"; then
+ echo "Created production directory: $PROD_DIR"
+ else
+ echo "Error: Failed to create production directory $PROD_DIR"
+ return 1
+ fi
+ fi
+
+ # Create the modules directory in production if it doesn't exist
+ if [ ! -d "$PROD_DIR/modules" ]; then
+ if mkdir -p "$PROD_DIR/modules"; then
+ echo "Created production modules directory: $PROD_DIR/modules"
+ else
+ echo "Error: Failed to create production modules directory"
+ return 1
+ fi
+ fi
+
+ # Check for JavaScript modules in dev directory
+ js_file_count=$(find "$MODULE_DIR" -maxdepth 1 -name "*.js" -type f | wc -l)
+ if [ "$js_file_count" -eq 0 ]; then
+ echo "Warning: No JavaScript module files found in $MODULE_DIR"
+ echo "Skipping production symlink creation"
+ else
+ # Create symlinks from development modules to production modules
+ for module in "$MODULE_DIR"/*.js; do
+ if [ -f "$module" ] && [ ! -L "$module" ]; then
+ MODULE_NAME=$(basename "$module")
+ # Create symlink in production directory
+ if ln -sf "$module" "$PROD_DIR/modules/$MODULE_NAME"; then
+ echo "Created production symlink: $PROD_DIR/modules/$MODULE_NAME -> $module"
+ else
+ echo "Error: Failed to create production symlink for $MODULE_NAME"
+ fi
+ fi
+ done
+ fi
+
+ # Copy server.js to production if it doesn't exist or needs updating
+ if [ -f "$DEV_DIR/server.js" ]; then
+ if [ ! -f "$PROD_DIR/server.js" ] || [ "$DEV_DIR/server.js" -nt "$PROD_DIR/server.js" ]; then
+ if cp "$DEV_DIR/server.js" "$PROD_DIR/server.js"; then
+ echo "Copied server.js to production directory"
+ else
+ echo "Error: Failed to copy server.js to production"
+ fi
+ fi
+ else
+ echo "Warning: server.js not found in development directory"
+ fi
+
+ # Create data directory in production if it doesn't exist
+ if mkdir -p "$PROD_DIR/data"; then
+ echo "Ensured data directory exists in production"
+ else
+ echo "Error: Failed to create production data directory"
+ fi
+
+ # Make sure scripts directory exists in production
+ if mkdir -p "$PROD_DIR/scripts"; then
+ echo "Ensured scripts directory exists in production"
+ else
+ echo "Error: Failed to create production scripts directory"
+ fi
+
+ # Copy test-and-start.sh to production
+ if [ -f "$DEV_DIR/scripts/test-and-start.sh" ]; then
+ if cp "$DEV_DIR/scripts/test-and-start.sh" "$PROD_DIR/scripts/test-and-start.sh"; then
+ chmod +x "$PROD_DIR/scripts/test-and-start.sh"
+ echo "Copied test-and-start.sh script to production"
+ else
+ echo "Error: Failed to copy test-and-start.sh to production"
+ fi
+ else
+ echo "Warning: test-and-start.sh not found in development scripts directory"
+ fi
+
+ echo "Production directory setup complete"
fi
}
# Execute the symlink creation function
-create_module_symlinks
\ No newline at end of file
+create_module_symlinks
+
+# Setup production directory if needed
+setup_production_dir
\ No newline at end of file
diff --git a/scripts/update.sh b/scripts/update.sh
index e381710..5818683 100755
--- a/scripts/update.sh
+++ b/scripts/update.sh
@@ -56,6 +56,11 @@ fi
# Install any new npm dependencies
echo -e "${YELLOW}Installing dependencies...${NC}"
npm install
+if [ $? -ne 0 ]; then
+ echo -e "${RED}Failed to install npm dependencies. Update aborted.${NC}"
+ echo -e "Please check the error messages above and try again."
+ exit 1
+fi
# Apply any local configuration changes
if git stash list | grep -q "stash@{0}"; then
diff --git a/server.js b/server.js
index e0da954..1afa700 100644
--- a/server.js
+++ b/server.js
@@ -45,6 +45,7 @@ function loadModule(baseName) {
`./modules/${baseName.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()}`
];
+ console.log(`Attempting to load module: ${baseName}`);
let lastError = null;
for (const modulePath of paths) {
try {
@@ -1236,7 +1237,16 @@ app.get('/api/auth/validate', authenticateJWT, (req, res) => {
});
});
-// System status and update endpoints
+// System endpoints - consolidated from server-endpoints.js
+// Ensure TransmissionClient has the necessary methods
+if (!TransmissionClient.prototype.sessionGet && TransmissionClient.prototype.getStatus) {
+ // Add compatibility method if missing
+ TransmissionClient.prototype.sessionGet = async function() {
+ return this.getStatus();
+ };
+}
+
+// System status endpoint
app.get('/api/system/status', authenticateJWT, async (req, res) => {
try {
// Get system uptime
@@ -1249,11 +1259,12 @@ app.get('/api/system/status', authenticateJWT, async (req, res) => {
// Check transmission connection
let transmissionStatus = 'Connected';
try {
- const status = await transmissionClient.getStatus();
- if (!status.connected) {
+ const status = await transmissionClient.sessionGet();
+ if (!status || (status.connected === false)) {
transmissionStatus = 'Disconnected';
}
} catch (err) {
+ console.error('Transmission connection error:', err);
transmissionStatus = 'Disconnected';
}
@@ -1278,11 +1289,34 @@ app.get('/api/system/status', authenticateJWT, async (req, res) => {
app.get('/api/system/check-updates', authenticateJWT, async (req, res) => {
try {
// Check if git is available and if this is a git repository
- const isGitRepo = fs.existsSync(path.join(__dirname, '.git'));
+ let isGitRepo = false;
+ let isGitAvailable = false;
+
+ try {
+ // First check if git command is available
+ await execAsync('which git');
+ isGitAvailable = true;
+
+ // Then check if directory is a git repository
+ isGitRepo = await fs.access(path.join(__dirname, '.git'))
+ .then(() => true)
+ .catch(() => false);
+ } catch (error) {
+ console.error('Error checking git availability:', error);
+ isGitAvailable = false;
+ }
+
+ if (!isGitAvailable) {
+ return res.json({
+ status: 'error',
+ message: 'Git is not installed or not available. Please install Git to enable updates.'
+ });
+ }
+
if (!isGitRepo) {
return res.json({
status: 'error',
- message: 'This installation is not set up for updates. Please use the bootstrap installer.'
+ message: 'This installation is not set up as a Git repository. Please use the bootstrap installer.'
});
}
@@ -1370,11 +1404,34 @@ app.get('/api/system/check-updates', authenticateJWT, async (req, res) => {
app.post('/api/system/update', authenticateJWT, async (req, res) => {
try {
// Check if git is available and if this is a git repository
- const isGitRepo = fs.existsSync(path.join(__dirname, '.git'));
+ let isGitRepo = false;
+ let isGitAvailable = false;
+
+ try {
+ // First check if git command is available
+ await execAsync('which git');
+ isGitAvailable = true;
+
+ // Then check if directory is a git repository
+ isGitRepo = await fs.access(path.join(__dirname, '.git'))
+ .then(() => true)
+ .catch(() => false);
+ } catch (error) {
+ console.error('Error checking git availability:', error);
+ isGitAvailable = false;
+ }
+
+ if (!isGitAvailable) {
+ return res.status(400).json({
+ status: 'error',
+ message: 'Git is not installed or not available. Please install Git to enable updates.'
+ });
+ }
+
if (!isGitRepo) {
return res.status(400).json({
status: 'error',
- message: 'This installation is not set up for updates. Please use the bootstrap installer.'
+ message: 'This installation is not set up as a Git repository. Please use the bootstrap installer.'
});
}