Fix update functionality and improve documentation
- Fixed update detection in install scripts - Added Git availability checks to update system - Improved error handling for update endpoint - Added detailed Git requirements to README - Added troubleshooting section for update issues 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
9b45e669e2
commit
2dcd4becef
49
README.md
Executable file → Normal file
49
README.md
Executable file → Normal file
@ -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!
|
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
|
## Changelog
|
||||||
|
|
||||||
### v2.0.9 (2025-03-07)
|
### v2.0.9 (2025-03-07)
|
||||||
@ -104,8 +114,13 @@ A comprehensive web-based tool to automate and manage your Transmission torrent
|
|||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
- Ubuntu/Debian-based system (may work on other Linux distributions)
|
- 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)
|
- 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
|
### System Requirements
|
||||||
|
|
||||||
@ -338,6 +353,38 @@ The system will:
|
|||||||
- Restore your configuration
|
- Restore your configuration
|
||||||
- Restart the service automatically
|
- 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
|
### Using the Installer
|
||||||
|
|
||||||
You can also update by running the installer again:
|
You can also update by running the installer again:
|
||||||
|
@ -28,14 +28,44 @@ SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
|
|||||||
# Create modules directory if it doesn't exist
|
# Create modules directory if it doesn't exist
|
||||||
mkdir -p "${SCRIPT_DIR}/modules"
|
mkdir -p "${SCRIPT_DIR}/modules"
|
||||||
|
|
||||||
# Check for installation type
|
# Check for installation type in multiple locations
|
||||||
IS_UPDATE=false
|
IS_UPDATE=false
|
||||||
if [ -f "${SCRIPT_DIR}/config.json" ]; then
|
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
|
IS_UPDATE=true
|
||||||
echo -e "${YELLOW}Existing installation detected. Running in update mode.${NC}"
|
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}"
|
echo -e "${GREEN}Your existing configuration will be preserved.${NC}"
|
||||||
else
|
|
||||||
echo -e "${GREEN}Fresh installation. Will create new configuration.${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
|
fi
|
||||||
|
|
||||||
# Check if modules exist, if not, extract them
|
# Check if modules exist, if not, extract them
|
||||||
|
@ -28,6 +28,10 @@ source "${SCRIPT_DIR}/modules/utils-module.sh"
|
|||||||
echo -e "${BOLD}==================================================${NC}"
|
echo -e "${BOLD}==================================================${NC}"
|
||||||
echo -e "${BOLD} Transmission RSS Manager Installer ${NC}"
|
echo -e "${BOLD} Transmission RSS Manager Installer ${NC}"
|
||||||
VERSION=$(grep -oP '"version": "\K[^"]+' "${SCRIPT_DIR}/package.json" 2>/dev/null || echo "Unknown")
|
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} Version ${VERSION} - Git Edition ${NC}"
|
||||||
echo -e "${BOLD}==================================================${NC}"
|
echo -e "${BOLD}==================================================${NC}"
|
||||||
echo
|
echo
|
||||||
@ -42,35 +46,74 @@ fi
|
|||||||
IS_UPDATE=false
|
IS_UPDATE=false
|
||||||
INSTALLATION_DETECTED=false
|
INSTALLATION_DETECTED=false
|
||||||
|
|
||||||
# Check for config.json file (primary indicator)
|
# Check if we have existing config info from install-script.sh
|
||||||
if [ -f "${SCRIPT_DIR}/config.json" ]; then
|
if [ -n "$EXISTING_INSTALL_DIR" ] && [ -n "$EXISTING_CONFIG_PATH" ]; then
|
||||||
INSTALLATION_DETECTED=true
|
INSTALLATION_DETECTED=true
|
||||||
fi
|
IS_UPDATE=true
|
||||||
|
# Use the existing installation directory as our target
|
||||||
# Check for service file (secondary indicator)
|
INSTALL_DIR="$EXISTING_INSTALL_DIR"
|
||||||
if [ -f "/etc/systemd/system/transmission-rss-manager.service" ]; then
|
CONFIG_FILE="$EXISTING_CONFIG_PATH"
|
||||||
INSTALLATION_DETECTED=true
|
log "INFO" "Using existing installation at $INSTALL_DIR detected by install-script.sh"
|
||||||
|
|
||||||
# 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
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check for data directory (tertiary indicator)
|
# Provide clear feedback about the installation type
|
||||||
if [ -d "${SCRIPT_DIR}/data" ] && [ "$(ls -A "${SCRIPT_DIR}/data" 2>/dev/null)" ]; then
|
if [ "$IS_UPDATE" = "true" ]; then
|
||||||
INSTALLATION_DETECTED=true
|
log "INFO" "Running in UPDATE mode - will preserve existing configuration"
|
||||||
fi
|
log "INFO" "Target installation directory: $INSTALL_DIR"
|
||||||
|
if [ -n "$CONFIG_FILE" ]; then
|
||||||
|
log "INFO" "Using configuration file: $CONFIG_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
if [ "$INSTALLATION_DETECTED" = true ]; then
|
# Make sure the variables are set correctly
|
||||||
IS_UPDATE=true
|
|
||||||
echo -e "${YELLOW}Existing installation detected. Running in update mode.${NC}"
|
echo -e "${YELLOW}Existing installation detected. Running in update mode.${NC}"
|
||||||
echo -e "${GREEN}Your existing configuration will be preserved.${NC}"
|
echo -e "${GREEN}Your existing configuration will be preserved.${NC}"
|
||||||
echo -e "${GREEN}Only application files will be updated.${NC}"
|
echo -e "${GREEN}Only application files will be updated.${NC}"
|
||||||
else
|
else
|
||||||
|
log "INFO" "Running in FRESH INSTALL mode"
|
||||||
echo -e "${GREEN}Fresh installation. Will create new configuration.${NC}"
|
echo -e "${GREEN}Fresh installation. Will create new configuration.${NC}"
|
||||||
fi
|
fi
|
||||||
export IS_UPDATE
|
export IS_UPDATE
|
||||||
|
38
modules/file-creator-module.sh
Normal file → Executable file
38
modules/file-creator-module.sh
Normal file → Executable file
@ -38,8 +38,42 @@ function create_directories() {
|
|||||||
function create_config_files() {
|
function create_config_files() {
|
||||||
echo -e "${YELLOW}Creating configuration files...${NC}"
|
echo -e "${YELLOW}Creating configuration files...${NC}"
|
||||||
|
|
||||||
# Create initial config.json
|
# Check if we're updating an existing installation
|
||||||
echo "Creating config.json..."
|
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"
|
mkdir -p "$CONFIG_DIR"
|
||||||
|
|
||||||
# Get version from package.json dynamically
|
# Get version from package.json dynamically
|
||||||
|
@ -50,13 +50,35 @@ function initSystemStatus() {
|
|||||||
const testMode = localStorage.getItem('showUpdateButton') === 'true';
|
const testMode = localStorage.getItem('showUpdateButton') === 'true';
|
||||||
const url = testMode ? '/api/system/check-updates?test=true' : '/api/system/check-updates';
|
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 = '<i class="fas fa-times-circle text-danger"></i> Check timed out';
|
||||||
|
showNotification('Update check timed out. Please try again later.', 'warning');
|
||||||
|
}, 10000); // 10 second timeout
|
||||||
|
|
||||||
fetch(url, {
|
fetch(url, {
|
||||||
headers: authHeaders()
|
headers: authHeaders(),
|
||||||
|
// Add a fetch timeout as well
|
||||||
|
signal: AbortSignal.timeout(15000) // 15 second timeout (available in modern browsers)
|
||||||
|
})
|
||||||
|
.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(handleResponse)
|
|
||||||
.then(data => {
|
.then(data => {
|
||||||
if (data.status === 'success') {
|
if (data.status === 'success') {
|
||||||
if (data.data.updateAvailable) {
|
if (data.data && data.data.updateAvailable) {
|
||||||
updateStatusElement.innerHTML = '<i class="fas fa-exclamation-circle text-warning"></i> Update available';
|
updateStatusElement.innerHTML = '<i class="fas fa-exclamation-circle text-warning"></i> Update available';
|
||||||
updateAvailableDiv.classList.remove('d-none');
|
updateAvailableDiv.classList.remove('d-none');
|
||||||
updateAvailableDiv.querySelector('span').textContent =
|
updateAvailableDiv.querySelector('span').textContent =
|
||||||
@ -65,14 +87,24 @@ function initSystemStatus() {
|
|||||||
updateStatusElement.innerHTML = '<i class="fas fa-check-circle text-success"></i> Up to date';
|
updateStatusElement.innerHTML = '<i class="fas fa-check-circle text-success"></i> Up to date';
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Error status but with a response
|
||||||
updateStatusElement.innerHTML = '<i class="fas fa-times-circle text-danger"></i> Check failed';
|
updateStatusElement.innerHTML = '<i class="fas fa-times-circle text-danger"></i> Check failed';
|
||||||
showNotification(data.message || 'Failed to check for updates', 'danger');
|
showNotification(data.message || 'Failed to check for updates', 'danger');
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
|
clearTimeout(timeoutId);
|
||||||
console.error('Error checking for updates:', error);
|
console.error('Error checking for updates:', error);
|
||||||
updateStatusElement.innerHTML = '<i class="fas fa-times-circle text-danger"></i> Check failed';
|
updateStatusElement.innerHTML = '<i class="fas fa-times-circle text-danger"></i> 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 = '<i class="fas fa-circle-notch fa-spin"></i> Updating...';
|
updateButton.innerHTML = '<i class="fas fa-circle-notch fa-spin"></i> Updating...';
|
||||||
showNotification('Applying update. Please wait...', 'info');
|
showNotification('Applying update. Please wait...', 'info');
|
||||||
|
|
||||||
|
// Set a timeout for the update process
|
||||||
|
const updateTimeoutId = setTimeout(() => {
|
||||||
|
updateButton.disabled = false;
|
||||||
|
updateButton.innerHTML = '<i class="fas fa-download"></i> 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', {
|
fetch('/api/system/update', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
...authHeaders()
|
...authHeaders()
|
||||||
}
|
},
|
||||||
|
signal: AbortSignal.timeout(45000) // 45 second timeout
|
||||||
|
})
|
||||||
|
.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(handleResponse)
|
|
||||||
.then(data => {
|
.then(data => {
|
||||||
|
clearTimeout(updateTimeoutId);
|
||||||
|
|
||||||
if (data.status === 'success') {
|
if (data.status === 'success') {
|
||||||
showNotification('Update applied successfully. The page will reload in 30 seconds.', 'success');
|
showNotification('Update applied successfully. The page will reload in 30 seconds.', 'success');
|
||||||
|
|
||||||
|
// Show a countdown to reload
|
||||||
|
let secondsLeft = 30;
|
||||||
|
updateButton.innerHTML = `<i class="fas fa-sync"></i> Reloading in ${secondsLeft}s...`;
|
||||||
|
|
||||||
|
const countdownInterval = setInterval(() => {
|
||||||
|
secondsLeft--;
|
||||||
|
updateButton.innerHTML = `<i class="fas fa-sync"></i> 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
|
// Set a timer to reload the page after the service has time to restart
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
clearInterval(countdownInterval);
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
}, 30000);
|
}, 30000);
|
||||||
} else {
|
} else {
|
||||||
@ -110,10 +181,19 @@ function initSystemStatus() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
|
clearTimeout(updateTimeoutId);
|
||||||
console.error('Error applying update:', error);
|
console.error('Error applying update:', error);
|
||||||
updateButton.disabled = false;
|
updateButton.disabled = false;
|
||||||
updateButton.innerHTML = '<i class="fas fa-download"></i> Update Now';
|
updateButton.innerHTML = '<i class="fas fa-download"></i> 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');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,20 @@ echo "Module directory: $MODULE_DIR"
|
|||||||
|
|
||||||
# Create a function to make bidirectional symlinks
|
# Create a function to make bidirectional symlinks
|
||||||
create_module_symlinks() {
|
create_module_symlinks() {
|
||||||
if [ -d "$MODULE_DIR" ]; then
|
if [ ! -d "$MODULE_DIR" ]; then
|
||||||
|
echo "Error: Module directory not found at $MODULE_DIR"
|
||||||
|
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
|
# Create symlinks for hyphenated modules
|
||||||
for module in "$MODULE_DIR"/*-*.js; do
|
for module in "$MODULE_DIR"/*-*.js; do
|
||||||
if [ -f "$module" ]; then
|
if [ -f "$module" ]; then
|
||||||
@ -20,21 +33,30 @@ create_module_symlinks() {
|
|||||||
|
|
||||||
# Create camelCase symlink if needed
|
# Create camelCase symlink if needed
|
||||||
if [ ! -f "$MODULE_DIR/$CAMEL_NAME" ] && [ ! -L "$MODULE_DIR/$CAMEL_NAME" ]; then
|
if [ ! -f "$MODULE_DIR/$CAMEL_NAME" ] && [ ! -L "$MODULE_DIR/$CAMEL_NAME" ]; then
|
||||||
ln -sf "$BASE_NAME" "$MODULE_DIR/$CAMEL_NAME"
|
if ln -sf "$BASE_NAME" "$MODULE_DIR/$CAMEL_NAME"; then
|
||||||
echo "Created symlink: $CAMEL_NAME -> $BASE_NAME"
|
echo "Created symlink: $CAMEL_NAME -> $BASE_NAME"
|
||||||
|
else
|
||||||
|
echo "Error: Failed to create symlink $CAMEL_NAME"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Create extension-less symlink for both versions
|
# Create extension-less symlink for both versions
|
||||||
NO_EXT_BASE="${BASE_NAME%.js}"
|
NO_EXT_BASE="${BASE_NAME%.js}"
|
||||||
if [ ! -f "$MODULE_DIR/$NO_EXT_BASE" ] && [ ! -L "$MODULE_DIR/$NO_EXT_BASE" ]; then
|
if [ ! -f "$MODULE_DIR/$NO_EXT_BASE" ] && [ ! -L "$MODULE_DIR/$NO_EXT_BASE" ]; then
|
||||||
ln -sf "$BASE_NAME" "$MODULE_DIR/$NO_EXT_BASE"
|
if ln -sf "$BASE_NAME" "$MODULE_DIR/$NO_EXT_BASE"; then
|
||||||
echo "Created symlink: $NO_EXT_BASE -> $BASE_NAME"
|
echo "Created symlink: $NO_EXT_BASE -> $BASE_NAME"
|
||||||
|
else
|
||||||
|
echo "Error: Failed to create symlink $NO_EXT_BASE"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
NO_EXT_CAMEL="${CAMEL_NAME%.js}"
|
NO_EXT_CAMEL="${CAMEL_NAME%.js}"
|
||||||
if [ ! -f "$MODULE_DIR/$NO_EXT_CAMEL" ] && [ ! -L "$MODULE_DIR/$NO_EXT_CAMEL" ]; then
|
if [ ! -f "$MODULE_DIR/$NO_EXT_CAMEL" ] && [ ! -L "$MODULE_DIR/$NO_EXT_CAMEL" ]; then
|
||||||
ln -sf "$BASE_NAME" "$MODULE_DIR/$NO_EXT_CAMEL"
|
if ln -sf "$BASE_NAME" "$MODULE_DIR/$NO_EXT_CAMEL"; then
|
||||||
echo "Created symlink: $NO_EXT_CAMEL -> $BASE_NAME"
|
echo "Created symlink: $NO_EXT_CAMEL -> $BASE_NAME"
|
||||||
|
else
|
||||||
|
echo "Error: Failed to create symlink $NO_EXT_CAMEL"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
@ -48,31 +70,143 @@ create_module_symlinks() {
|
|||||||
|
|
||||||
# Create hyphenated symlink if needed
|
# Create hyphenated symlink if needed
|
||||||
if [ ! -f "$MODULE_DIR/$HYPHEN_NAME" ] && [ ! -L "$MODULE_DIR/$HYPHEN_NAME" ]; then
|
if [ ! -f "$MODULE_DIR/$HYPHEN_NAME" ] && [ ! -L "$MODULE_DIR/$HYPHEN_NAME" ]; then
|
||||||
ln -sf "$BASE_NAME" "$MODULE_DIR/$HYPHEN_NAME"
|
if ln -sf "$BASE_NAME" "$MODULE_DIR/$HYPHEN_NAME"; then
|
||||||
echo "Created symlink: $HYPHEN_NAME -> $BASE_NAME"
|
echo "Created symlink: $HYPHEN_NAME -> $BASE_NAME"
|
||||||
|
else
|
||||||
|
echo "Error: Failed to create symlink $HYPHEN_NAME"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Create extension-less symlink for both versions
|
# Create extension-less symlink for both versions
|
||||||
NO_EXT_BASE="${BASE_NAME%.js}"
|
NO_EXT_BASE="${BASE_NAME%.js}"
|
||||||
if [ ! -f "$MODULE_DIR/$NO_EXT_BASE" ] && [ ! -L "$MODULE_DIR/$NO_EXT_BASE" ]; then
|
if [ ! -f "$MODULE_DIR/$NO_EXT_BASE" ] && [ ! -L "$MODULE_DIR/$NO_EXT_BASE" ]; then
|
||||||
ln -sf "$BASE_NAME" "$MODULE_DIR/$NO_EXT_BASE"
|
if ln -sf "$BASE_NAME" "$MODULE_DIR/$NO_EXT_BASE"; then
|
||||||
echo "Created symlink: $NO_EXT_BASE -> $BASE_NAME"
|
echo "Created symlink: $NO_EXT_BASE -> $BASE_NAME"
|
||||||
|
else
|
||||||
|
echo "Error: Failed to create symlink $NO_EXT_BASE"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
NO_EXT_HYPHEN="${HYPHEN_NAME%.js}"
|
NO_EXT_HYPHEN="${HYPHEN_NAME%.js}"
|
||||||
if [ ! -f "$MODULE_DIR/$NO_EXT_HYPHEN" ] && [ ! -L "$MODULE_DIR/$NO_EXT_HYPHEN" ]; then
|
if [ ! -f "$MODULE_DIR/$NO_EXT_HYPHEN" ] && [ ! -L "$MODULE_DIR/$NO_EXT_HYPHEN" ]; then
|
||||||
ln -sf "$BASE_NAME" "$MODULE_DIR/$NO_EXT_HYPHEN"
|
if ln -sf "$BASE_NAME" "$MODULE_DIR/$NO_EXT_HYPHEN"; then
|
||||||
echo "Created symlink: $NO_EXT_HYPHEN -> $BASE_NAME"
|
echo "Created symlink: $NO_EXT_HYPHEN -> $BASE_NAME"
|
||||||
|
else
|
||||||
|
echo "Error: Failed to create symlink $NO_EXT_HYPHEN"
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "Module symlinks created successfully"
|
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
|
else
|
||||||
echo "Error: Module directory not found at $MODULE_DIR"
|
echo "Error: Failed to create production directory $PROD_DIR"
|
||||||
exit 1
|
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
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Execute the symlink creation function
|
# Execute the symlink creation function
|
||||||
create_module_symlinks
|
create_module_symlinks
|
||||||
|
|
||||||
|
# Setup production directory if needed
|
||||||
|
setup_production_dir
|
@ -56,6 +56,11 @@ fi
|
|||||||
# Install any new npm dependencies
|
# Install any new npm dependencies
|
||||||
echo -e "${YELLOW}Installing dependencies...${NC}"
|
echo -e "${YELLOW}Installing dependencies...${NC}"
|
||||||
npm install
|
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
|
# Apply any local configuration changes
|
||||||
if git stash list | grep -q "stash@{0}"; then
|
if git stash list | grep -q "stash@{0}"; then
|
||||||
|
71
server.js
71
server.js
@ -45,6 +45,7 @@ function loadModule(baseName) {
|
|||||||
`./modules/${baseName.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()}`
|
`./modules/${baseName.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()}`
|
||||||
];
|
];
|
||||||
|
|
||||||
|
console.log(`Attempting to load module: ${baseName}`);
|
||||||
let lastError = null;
|
let lastError = null;
|
||||||
for (const modulePath of paths) {
|
for (const modulePath of paths) {
|
||||||
try {
|
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) => {
|
app.get('/api/system/status', authenticateJWT, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
// Get system uptime
|
// Get system uptime
|
||||||
@ -1249,11 +1259,12 @@ app.get('/api/system/status', authenticateJWT, async (req, res) => {
|
|||||||
// Check transmission connection
|
// Check transmission connection
|
||||||
let transmissionStatus = 'Connected';
|
let transmissionStatus = 'Connected';
|
||||||
try {
|
try {
|
||||||
const status = await transmissionClient.getStatus();
|
const status = await transmissionClient.sessionGet();
|
||||||
if (!status.connected) {
|
if (!status || (status.connected === false)) {
|
||||||
transmissionStatus = 'Disconnected';
|
transmissionStatus = 'Disconnected';
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.error('Transmission connection error:', err);
|
||||||
transmissionStatus = 'Disconnected';
|
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) => {
|
app.get('/api/system/check-updates', authenticateJWT, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
// Check if git is available and if this is a git repository
|
// 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) {
|
if (!isGitRepo) {
|
||||||
return res.json({
|
return res.json({
|
||||||
status: 'error',
|
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) => {
|
app.post('/api/system/update', authenticateJWT, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
// Check if git is available and if this is a git repository
|
// 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) {
|
if (!isGitRepo) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
status: 'error',
|
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.'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user