Compare commits
63 Commits
bd55ef8613
...
main
Author | SHA1 | Date | |
---|---|---|---|
897862184f | |||
90a6e5e16b | |||
cbae1d57fe | |||
d2d2ea976b | |||
2705989ff6 | |||
8589a0833e | |||
467979971a | |||
5ce348d61e | |||
dc4131f04c | |||
5261f7b4f4 | |||
b8818a9bec | |||
3ff0a50553 | |||
c0a7362226 | |||
dd08278e28 | |||
980a6ca3a4 | |||
1ff479a3cf | |||
313c85ee4b | |||
eaed045323 | |||
2dcd4becef | |||
9b45e669e2 | |||
f2b217ad84 | |||
5a1318bbf2 | |||
3aee416cda | |||
6dc2df3cee | |||
83222078d9 | |||
16c73bca70 | |||
852de32907 | |||
35420335d7 | |||
302c75c534 | |||
8887f6fda1 | |||
70ccb8f4fd | |||
301684886f | |||
f28d49284e | |||
54871518fc | |||
72d230706a | |||
0bce35d899 | |||
484a021936 | |||
16a7c0c0b6 | |||
e7076859b7 | |||
4196914fbd | |||
36ac18c998 | |||
626b24d35e | |||
a5afa1bb80 | |||
40878c7d3a | |||
588ba1ea01 | |||
a3924912f1 | |||
4e4fd09811 | |||
8af47ed35c | |||
1c16243a2d | |||
306049abdd | |||
1d99962024 | |||
95a086dff7 | |||
455006992d | |||
b54086cfd0 | |||
aa3978903b | |||
a1773d0bae | |||
1795373b42 | |||
d2babeba27 | |||
e05c8da811 | |||
4c68a1ac07 | |||
c495bce21f | |||
3cfc5f3489 | |||
f5404c241d |
8
.env.install
Normal file
8
.env.install
Normal file
@@ -0,0 +1,8 @@
|
||||
export TRANSMISSION_REMOTE=true
|
||||
export TRANSMISSION_HOST="192.168.5.19"
|
||||
export TRANSMISSION_PORT="9091"
|
||||
export TRANSMISSION_USER=""
|
||||
export TRANSMISSION_PASS=""
|
||||
export TRANSMISSION_RPC_PATH="/transmission/rpc"
|
||||
export REMOTE_DOWNLOAD_DIR="/downloads"
|
||||
export LOCAL_DOWNLOAD_DIR="/media"
|
132
README.md
Executable file → Normal file
132
README.md
Executable file → Normal file
@@ -1,9 +1,88 @@
|
||||
# Transmission RSS Manager v2.0.1
|
||||
# Transmission RSS Manager v2.0.12
|
||||
|
||||
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.12 (2025-03-10)
|
||||
- **Fixed**: Removed persistent floating update notification that wouldn't disappear
|
||||
- **Fixed**: Major localStorage cleanup to prevent notification state persistence
|
||||
- **Improved**: Complete rewrite of update notification system to use dashboard-only alerts
|
||||
- **Improved**: Added DOM cleanup to remove any rogue notification elements
|
||||
|
||||
### v2.0.11 (2025-03-10)
|
||||
- **Fixed**: Fixed update button persistence with advanced floating notification
|
||||
- **Fixed**: Resolved version display issues with direct package.json reading
|
||||
- **Fixed**: Improved update process for better version reporting
|
||||
- **Fixed**: Resolved conflict between test mode and actual update status
|
||||
- **Added**: Refresh button on update notification for easier version checking
|
||||
- **Added**: Clear test mode indicators to prevent confusion
|
||||
- **Improved**: Enhanced cache busting for more reliable version checking
|
||||
- **Improved**: Better user feedback during update process
|
||||
|
||||
### v2.0.10 (2025-03-10)
|
||||
- **Fixed**: Fixed "fs.existsSync is not a function" error in update check
|
||||
- **Improved**: Better error handling for git repository checks
|
||||
- **Improved**: More robust file system operations for update detection
|
||||
|
||||
### v2.0.9 (2025-03-07)
|
||||
- **Fixed**: Update button now appears properly on dashboard
|
||||
- **Fixed**: Remote Transmission connection issues resolved
|
||||
- **Fixed**: Improved connection test with better error handling
|
||||
- **Added**: System status and update endpoints for version checking
|
||||
- **Improved**: Update detection and notification on dashboard
|
||||
- **Improved**: Better error handling for git operations
|
||||
|
||||
### v2.0.8 (2025-03-07)
|
||||
- **Fixed**: Module import issues on fresh installations with proper file extension handling
|
||||
- **Fixed**: Adding compatibility symlinks for different module naming styles
|
||||
- **Improved**: Server.js now uses consistent module import paths with .js extensions
|
||||
- **Improved**: More robust module file handling in the installer
|
||||
|
||||
### v2.0.7 (2025-03-07)
|
||||
- **Fixed**: Installation directory handling with prompt for choosing install path
|
||||
- **Fixed**: Bootstrap-installer now defaults to /opt/trans-install with user configuration option
|
||||
- **Improved**: Better detection and usage of existing installation directories during updates
|
||||
- **Improved**: Updated documentation to clarify default installation paths
|
||||
|
||||
### v2.0.6 (2025-03-05)
|
||||
- **Added**: Non-interactive mode support for scripted installations
|
||||
- **Improved**: Remote Transmission configuration collection in install-script.sh
|
||||
- **Improved**: Better handling of piped input for remote configuration details
|
||||
- **Improved**: Added debug output to help troubleshoot configuration issues
|
||||
- **Note**: When updating an existing installation, manual configuration of remote settings through the web interface may still be required
|
||||
|
||||
### v2.0.5 (2025-03-05)
|
||||
- **Fixed**: Config file now properly stored in /etc/transmission-rss-manager directory
|
||||
- **Fixed**: Remote Transmission detection in install-script.sh
|
||||
- **Improved**: Enhanced symlink handling between installation dir and config dir
|
||||
- **Improved**: Better environment variable passing between install scripts
|
||||
|
||||
### v2.0.4 (2025-03-05)
|
||||
- **Fixed**: Remote transmission detection in installer
|
||||
- **Fixed**: Configuration directory creation and permissions
|
||||
- **Fixed**: Config file symlink now ensures application finds correct config location
|
||||
|
||||
### v2.0.3 (2025-03-05)
|
||||
- **New**: Configuration file now stored in `/etc/transmission-rss-manager/`
|
||||
- **Improved**: Installer creates config directory with proper permissions
|
||||
- **Improved**: Application now stores path to installation directory for easier updates
|
||||
|
||||
### v2.0.2 (2025-03-05)
|
||||
- **Fixed**: Bug in installer that would prompt to install Transmission for remote configurations
|
||||
- **Improved**: Better detection of remote vs local Transmission installations
|
||||
- **Improved**: Installation process now checks the TRANSMISSION_REMOTE flag correctly
|
||||
|
||||
### v2.0.1 (2025-03-05)
|
||||
- **New**: Automatic detection of existing installations for seamless updates
|
||||
- **Improved**: Enhanced update process that preserves existing configurations
|
||||
@@ -48,11 +127,21 @@ A comprehensive web-based tool to automate and manage your Transmission torrent
|
||||
|
||||
## Installation
|
||||
|
||||
### Known Issues
|
||||
|
||||
- When updating an existing installation with Remote Transmission settings, you may need to manually configure the remote settings through the web interface after installation.
|
||||
- The installer will properly detect and collect remote Transmission settings, but they may not be applied to the configuration file in update mode.
|
||||
|
||||
### 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
|
||||
|
||||
@@ -61,6 +150,10 @@ A comprehensive web-based tool to automate and manage your Transmission torrent
|
||||
- Disk: At least 200MB for the application, plus storage space for your media
|
||||
- Network: Internet connection for RSS feed fetching and torrent downloading
|
||||
|
||||
### Installation Directory
|
||||
|
||||
**Note**: By default, the application will be installed to `/opt/trans-install`. During installation, you'll be prompted to choose a different directory if needed. If you're updating an existing installation, the installer will detect and use your current installation path.
|
||||
|
||||
### Automatic Installation
|
||||
|
||||
The easiest way to install Transmission RSS Manager is with the bootstrap installer:
|
||||
@@ -127,10 +220,11 @@ If you prefer to install manually:
|
||||
|
||||
### Main Configuration Options
|
||||
|
||||
The system can be configured through the web interface or by editing the `config.json` file:
|
||||
The system can be configured through the web interface or by editing the configuration file, which is located at `/etc/transmission-rss-manager/config.json` (or at the application's installation directory as a fallback):
|
||||
|
||||
```json
|
||||
{
|
||||
"installPath": "/opt/transmission-rss-manager",
|
||||
"transmissionConfig": {
|
||||
"host": "localhost",
|
||||
"port": 9091,
|
||||
@@ -280,6 +374,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:
|
||||
|
@@ -11,9 +11,14 @@ NC='\033[0m' # No Color
|
||||
BOLD='\033[1m'
|
||||
|
||||
# Installation directory
|
||||
INSTALL_DIR="/opt/trans-install"
|
||||
DEFAULT_INSTALL_DIR="/opt/trans-install"
|
||||
REPO_URL="https://git.powerdata.dk/masterdraco/transmission-rss-manager.git"
|
||||
|
||||
# Ask for installation directory
|
||||
echo -e "${YELLOW}Where would you like to install Transmission RSS Manager?${NC}"
|
||||
read -p "Installation directory [$DEFAULT_INSTALL_DIR]: " input_install_dir
|
||||
INSTALL_DIR=${input_install_dir:-$DEFAULT_INSTALL_DIR}
|
||||
|
||||
# Check if running as root
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo -e "${RED}This script must be run as root or with sudo privileges.${NC}"
|
||||
@@ -23,6 +28,7 @@ fi
|
||||
# Display welcome message
|
||||
echo -e "${GREEN}${BOLD}Transmission RSS Manager - Bootstrap Installer${NC}"
|
||||
echo -e "This script will install the latest version from the git repository."
|
||||
echo -e "The default installation directory is ${BOLD}/opt/trans-install${NC}, but you can choose a different location."
|
||||
echo
|
||||
|
||||
# Check for git installation
|
||||
@@ -62,7 +68,21 @@ fi
|
||||
echo -e "${YELLOW}Running the main installer...${NC}"
|
||||
cd "$INSTALL_DIR"
|
||||
chmod +x main-installer.sh
|
||||
./main-installer.sh
|
||||
|
||||
# Let the user know what's about to happen
|
||||
echo -e "${YELLOW}You will now be asked for configuration options including:${NC}"
|
||||
echo -e "- Installation directory"
|
||||
echo -e "- User to run the service as"
|
||||
echo -e "- Whether Transmission is running on a remote server"
|
||||
echo -e "- Transmission connection details"
|
||||
echo -e "- Media organization preferences"
|
||||
echo
|
||||
|
||||
# Add small delay to ensure user sees this message
|
||||
sleep 2
|
||||
|
||||
# Force the installer to run with the right environment
|
||||
env -i PATH="$PATH" TERM="$TERM" USER="$USER" HOME="$HOME" SUDO_USER="$SUDO_USER" ./main-installer.sh
|
||||
|
||||
# Installation complete
|
||||
echo -e "${GREEN}${BOLD}Bootstrap installation complete!${NC}"
|
||||
|
@@ -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
|
||||
@@ -1166,4 +1196,164 @@ fi
|
||||
|
||||
# Launch the main installer
|
||||
echo -e "${GREEN}Launching main installer...${NC}"
|
||||
exec "${SCRIPT_DIR}/main-installer.sh"
|
||||
|
||||
# Skip Transmission configuration if we're in update mode
|
||||
if [ "$IS_UPDATE" = "true" ] && [ -n "$EXISTING_CONFIG_PATH" ]; then
|
||||
echo -e "${GREEN}Existing configuration detected, skipping Transmission configuration...${NC}"
|
||||
|
||||
# Extract Transmission remote setting from existing config
|
||||
if [ -f "$EXISTING_CONFIG_PATH" ]; then
|
||||
# Try to extract remoteConfig.isRemote value from config.json
|
||||
if command -v grep &> /dev/null && command -v sed &> /dev/null; then
|
||||
IS_REMOTE=$(grep -o '"isRemote":[^,}]*' "$EXISTING_CONFIG_PATH" | sed 's/"isRemote"://; s/[[:space:]]//g')
|
||||
if [ "$IS_REMOTE" = "true" ]; then
|
||||
export TRANSMISSION_REMOTE=true
|
||||
echo -e "${GREEN}Using existing remote Transmission configuration.${NC}"
|
||||
else
|
||||
export TRANSMISSION_REMOTE=false
|
||||
echo -e "${GREEN}Using existing local Transmission configuration.${NC}"
|
||||
fi
|
||||
else
|
||||
# Default to false if we can't extract it
|
||||
export TRANSMISSION_REMOTE=false
|
||||
echo -e "${YELLOW}Could not determine Transmission remote setting, using local configuration.${NC}"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
# Ask about remote Transmission before launching main installer
|
||||
# This ensures the TRANSMISSION_REMOTE variable is set correctly
|
||||
echo -e "${BOLD}Transmission Configuration:${NC}"
|
||||
echo -e "Configure connection to your Transmission client:"
|
||||
echo
|
||||
|
||||
# If stdin is not a terminal (pipe or redirect), read from stdin
|
||||
if [ ! -t 0 ]; then
|
||||
# Save all input to a temporary file
|
||||
INPUT_FILE=$(mktemp)
|
||||
cat > "$INPUT_FILE"
|
||||
|
||||
# Read the first line as the remote selection
|
||||
input_remote=$(awk 'NR==1{print}' "$INPUT_FILE")
|
||||
echo "DEBUG: Non-interactive mode detected, read input: '$input_remote'"
|
||||
|
||||
# Keep the rest of the input for later use
|
||||
tail -n +2 "$INPUT_FILE" > "${INPUT_FILE}.rest"
|
||||
mv "${INPUT_FILE}.rest" "$INPUT_FILE"
|
||||
else
|
||||
read -p "Is Transmission running on a remote server? (y/n) [n]: " input_remote
|
||||
fi
|
||||
echo "DEBUG: Input received for remote in install-script.sh: '$input_remote'"
|
||||
# Explicitly check for "y" or "Y" response
|
||||
if [ "$input_remote" = "y" ] || [ "$input_remote" = "Y" ]; then
|
||||
export TRANSMISSION_REMOTE=true
|
||||
echo -e "${GREEN}Remote Transmission selected.${NC}"
|
||||
else
|
||||
export TRANSMISSION_REMOTE=false
|
||||
echo -e "${GREEN}Local Transmission selected.${NC}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# If remote mode is selected and not an update, collect remote details here and pass to main installer
|
||||
if [ "$TRANSMISSION_REMOTE" = "true" ] && [ "$IS_UPDATE" != "true" ]; then
|
||||
# Get remote transmission details
|
||||
if [ ! -t 0 ]; then
|
||||
# Non-interactive mode - we already have input saved to INPUT_FILE
|
||||
# from the previous step
|
||||
|
||||
# Read each line from the input file
|
||||
TRANSMISSION_HOST=$(awk 'NR==1{print}' "$INPUT_FILE")
|
||||
TRANSMISSION_PORT=$(awk 'NR==2{print}' "$INPUT_FILE")
|
||||
TRANSMISSION_USER=$(awk 'NR==3{print}' "$INPUT_FILE")
|
||||
TRANSMISSION_PASS=$(awk 'NR==4{print}' "$INPUT_FILE")
|
||||
TRANSMISSION_RPC_PATH=$(awk 'NR==5{print}' "$INPUT_FILE")
|
||||
REMOTE_DOWNLOAD_DIR=$(awk 'NR==6{print}' "$INPUT_FILE")
|
||||
LOCAL_DOWNLOAD_DIR=$(awk 'NR==7{print}' "$INPUT_FILE")
|
||||
|
||||
# Use defaults for empty values
|
||||
TRANSMISSION_HOST=${TRANSMISSION_HOST:-"localhost"}
|
||||
TRANSMISSION_PORT=${TRANSMISSION_PORT:-"9091"}
|
||||
TRANSMISSION_USER=${TRANSMISSION_USER:-""}
|
||||
TRANSMISSION_PASS=${TRANSMISSION_PASS:-""}
|
||||
TRANSMISSION_RPC_PATH=${TRANSMISSION_RPC_PATH:-"/transmission/rpc"}
|
||||
REMOTE_DOWNLOAD_DIR=${REMOTE_DOWNLOAD_DIR:-"/var/lib/transmission-daemon/downloads"}
|
||||
LOCAL_DOWNLOAD_DIR=${LOCAL_DOWNLOAD_DIR:-"/mnt/transmission-downloads"}
|
||||
|
||||
# Clean up
|
||||
rm -f "$INPUT_FILE"
|
||||
echo "DEBUG: Non-interactive mode with remote details:"
|
||||
echo "DEBUG: Host: $TRANSMISSION_HOST, Port: $TRANSMISSION_PORT"
|
||||
echo "DEBUG: Remote dir: $REMOTE_DOWNLOAD_DIR, Local dir: $LOCAL_DOWNLOAD_DIR"
|
||||
else
|
||||
# Interactive mode - ask for details
|
||||
read -p "Remote Transmission host [localhost]: " TRANSMISSION_HOST
|
||||
TRANSMISSION_HOST=${TRANSMISSION_HOST:-"localhost"}
|
||||
|
||||
read -p "Remote Transmission port [9091]: " TRANSMISSION_PORT
|
||||
TRANSMISSION_PORT=${TRANSMISSION_PORT:-"9091"}
|
||||
|
||||
read -p "Remote Transmission username []: " TRANSMISSION_USER
|
||||
TRANSMISSION_USER=${TRANSMISSION_USER:-""}
|
||||
|
||||
read -s -p "Remote Transmission password []: " TRANSMISSION_PASS
|
||||
echo # Add a newline after password input
|
||||
TRANSMISSION_PASS=${TRANSMISSION_PASS:-""}
|
||||
|
||||
read -p "Remote Transmission RPC path [/transmission/rpc]: " TRANSMISSION_RPC_PATH
|
||||
TRANSMISSION_RPC_PATH=${TRANSMISSION_RPC_PATH:-"/transmission/rpc"}
|
||||
|
||||
# Configure directory mapping for remote setup
|
||||
echo
|
||||
echo -e "${YELLOW}Directory Mapping Configuration${NC}"
|
||||
echo -e "When using a remote Transmission server, you need to map paths between servers."
|
||||
echo -e "For each directory on the remote server, specify the corresponding local directory."
|
||||
echo
|
||||
|
||||
read -p "Remote Transmission download directory [/var/lib/transmission-daemon/downloads]: " REMOTE_DOWNLOAD_DIR
|
||||
REMOTE_DOWNLOAD_DIR=${REMOTE_DOWNLOAD_DIR:-"/var/lib/transmission-daemon/downloads"}
|
||||
|
||||
read -p "Local directory that corresponds to the remote download directory [/mnt/transmission-downloads]: " LOCAL_DOWNLOAD_DIR
|
||||
LOCAL_DOWNLOAD_DIR=${LOCAL_DOWNLOAD_DIR:-"/mnt/transmission-downloads"}
|
||||
fi
|
||||
|
||||
# Create the environment file with all remote details
|
||||
cat > "${SCRIPT_DIR}/.env.install" << EOF
|
||||
export TRANSMISSION_REMOTE=$TRANSMISSION_REMOTE
|
||||
export TRANSMISSION_HOST="$TRANSMISSION_HOST"
|
||||
export TRANSMISSION_PORT="$TRANSMISSION_PORT"
|
||||
export TRANSMISSION_USER="$TRANSMISSION_USER"
|
||||
export TRANSMISSION_PASS="$TRANSMISSION_PASS"
|
||||
export TRANSMISSION_RPC_PATH="$TRANSMISSION_RPC_PATH"
|
||||
export REMOTE_DOWNLOAD_DIR="$REMOTE_DOWNLOAD_DIR"
|
||||
export LOCAL_DOWNLOAD_DIR="$LOCAL_DOWNLOAD_DIR"
|
||||
EOF
|
||||
else
|
||||
# Local mode - simpler environment file
|
||||
echo "export TRANSMISSION_REMOTE=$TRANSMISSION_REMOTE" > "${SCRIPT_DIR}/.env.install"
|
||||
fi
|
||||
|
||||
chmod +x "${SCRIPT_DIR}/.env.install"
|
||||
|
||||
# Ensure the environment file is world-readable to avoid permission issues
|
||||
chmod 644 "${SCRIPT_DIR}/.env.install"
|
||||
|
||||
# If we're in update mode, add the existing installation path to the environment file
|
||||
if [ "$IS_UPDATE" = "true" ] && [ -n "$EXISTING_CONFIG_PATH" ]; then
|
||||
echo "export EXISTING_CONFIG_PATH=\"$EXISTING_CONFIG_PATH\"" >> "${SCRIPT_DIR}/.env.install"
|
||||
echo "export EXISTING_INSTALL_DIR=\"$EXISTING_INSTALL_DIR\"" >> "${SCRIPT_DIR}/.env.install"
|
||||
echo "export IS_UPDATE=true" >> "${SCRIPT_DIR}/.env.install"
|
||||
fi
|
||||
|
||||
# Force inclusion in the main installer - modify the main installer temporarily if needed
|
||||
if ! grep -q "source.*\.env\.install" "${SCRIPT_DIR}/main-installer.sh"; then
|
||||
# Backup the main installer
|
||||
cp "${SCRIPT_DIR}/main-installer.sh" "${SCRIPT_DIR}/main-installer.sh.bak"
|
||||
|
||||
# Insert the source command after the shebang line
|
||||
awk 'NR==1{print; print "# Load installation environment variables"; print "if [ -f \"$(dirname \"$0\")/.env.install\" ]; then"; print " source \"$(dirname \"$0\")/.env.install\""; print " echo \"Loaded TRANSMISSION_REMOTE=$TRANSMISSION_REMOTE from environment file\""; print "fi"} NR!=1{print}' "${SCRIPT_DIR}/main-installer.sh.bak" > "${SCRIPT_DIR}/main-installer.sh"
|
||||
chmod +x "${SCRIPT_DIR}/main-installer.sh"
|
||||
fi
|
||||
|
||||
# Now execute the main installer with the environment variables set
|
||||
echo "Running main installer with TRANSMISSION_REMOTE=$TRANSMISSION_REMOTE"
|
||||
export TRANSMISSION_REMOTE
|
||||
"${SCRIPT_DIR}/main-installer.sh"
|
@@ -5,6 +5,12 @@
|
||||
# Set script to exit on error
|
||||
set -e
|
||||
|
||||
# Load installation environment variables if they exist
|
||||
if [ -f "$(dirname "$0")/.env.install" ]; then
|
||||
source "$(dirname "$0")/.env.install"
|
||||
echo "Loaded TRANSMISSION_REMOTE=$TRANSMISSION_REMOTE from environment file"
|
||||
fi
|
||||
|
||||
# Text formatting
|
||||
BOLD='\033[1m'
|
||||
GREEN='\033[0;32m'
|
||||
@@ -12,10 +18,20 @@ YELLOW='\033[0;33m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Get current directory
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
|
||||
|
||||
# Source the utils module first to make the log function available
|
||||
source "${SCRIPT_DIR}/modules/utils-module.sh"
|
||||
|
||||
# Print header
|
||||
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
|
||||
@@ -26,34 +42,78 @@ if [ "$EUID" -ne 0 ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get current directory
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
|
||||
|
||||
# Check for installation type
|
||||
IS_UPDATE=false
|
||||
INSTALLATION_DETECTED=false
|
||||
|
||||
# Check for config.json file (primary indicator)
|
||||
if [ -f "${SCRIPT_DIR}/config.json" ]; 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
|
||||
fi
|
||||
|
||||
# Check for service file (secondary indicator)
|
||||
if [ -f "/etc/systemd/system/transmission-rss-manager.service" ]; then
|
||||
INSTALLATION_DETECTED=true
|
||||
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
|
||||
# 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"
|
||||
)
|
||||
|
||||
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
|
||||
|
||||
# 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
|
||||
@@ -75,11 +135,17 @@ for module in "${REQUIRED_MODULES[@]}"; do
|
||||
fi
|
||||
done
|
||||
|
||||
# Source the module files
|
||||
source "${SCRIPT_DIR}/modules/utils-module.sh" # Load utilities first for logging
|
||||
# Source the remaining module files
|
||||
source "${SCRIPT_DIR}/modules/config-module.sh"
|
||||
source "${SCRIPT_DIR}/modules/dependencies-module.sh"
|
||||
source "${SCRIPT_DIR}/modules/service-setup-module.sh"
|
||||
# Check if the updated service module exists, use it if available
|
||||
if [ -f "${SCRIPT_DIR}/modules/service-setup-module-updated.sh" ]; then
|
||||
log "INFO" "Using updated service setup module"
|
||||
source "${SCRIPT_DIR}/modules/service-setup-module-updated.sh"
|
||||
else
|
||||
log "INFO" "Using standard service setup module"
|
||||
source "${SCRIPT_DIR}/modules/service-setup-module.sh"
|
||||
fi
|
||||
source "${SCRIPT_DIR}/modules/file-creator-module.sh"
|
||||
|
||||
# Function to handle cleanup on error
|
||||
@@ -99,11 +165,164 @@ trap 'cleanup_on_error "$BASH_COMMAND"' ERR
|
||||
# Execute the installation steps in sequence
|
||||
log "INFO" "Starting installation process..."
|
||||
|
||||
# Set defaults for key variables
|
||||
export TRANSMISSION_REMOTE=false
|
||||
export CONFIG_DIR=${CONFIG_DIR:-"/etc/transmission-rss-manager"}
|
||||
export USER=${USER:-$(logname || echo $SUDO_USER)}
|
||||
|
||||
if [ "$IS_UPDATE" = true ]; then
|
||||
log "INFO" "Running in update mode - preserving existing configuration..."
|
||||
|
||||
# When updating, we only need to update core files and dependencies
|
||||
# Configuration should be preserved
|
||||
# First, let's check if we already have this value from the environment
|
||||
# This allows for non-interactive usage in scripts
|
||||
if [ -n "$TRANSMISSION_REMOTE" ]; then
|
||||
is_remote=$([ "$TRANSMISSION_REMOTE" = true ] && echo "Remote" || echo "Local")
|
||||
log "INFO" "Using Transmission mode from environment: $is_remote"
|
||||
|
||||
# Set the input_remote variable based on the environment variable
|
||||
# This ensures consistent behavior with the rest of the script
|
||||
if [ "$TRANSMISSION_REMOTE" = true ]; then
|
||||
input_remote="y"
|
||||
else
|
||||
input_remote="n"
|
||||
fi
|
||||
else
|
||||
# Directly ask about Transmission
|
||||
# This is a direct approach that bypasses any potential sourcing issues
|
||||
log "INFO" "Configuring Transmission connection..."
|
||||
echo -e "${BOLD}Transmission Configuration:${NC}"
|
||||
echo -e "Configure connection to your Transmission client:"
|
||||
echo
|
||||
|
||||
# If stdin is not a terminal (pipe or redirect), assume default
|
||||
if [ ! -t 0 ]; then
|
||||
input_remote="n" # Default to no
|
||||
log "INFO" "Non-interactive mode detected, using default: local Transmission"
|
||||
else
|
||||
read -p "Is Transmission running on a remote server? (y/n) [n]: " input_remote
|
||||
fi
|
||||
log "INFO" "DEBUG: Input received for remote: '$input_remote'"
|
||||
fi
|
||||
|
||||
# More explicit check for "y" or "Y" input
|
||||
if [ "$input_remote" = "y" ] || [ "$input_remote" = "Y" ]; then
|
||||
export TRANSMISSION_REMOTE=true
|
||||
log "INFO" "Remote Transmission selected."
|
||||
|
||||
# Update the config file directly to set remote mode
|
||||
if [ -f "$CONFIG_DIR/config.json" ]; then
|
||||
log "INFO" "Updating configuration file for remote Transmission..."
|
||||
|
||||
# Log all environment variables we have for debugging
|
||||
log "INFO" "DEBUG: Environment variables for remote configuration:"
|
||||
log "INFO" "DEBUG: TRANSMISSION_HOST=${TRANSMISSION_HOST:-'not set'}"
|
||||
log "INFO" "DEBUG: TRANSMISSION_PORT=${TRANSMISSION_PORT:-'not set'}"
|
||||
log "INFO" "DEBUG: REMOTE_DOWNLOAD_DIR=${REMOTE_DOWNLOAD_DIR:-'not set'}"
|
||||
log "INFO" "DEBUG: LOCAL_DOWNLOAD_DIR=${LOCAL_DOWNLOAD_DIR:-'not set'}"
|
||||
|
||||
# Check if we already have the remote configuration details from the environment
|
||||
if [ -n "$TRANSMISSION_HOST" ] && [ -n "$TRANSMISSION_PORT" ] && [ -n "$REMOTE_DOWNLOAD_DIR" ] && [ -n "$LOCAL_DOWNLOAD_DIR" ]; then
|
||||
log "INFO" "Using remote Transmission configuration from environment"
|
||||
# Values are already set from the environment, no need to ask again
|
||||
else
|
||||
# Get and validate hostname
|
||||
read -p "Remote Transmission host [localhost]: " input_trans_host
|
||||
TRANSMISSION_HOST=${input_trans_host:-"localhost"}
|
||||
|
||||
# Get and validate port
|
||||
read -p "Remote Transmission port [9091]: " input_trans_port
|
||||
TRANSMISSION_PORT=${input_trans_port:-9091}
|
||||
|
||||
# Get credentials
|
||||
read -p "Remote Transmission username []: " input_trans_user
|
||||
TRANSMISSION_USER=${input_trans_user:-""}
|
||||
|
||||
# Use read -s for password to avoid showing it on screen
|
||||
read -s -p "Remote Transmission password []: " input_trans_pass
|
||||
echo # Add a newline after the password input
|
||||
TRANSMISSION_PASS=${input_trans_pass:-""}
|
||||
|
||||
read -p "Remote Transmission RPC path [/transmission/rpc]: " input_trans_path
|
||||
TRANSMISSION_RPC_PATH=${input_trans_path:-"/transmission/rpc"}
|
||||
|
||||
# Configure directory mapping for remote setup
|
||||
echo
|
||||
echo -e "${YELLOW}Directory Mapping Configuration${NC}"
|
||||
echo -e "When using a remote Transmission server, you need to map paths between servers."
|
||||
echo -e "For each directory on the remote server, specify the corresponding local directory."
|
||||
echo
|
||||
|
||||
# Get remote download directory
|
||||
read -p "Remote Transmission download directory [/var/lib/transmission-daemon/downloads]: " REMOTE_DOWNLOAD_DIR
|
||||
REMOTE_DOWNLOAD_DIR=${REMOTE_DOWNLOAD_DIR:-"/var/lib/transmission-daemon/downloads"}
|
||||
|
||||
# Get local directory that corresponds to remote download directory
|
||||
read -p "Local directory that corresponds to the remote download directory [/mnt/transmission-downloads]: " LOCAL_DOWNLOAD_DIR
|
||||
LOCAL_DOWNLOAD_DIR=${LOCAL_DOWNLOAD_DIR:-"/mnt/transmission-downloads"}
|
||||
fi
|
||||
|
||||
# Create mapping JSON
|
||||
TRANSMISSION_DIR_MAPPING=$(cat <<EOF
|
||||
{
|
||||
"$REMOTE_DOWNLOAD_DIR": "$LOCAL_DOWNLOAD_DIR"
|
||||
}
|
||||
EOF
|
||||
)
|
||||
|
||||
# Create the local directory
|
||||
mkdir -p "$LOCAL_DOWNLOAD_DIR"
|
||||
chown -R $USER:$USER "$LOCAL_DOWNLOAD_DIR"
|
||||
|
||||
# Update the config file with the new remote settings
|
||||
log "INFO" "Updating configuration file with remote Transmission settings..."
|
||||
|
||||
# Backup the original config file
|
||||
cp "$CONFIG_DIR/config.json" "$CONFIG_DIR/config.json.bak.$(date +%Y%m%d%H%M%S)"
|
||||
|
||||
# Update the isRemote setting
|
||||
sed -i 's/"isRemote": false/"isRemote": true/' "$CONFIG_DIR/config.json"
|
||||
|
||||
# Update the host setting
|
||||
sed -i "s/\"host\": \"[^\"]*\"/\"host\": \"$TRANSMISSION_HOST\"/" "$CONFIG_DIR/config.json"
|
||||
|
||||
# Update the port setting
|
||||
sed -i "s/\"port\": [0-9]*/\"port\": $TRANSMISSION_PORT/" "$CONFIG_DIR/config.json"
|
||||
|
||||
# Update the username setting
|
||||
sed -i "s/\"username\": \"[^\"]*\"/\"username\": \"$TRANSMISSION_USER\"/" "$CONFIG_DIR/config.json"
|
||||
|
||||
# Update the password setting
|
||||
sed -i "s/\"password\": \"[^\"]*\"/\"password\": \"$TRANSMISSION_PASS\"/" "$CONFIG_DIR/config.json"
|
||||
|
||||
# Update the RPC path setting
|
||||
sed -i "s|\"path\": \"[^\"]*\"|\"path\": \"$TRANSMISSION_RPC_PATH\"|" "$CONFIG_DIR/config.json"
|
||||
|
||||
# Update the directory mapping
|
||||
# Use a more complex approach since it's a JSON object
|
||||
# This is a simplification and might need improvement for complex JSON handling
|
||||
sed -i "/\"directoryMapping\":/c\\ \"directoryMapping\": $TRANSMISSION_DIR_MAPPING" "$CONFIG_DIR/config.json"
|
||||
|
||||
log "INFO" "Configuration updated for remote Transmission."
|
||||
fi
|
||||
else
|
||||
export TRANSMISSION_REMOTE=false
|
||||
log "INFO" "Local Transmission selected."
|
||||
|
||||
# Update the config file directly to set local mode
|
||||
if [ -f "$CONFIG_DIR/config.json" ]; then
|
||||
log "INFO" "Updating configuration file for local Transmission..."
|
||||
# Backup the original config file
|
||||
cp "$CONFIG_DIR/config.json" "$CONFIG_DIR/config.json.bak.$(date +%Y%m%d%H%M%S)"
|
||||
|
||||
# Update the isRemote setting
|
||||
sed -i 's/"isRemote": true/"isRemote": false/' "$CONFIG_DIR/config.json"
|
||||
|
||||
# Update the host setting
|
||||
sed -i 's/"host": "[^"]*"/"host": "localhost"/' "$CONFIG_DIR/config.json"
|
||||
|
||||
log "INFO" "Configuration updated for local Transmission."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Step 1: Check dependencies (but don't reconfigure)
|
||||
log "INFO" "Checking dependencies..."
|
||||
@@ -119,24 +338,71 @@ if [ "$IS_UPDATE" = true ]; then
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Install npm dependencies
|
||||
log "INFO" "Updating npm dependencies..."
|
||||
cd "$SCRIPT_DIR"
|
||||
npm install || {
|
||||
# Install npm dependencies using our common function
|
||||
ensure_npm_packages "$INSTALL_DIR" || {
|
||||
log "ERROR" "NPM installation failed"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Copy JavaScript module files during update as well
|
||||
log "INFO" "Copying JavaScript module files..."
|
||||
copy_module_files || {
|
||||
log "ERROR" "Failed to copy JavaScript module files"
|
||||
exit 1
|
||||
}
|
||||
|
||||
else
|
||||
# This is a fresh installation - run all steps
|
||||
|
||||
# Step 1: Gather configuration from user
|
||||
log "INFO" "Gathering configuration..."
|
||||
# Step 1: First, let's check if we already have this value from the environment
|
||||
# This allows for non-interactive usage in scripts
|
||||
if [ -n "$TRANSMISSION_REMOTE" ]; then
|
||||
is_remote=$([ "$TRANSMISSION_REMOTE" = true ] && echo "Remote" || echo "Local")
|
||||
log "INFO" "Using Transmission mode from environment: $is_remote"
|
||||
|
||||
# Set the input_remote variable based on the environment variable
|
||||
# This ensures consistent behavior with the rest of the script
|
||||
if [ "$TRANSMISSION_REMOTE" = true ]; then
|
||||
input_remote="y"
|
||||
else
|
||||
input_remote="n"
|
||||
fi
|
||||
else
|
||||
# Directly ask about Transmission
|
||||
# This is a direct approach that bypasses any potential sourcing issues
|
||||
log "INFO" "Configuring Transmission connection..."
|
||||
echo -e "${BOLD}Transmission Configuration:${NC}"
|
||||
echo -e "Configure connection to your Transmission client:"
|
||||
echo
|
||||
|
||||
# If stdin is not a terminal (pipe or redirect), assume default
|
||||
if [ ! -t 0 ]; then
|
||||
input_remote="n" # Default to no
|
||||
log "INFO" "Non-interactive mode detected, using default: local Transmission"
|
||||
else
|
||||
read -p "Is Transmission running on a remote server? (y/n) [n]: " input_remote
|
||||
fi
|
||||
log "INFO" "DEBUG: Input received for remote: '$input_remote'"
|
||||
fi
|
||||
# More explicit check for "y" or "Y" input
|
||||
if [ "$input_remote" = "y" ] || [ "$input_remote" = "Y" ]; then
|
||||
export TRANSMISSION_REMOTE=true
|
||||
log "INFO" "Remote Transmission selected."
|
||||
else
|
||||
export TRANSMISSION_REMOTE=false
|
||||
log "INFO" "Local Transmission selected."
|
||||
fi
|
||||
|
||||
# Now gather the rest of the configuration
|
||||
log "INFO" "Gathering remaining configuration..."
|
||||
gather_configuration || {
|
||||
log "ERROR" "Configuration gathering failed"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Debug: Verify TRANSMISSION_REMOTE is set
|
||||
log "INFO" "After configuration gathering, TRANSMISSION_REMOTE=$TRANSMISSION_REMOTE"
|
||||
|
||||
# Step 2: Install dependencies
|
||||
log "INFO" "Installing dependencies..."
|
||||
install_dependencies || {
|
||||
@@ -146,6 +412,9 @@ else
|
||||
|
||||
# Step 3: Create installation directories
|
||||
log "INFO" "Creating directories..."
|
||||
# Make sure CONFIG_DIR is set and exported
|
||||
export CONFIG_DIR=${CONFIG_DIR:-"/etc/transmission-rss-manager"}
|
||||
# Call our new create_directories function
|
||||
create_directories || {
|
||||
log "ERROR" "Directory creation failed"
|
||||
exit 1
|
||||
@@ -165,10 +434,8 @@ else
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Step 6: Install npm dependencies
|
||||
log "INFO" "Installing npm dependencies..."
|
||||
cd "$SCRIPT_DIR"
|
||||
npm install || {
|
||||
# Step 6: Install npm dependencies using our common function
|
||||
ensure_npm_packages "$INSTALL_DIR" || {
|
||||
log "ERROR" "NPM installation failed"
|
||||
exit 1
|
||||
}
|
||||
@@ -177,9 +444,15 @@ fi
|
||||
# Step 7: Set up update script
|
||||
log "INFO" "Setting up update script..."
|
||||
mkdir -p "${SCRIPT_DIR}/scripts"
|
||||
cp "${SCRIPT_DIR}/scripts/update.sh" "${SCRIPT_DIR}/scripts/update.sh" 2>/dev/null || {
|
||||
# If copy fails, it probably doesn't exist, so we'll create it
|
||||
cat > "${SCRIPT_DIR}/scripts/update.sh" << 'EOL'
|
||||
# Check if update script exists - don't copy it to itself
|
||||
if [ ! -f "${SCRIPT_DIR}/scripts/update.sh" ]; then
|
||||
# First, check if we have an update script to copy
|
||||
if [ -f "${SCRIPT_DIR}/update.sh" ]; then
|
||||
cp "${SCRIPT_DIR}/update.sh" "${SCRIPT_DIR}/scripts/update.sh"
|
||||
log "INFO" "Copied update script from root to scripts directory"
|
||||
else
|
||||
# Create the update script since it doesn't exist
|
||||
cat > "${SCRIPT_DIR}/scripts/update.sh" << 'EOL'
|
||||
#!/bin/bash
|
||||
|
||||
# Transmission RSS Manager - Update Script
|
||||
@@ -266,8 +539,10 @@ echo -e "Updated from version $CURRENT_VERSION to $NEW_VERSION"
|
||||
echo -e "Changes will take effect immediately."
|
||||
EOL
|
||||
|
||||
chmod +x "${SCRIPT_DIR}/scripts/update.sh"
|
||||
}
|
||||
chmod +x "${SCRIPT_DIR}/scripts/update.sh"
|
||||
log "INFO" "Created update script: ${SCRIPT_DIR}/scripts/update.sh"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Step 8: Final setup and permissions
|
||||
log "INFO" "Finalizing setup..."
|
||||
|
@@ -2,7 +2,8 @@
|
||||
# Configuration module for Transmission RSS Manager Installation
|
||||
|
||||
# Configuration variables with defaults
|
||||
INSTALL_DIR="/opt/transmission-rss-manager"
|
||||
INSTALL_DIR=${INSTALL_DIR:-"/opt/trans-install"}
|
||||
CONFIG_DIR="/etc/transmission-rss-manager"
|
||||
SERVICE_NAME="transmission-rss-manager"
|
||||
PORT=3000
|
||||
|
||||
@@ -74,6 +75,12 @@ validate_hostname() {
|
||||
|
||||
function gather_configuration() {
|
||||
log "INFO" "Starting configuration gathering"
|
||||
|
||||
# Initialize default values for Transmission mode
|
||||
export TRANSMISSION_REMOTE=false
|
||||
|
||||
# Set flag to indicate that configuration was gathered
|
||||
export CONFIG_GATHERED=true
|
||||
echo -e "${BOLD}Installation Configuration:${NC}"
|
||||
echo -e "Please provide the following configuration parameters:"
|
||||
echo
|
||||
@@ -109,11 +116,11 @@ function gather_configuration() {
|
||||
echo -e "Configure connection to your Transmission client:"
|
||||
echo
|
||||
|
||||
# Ask if Transmission is remote
|
||||
read -p "Is Transmission running on a remote server? (y/n) [n]: " input_remote
|
||||
if [[ $input_remote =~ ^[Yy]$ ]]; then
|
||||
TRANSMISSION_REMOTE=true
|
||||
# Don't ask about remote again - this is now handled in the main installer
|
||||
# Just log the current setting for clarity
|
||||
log "INFO" "Using previously selected Transmission mode: $([ "$TRANSMISSION_REMOTE" = true ] && echo "Remote" || echo "Local")"
|
||||
|
||||
if [ "$TRANSMISSION_REMOTE" = true ]; then
|
||||
# Get and validate hostname
|
||||
while true; do
|
||||
read -p "Remote Transmission host [localhost]: " input_trans_host
|
||||
@@ -225,7 +232,8 @@ function gather_configuration() {
|
||||
# Set Transmission download dir for configuration
|
||||
TRANSMISSION_DOWNLOAD_DIR=$REMOTE_DOWNLOAD_DIR
|
||||
else
|
||||
# Local Transmission selected
|
||||
# Local Transmission selected - this part was already set in main-installer.sh
|
||||
|
||||
echo -e "${YELLOW}You've selected to use a local Transmission installation.${NC}"
|
||||
|
||||
# Check if Transmission is already installed
|
||||
|
82
modules/dependencies-module.sh
Normal file → Executable file
82
modules/dependencies-module.sh
Normal file → Executable file
@@ -4,6 +4,66 @@
|
||||
function install_dependencies() {
|
||||
log "INFO" "Installing dependencies..."
|
||||
|
||||
# Make sure TRANSMISSION_REMOTE variable is set and honored
|
||||
# First check for environment variable that might have been directly set
|
||||
# Then check the .env.install file in various locations
|
||||
|
||||
# Try relative path first
|
||||
ENV_FILE="$(dirname "$(dirname "$0")")/.env.install"
|
||||
if [ -f "$ENV_FILE" ]; then
|
||||
source "$ENV_FILE"
|
||||
log "INFO" "Loaded transmission settings from environment file: TRANSMISSION_REMOTE=$TRANSMISSION_REMOTE"
|
||||
# Try absolute path as fallback
|
||||
elif [ -f "/opt/develop/transmission-rss-manager/.env.install" ]; then
|
||||
source "/opt/develop/transmission-rss-manager/.env.install"
|
||||
log "INFO" "Loaded transmission settings from absolute path: TRANSMISSION_REMOTE=$TRANSMISSION_REMOTE"
|
||||
fi
|
||||
|
||||
# If we're in update mode, try to load the remote status from existing config
|
||||
if [ "$IS_UPDATE" = "true" ] && [ -n "$EXISTING_CONFIG_PATH" ]; then
|
||||
log "INFO" "Update mode detected with config at $EXISTING_CONFIG_PATH, checking Transmission remote setting"
|
||||
if [ -f "$EXISTING_CONFIG_PATH" ]; then
|
||||
# Try to extract the isRemote setting from the config file
|
||||
if command -v grep &> /dev/null; then
|
||||
IS_REMOTE=$(grep -o '"isRemote":[^,}]*' "$EXISTING_CONFIG_PATH" | grep -o 'true\|false')
|
||||
if [ "$IS_REMOTE" = "true" ]; then
|
||||
export TRANSMISSION_REMOTE=true
|
||||
log "INFO" "Detected remote Transmission configuration from existing config"
|
||||
elif [ "$IS_REMOTE" = "false" ]; then
|
||||
export TRANSMISSION_REMOTE=false
|
||||
log "INFO" "Detected local Transmission configuration from existing config"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Always prompt if we didn't get TRANSMISSION_REMOTE from environment or previous steps
|
||||
if [ -z "$TRANSMISSION_REMOTE" ]; then
|
||||
log "WARN" "TRANSMISSION_REMOTE variable was not set, asking now..."
|
||||
|
||||
# Directly ask about remote Transmission
|
||||
echo -e "${BOLD}Transmission Configuration:${NC}"
|
||||
echo -e "Configure connection to your Transmission client:"
|
||||
echo
|
||||
|
||||
read -p "Is Transmission running on a remote server? (y/n) [n]: " input_remote
|
||||
if [[ $input_remote =~ ^[Yy]$ ]]; then
|
||||
export TRANSMISSION_REMOTE=true
|
||||
log "INFO" "Remote Transmission selected."
|
||||
else
|
||||
export TRANSMISSION_REMOTE=false
|
||||
log "INFO" "Local Transmission selected."
|
||||
fi
|
||||
|
||||
# Save this choice to environment file for other scripts
|
||||
echo "export TRANSMISSION_REMOTE=$TRANSMISSION_REMOTE" > "$(dirname "$(dirname "$0")")/.env.install"
|
||||
chmod +x "$(dirname "$(dirname "$0")")/.env.install"
|
||||
else
|
||||
log "INFO" "Using previously set TRANSMISSION_REMOTE=$TRANSMISSION_REMOTE"
|
||||
fi
|
||||
|
||||
log "INFO" "Proceeding with Transmission mode: $([ "$TRANSMISSION_REMOTE" = true ] && echo "Remote" || echo "Local")"
|
||||
|
||||
# Check for package manager
|
||||
if command -v apt-get &> /dev/null; then
|
||||
# Update package index
|
||||
@@ -35,10 +95,11 @@ function install_dependencies() {
|
||||
log "INFO" "Node.js is already installed."
|
||||
fi
|
||||
|
||||
# Check if we need to install Transmission (only if local transmission was selected)
|
||||
if [ "$TRANSMISSION_HOST" = "localhost" ] || [ "$TRANSMISSION_HOST" = "127.0.0.1" ]; then
|
||||
# Check if we need to install Transmission (only if local transmission was selected and not in update mode)
|
||||
if [ "$TRANSMISSION_REMOTE" = false ] && [ "$IS_UPDATE" != "true" ]; then
|
||||
if ! command_exists transmission-daemon; then
|
||||
log "INFO" "Local Transmission installation selected, but transmission-daemon is not installed."
|
||||
log "INFO" "You selected to use a local Transmission installation during configuration."
|
||||
read -p "Would you like to install Transmission now? (y/n): " install_transmission
|
||||
|
||||
if [[ "$install_transmission" =~ ^[Yy]$ ]]; then
|
||||
@@ -110,7 +171,7 @@ function install_dependencies() {
|
||||
local missing_deps=()
|
||||
|
||||
# Add transmission to dependencies check if local installation was selected
|
||||
if [ "$TRANSMISSION_HOST" = "localhost" ] || [ "$TRANSMISSION_HOST" = "127.0.0.1" ]; then
|
||||
if [ "$TRANSMISSION_REMOTE" = false ]; then
|
||||
dependencies+=("transmission-daemon")
|
||||
fi
|
||||
|
||||
@@ -163,6 +224,12 @@ function create_directories() {
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if CONFIG_DIR is defined
|
||||
if [ -z "$CONFIG_DIR" ]; then
|
||||
log "ERROR" "CONFIG_DIR is not defined"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create directories and check for errors
|
||||
DIRECTORIES=(
|
||||
"$INSTALL_DIR"
|
||||
@@ -171,6 +238,7 @@ function create_directories() {
|
||||
"$INSTALL_DIR/public/css"
|
||||
"$INSTALL_DIR/modules"
|
||||
"$INSTALL_DIR/data"
|
||||
"$CONFIG_DIR"
|
||||
)
|
||||
|
||||
for dir in "${DIRECTORIES[@]}"; do
|
||||
@@ -180,5 +248,13 @@ function create_directories() {
|
||||
fi
|
||||
done
|
||||
|
||||
# Set permissions for configuration directory
|
||||
chown -R "$USER:$USER" "$CONFIG_DIR"
|
||||
chmod 755 "$CONFIG_DIR"
|
||||
|
||||
# Create a symlink from the installation directory to the config directory
|
||||
# This ensures the application can find the config regardless of where it looks
|
||||
ln -sf "$CONFIG_DIR/config.json" "$INSTALL_DIR/config.json"
|
||||
|
||||
log "INFO" "Directories created successfully."
|
||||
}
|
241
modules/file-creator-module.sh
Normal file → Executable file
241
modules/file-creator-module.sh
Normal file → Executable file
@@ -1,9 +1,139 @@
|
||||
#!/bin/bash
|
||||
# File creator module for Transmission RSS Manager Installation
|
||||
|
||||
function create_directories() {
|
||||
echo -e "${YELLOW}Creating directories...${NC}"
|
||||
|
||||
# Create main installation directory
|
||||
mkdir -p "$INSTALL_DIR"
|
||||
log "INFO" "Created installation directory: $INSTALL_DIR"
|
||||
|
||||
# Create modules directory
|
||||
mkdir -p "$INSTALL_DIR/modules"
|
||||
log "INFO" "Created modules directory: $INSTALL_DIR/modules"
|
||||
|
||||
# Create data directory
|
||||
mkdir -p "$INSTALL_DIR/data"
|
||||
log "INFO" "Created data directory: $INSTALL_DIR/data"
|
||||
|
||||
# Create public directory structure
|
||||
mkdir -p "$INSTALL_DIR/public/css"
|
||||
mkdir -p "$INSTALL_DIR/public/js"
|
||||
log "INFO" "Created public directories"
|
||||
|
||||
# Create config directory
|
||||
mkdir -p "$CONFIG_DIR"
|
||||
log "INFO" "Created config directory: $CONFIG_DIR"
|
||||
|
||||
# Create logs directory
|
||||
mkdir -p "$INSTALL_DIR/logs"
|
||||
log "INFO" "Created logs directory: $INSTALL_DIR/logs"
|
||||
|
||||
# Set permissions
|
||||
chown -R "$USER:$USER" "$INSTALL_DIR"
|
||||
chmod -R 755 "$INSTALL_DIR"
|
||||
log "INFO" "Set permissions for installation directories"
|
||||
}
|
||||
|
||||
function create_config_files() {
|
||||
echo -e "${YELLOW}Creating configuration files...${NC}"
|
||||
|
||||
# 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
|
||||
VERSION=$(grep -oP '"version": "\K[^"]+' "${SCRIPT_DIR}/package.json" 2>/dev/null || echo "2.0.9")
|
||||
|
||||
cat > $CONFIG_DIR/config.json << EOF
|
||||
{
|
||||
"version": "$VERSION",
|
||||
"installPath": "$INSTALL_DIR",
|
||||
"transmissionConfig": {
|
||||
"host": "$TRANSMISSION_HOST",
|
||||
"port": $TRANSMISSION_PORT,
|
||||
"username": "$TRANSMISSION_USER",
|
||||
"password": "$TRANSMISSION_PASS",
|
||||
"path": "$TRANSMISSION_RPC_PATH"
|
||||
},
|
||||
"remoteConfig": {
|
||||
"isRemote": $TRANSMISSION_REMOTE,
|
||||
"directoryMapping": $TRANSMISSION_DIR_MAPPING
|
||||
},
|
||||
"destinationPaths": {
|
||||
"movies": "$MEDIA_DIR/movies",
|
||||
"tvShows": "$MEDIA_DIR/tvshows",
|
||||
"music": "$MEDIA_DIR/music",
|
||||
"books": "$MEDIA_DIR/books",
|
||||
"magazines": "$MEDIA_DIR/magazines",
|
||||
"software": "$MEDIA_DIR/software"
|
||||
},
|
||||
"seedingRequirements": {
|
||||
"minRatio": 1.0,
|
||||
"minTimeMinutes": 60,
|
||||
"checkIntervalSeconds": 300
|
||||
},
|
||||
"processingOptions": {
|
||||
"enableBookSorting": $ENABLE_BOOK_SORTING,
|
||||
"extractArchives": true,
|
||||
"deleteArchives": true,
|
||||
"createCategoryFolders": true,
|
||||
"ignoreSample": true,
|
||||
"ignoreExtras": true,
|
||||
"renameFiles": true,
|
||||
"autoReplaceUpgrades": true,
|
||||
"removeDuplicates": true,
|
||||
"keepOnlyBestVersion": true
|
||||
},
|
||||
"rssFeeds": [],
|
||||
"rssUpdateIntervalMinutes": 60,
|
||||
"autoProcessing": false,
|
||||
"securitySettings": {
|
||||
"authEnabled": false,
|
||||
"httpsEnabled": false,
|
||||
"sslCertPath": "",
|
||||
"sslKeyPath": "",
|
||||
"users": []
|
||||
},
|
||||
"port": $PORT,
|
||||
"logLevel": "info"
|
||||
}
|
||||
EOF
|
||||
|
||||
# Create package.json
|
||||
echo "Creating package.json..."
|
||||
cat > $INSTALL_DIR/package.json << EOF
|
||||
@@ -44,8 +174,9 @@ const cors = require('cors');
|
||||
const Transmission = require('transmission');
|
||||
|
||||
// Import custom modules
|
||||
const PostProcessor = require('./modules/postProcessor');
|
||||
const RssFeedManager = require('./modules/rssFeedManager');
|
||||
const PostProcessor = require('./modules/post-processor.js');
|
||||
const RssFeedManager = require('./modules/rss-feed-manager.js');
|
||||
const TransmissionClient = require('./modules/transmission-client.js');
|
||||
|
||||
// Initialize Express app
|
||||
const app = express();
|
||||
@@ -207,11 +338,21 @@ app.use(bodyParser.urlencoded({ extended: true, limit: '50mb' }));
|
||||
// API routes
|
||||
//==============================
|
||||
|
||||
// Get the version from package.json
|
||||
let appVersion;
|
||||
try {
|
||||
const packageJson = require('./package.json');
|
||||
appVersion = packageJson.version;
|
||||
} catch (err) {
|
||||
console.warn('Could not read version from package.json, using default');
|
||||
appVersion = '2.0.9'; // Default fallback version aligned with package.json
|
||||
}
|
||||
|
||||
// Server status API
|
||||
app.get('/api/status', (req, res) => {
|
||||
res.json({
|
||||
status: 'running',
|
||||
version: '1.2.0',
|
||||
version: appVersion,
|
||||
transmissionConnected: !!transmissionClient,
|
||||
postProcessorActive: postProcessor && postProcessor.processingIntervalId !== null,
|
||||
rssFeedManagerActive: rssFeedManager && rssFeedManager.updateIntervalId !== null,
|
||||
@@ -1704,4 +1845,98 @@ EOF
|
||||
}
|
||||
|
||||
echo "Configuration files created."
|
||||
|
||||
# Copy all JavaScript modules to the installation directory
|
||||
echo "Copying JavaScript module files..."
|
||||
copy_module_files
|
||||
}
|
||||
|
||||
# Function to copy all JavaScript module files to the installation directory
|
||||
function copy_module_files() {
|
||||
# Create the modules directory if it doesn't exist
|
||||
mkdir -p "$INSTALL_DIR/modules"
|
||||
|
||||
# Copy all JavaScript module files from the source directory
|
||||
for js_file in "${SCRIPT_DIR}/modules/"*.js; do
|
||||
if [ -f "$js_file" ]; then
|
||||
module_name=$(basename "$js_file")
|
||||
echo "Copying module: $module_name"
|
||||
cp "$js_file" "$INSTALL_DIR/modules/$module_name"
|
||||
|
||||
# Set permissions
|
||||
chown "$USER:$USER" "$INSTALL_DIR/modules/$module_name"
|
||||
chmod 644 "$INSTALL_DIR/modules/$module_name"
|
||||
fi
|
||||
done
|
||||
|
||||
# Copy main server files
|
||||
echo "Copying main server files..."
|
||||
|
||||
# server.js
|
||||
if [ -f "${SCRIPT_DIR}/server.js" ]; then
|
||||
cp "${SCRIPT_DIR}/server.js" "$INSTALL_DIR/"
|
||||
log "INFO" "Copied main server file: server.js"
|
||||
chown "$USER:$USER" "$INSTALL_DIR/server.js"
|
||||
chmod 644 "$INSTALL_DIR/server.js"
|
||||
else
|
||||
log "ERROR" "Main server file server.js not found in source directory"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# server-endpoints.js (if it exists)
|
||||
if [ -f "${SCRIPT_DIR}/server-endpoints.js" ]; then
|
||||
cp "${SCRIPT_DIR}/server-endpoints.js" "$INSTALL_DIR/"
|
||||
log "INFO" "Copied API endpoints file: server-endpoints.js"
|
||||
chown "$USER:$USER" "$INSTALL_DIR/server-endpoints.js"
|
||||
chmod 644 "$INSTALL_DIR/server-endpoints.js"
|
||||
fi
|
||||
|
||||
# Function to create bidirectional symlinks for module compatibility
|
||||
create_bidirectional_links() {
|
||||
local hyphenated="$1"
|
||||
local camelCase="$2"
|
||||
|
||||
# Check if hyphenated version exists
|
||||
if [ -f "$INSTALL_DIR/modules/$hyphenated.js" ]; then
|
||||
# Create camelCase symlink
|
||||
ln -sf "$hyphenated.js" "$INSTALL_DIR/modules/$camelCase.js"
|
||||
log "INFO" "Created symlink: $camelCase.js -> $hyphenated.js"
|
||||
|
||||
# Create symlinks without extension
|
||||
ln -sf "$hyphenated.js" "$INSTALL_DIR/modules/$hyphenated"
|
||||
ln -sf "$hyphenated.js" "$INSTALL_DIR/modules/$camelCase"
|
||||
log "INFO" "Created extension-less symlinks: $hyphenated, $camelCase -> $hyphenated.js"
|
||||
# Check if camelCase version exists
|
||||
elif [ -f "$INSTALL_DIR/modules/$camelCase.js" ]; then
|
||||
# Create hyphenated symlink
|
||||
ln -sf "$camelCase.js" "$INSTALL_DIR/modules/$hyphenated.js"
|
||||
log "INFO" "Created symlink: $hyphenated.js -> $camelCase.js"
|
||||
|
||||
# Create symlinks without extension
|
||||
ln -sf "$camelCase.js" "$INSTALL_DIR/modules/$hyphenated"
|
||||
ln -sf "$camelCase.js" "$INSTALL_DIR/modules/$camelCase"
|
||||
log "INFO" "Created extension-less symlinks: $hyphenated, $camelCase -> $camelCase.js"
|
||||
else
|
||||
log "WARN" "Neither $hyphenated.js nor $camelCase.js exists in $INSTALL_DIR/modules"
|
||||
fi
|
||||
|
||||
# Set permissions for all symlinks
|
||||
chmod 644 "$INSTALL_DIR/modules/$hyphenated.js" 2>/dev/null || true
|
||||
chmod 644 "$INSTALL_DIR/modules/$camelCase.js" 2>/dev/null || true
|
||||
chmod 644 "$INSTALL_DIR/modules/$hyphenated" 2>/dev/null || true
|
||||
chmod 644 "$INSTALL_DIR/modules/$camelCase" 2>/dev/null || true
|
||||
|
||||
# Set ownership for all symlinks
|
||||
chown "$USER:$USER" "$INSTALL_DIR/modules/$hyphenated.js" 2>/dev/null || true
|
||||
chown "$USER:$USER" "$INSTALL_DIR/modules/$camelCase.js" 2>/dev/null || true
|
||||
chown "$USER:$USER" "$INSTALL_DIR/modules/$hyphenated" 2>/dev/null || true
|
||||
chown "$USER:$USER" "$INSTALL_DIR/modules/$camelCase" 2>/dev/null || true
|
||||
}
|
||||
|
||||
# Create bidirectional symlinks for all modules
|
||||
create_bidirectional_links "rss-feed-manager" "rssFeedManager"
|
||||
create_bidirectional_links "transmission-client" "transmissionClient"
|
||||
create_bidirectional_links "post-processor" "postProcessor"
|
||||
|
||||
log "INFO" "Copied JavaScript modules and created compatibility symlinks in $INSTALL_DIR/modules/"
|
||||
}
|
1
modules/post-processor
Symbolic link
1
modules/post-processor
Symbolic link
@@ -0,0 +1 @@
|
||||
post-processor.js
|
1
modules/postProcessor
Symbolic link
1
modules/postProcessor
Symbolic link
@@ -0,0 +1 @@
|
||||
post-processor.js
|
1
modules/postProcessor.js
Symbolic link
1
modules/postProcessor.js
Symbolic link
@@ -0,0 +1 @@
|
||||
post-processor.js
|
1
modules/rss-feed-manager
Symbolic link
1
modules/rss-feed-manager
Symbolic link
@@ -0,0 +1 @@
|
||||
rss-feed-manager.js
|
@@ -18,8 +18,29 @@ class RssFeedManager {
|
||||
this.updateIntervalMinutes = config.updateIntervalMinutes || 60;
|
||||
this.parser = new xml2js.Parser({ explicitArray: false });
|
||||
|
||||
// Ensure dataPath is properly defined
|
||||
this.dataPath = path.join(__dirname, '..', 'data');
|
||||
// Set up the data path - first check if a data path was provided in the config
|
||||
if (config.dataPath) {
|
||||
this.dataPath = config.dataPath;
|
||||
} else {
|
||||
// Otherwise, use the default path relative to this module
|
||||
this.dataPath = path.join(__dirname, '..', 'data');
|
||||
|
||||
// Log the data path for debugging
|
||||
console.log(`Data directory path set to: ${this.dataPath}`);
|
||||
}
|
||||
|
||||
// We'll always ensure the data directory exists regardless of where it's set
|
||||
// Use synchronous operation to ensure directory exists immediately upon construction
|
||||
try {
|
||||
const fsSync = require('fs');
|
||||
if (!fsSync.existsSync(this.dataPath)) {
|
||||
fsSync.mkdirSync(this.dataPath, { recursive: true });
|
||||
console.log(`Created data directory synchronously: ${this.dataPath}`);
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(`Warning: Could not create data directory synchronously: ${err.message}`);
|
||||
// Will try again asynchronously in ensureDataDirectory when start() is called
|
||||
}
|
||||
|
||||
// Maximum items to keep in memory to prevent memory leaks
|
||||
this.maxItemsInMemory = config.maxItemsInMemory || 5000;
|
||||
@@ -31,6 +52,10 @@ class RssFeedManager {
|
||||
}
|
||||
|
||||
try {
|
||||
// Make sure the data directory exists first
|
||||
await this.ensureDataDirectory();
|
||||
console.log(`Using data directory: ${this.dataPath}`);
|
||||
|
||||
// Load existing feeds and items
|
||||
await this.loadFeeds();
|
||||
await this.loadItems();
|
||||
@@ -131,10 +156,13 @@ class RssFeedManager {
|
||||
console.log(`Updating feed: ${feed.name || 'Unnamed'} (${feed.url})`);
|
||||
|
||||
try {
|
||||
// Get version from package.json if available, fallback to environment or hardcoded
|
||||
const version = process.env.APP_VERSION || require('../package.json').version || '2.0.9';
|
||||
|
||||
const response = await fetch(feed.url, {
|
||||
timeout: 30000, // 30 second timeout
|
||||
headers: {
|
||||
'User-Agent': 'Transmission-RSS-Manager/1.2.0'
|
||||
'User-Agent': `Transmission-RSS-Manager/${version}`
|
||||
}
|
||||
});
|
||||
|
||||
@@ -480,12 +508,30 @@ class RssFeedManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures the data directory exists, using a consistent approach across the application
|
||||
* @returns {Promise<boolean>} true if directory exists or was created
|
||||
*/
|
||||
async ensureDataDirectory() {
|
||||
try {
|
||||
// Create data directory with recursive option (creates all parent directories if they don't exist)
|
||||
await fs.mkdir(this.dataPath, { recursive: true });
|
||||
console.log(`Ensured data directory exists at: ${this.dataPath}`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Error creating data directory:', error);
|
||||
throw error;
|
||||
// Log the error details for debugging
|
||||
console.error(`Error creating data directory ${this.dataPath}:`, error);
|
||||
|
||||
// Try an alternate approach if the first method fails
|
||||
try {
|
||||
const { execSync } = require('child_process');
|
||||
execSync(`mkdir -p "${this.dataPath}"`);
|
||||
console.log(`Created data directory using fallback method: ${this.dataPath}`);
|
||||
return true;
|
||||
} catch (fallbackError) {
|
||||
console.error('All methods for creating data directory failed:', fallbackError);
|
||||
throw new Error(`Failed to create data directory: ${this.dataPath}. Original error: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
1
modules/rssFeedManager
Symbolic link
1
modules/rssFeedManager
Symbolic link
@@ -0,0 +1 @@
|
||||
rss-feed-manager.js
|
1
modules/rssFeedManager.js
Symbolic link
1
modules/rssFeedManager.js
Symbolic link
@@ -0,0 +1 @@
|
||||
rss-feed-manager.js
|
324
modules/service-setup-module-updated.sh
Executable file
324
modules/service-setup-module-updated.sh
Executable file
@@ -0,0 +1,324 @@
|
||||
#!/bin/bash
|
||||
# Service setup module for Transmission RSS Manager Installation
|
||||
|
||||
# Setup systemd service
|
||||
function setup_service() {
|
||||
log "INFO" "Setting up systemd service..."
|
||||
|
||||
# Ensure required variables are set
|
||||
if [ -z "$SERVICE_NAME" ]; then
|
||||
log "ERROR" "SERVICE_NAME variable is not set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$USER" ]; then
|
||||
log "ERROR" "USER variable is not set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$INSTALL_DIR" ]; then
|
||||
log "ERROR" "INSTALL_DIR variable is not set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$CONFIG_DIR" ]; then
|
||||
log "ERROR" "CONFIG_DIR variable is not set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$PORT" ]; then
|
||||
log "ERROR" "PORT variable is not set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if systemd is available
|
||||
if ! command -v systemctl &> /dev/null; then
|
||||
log "ERROR" "systemd is not available on this system"
|
||||
log "INFO" "Please set up the service manually using your system's service manager"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Ensure the test-and-start script exists and is executable
|
||||
TEST_START_SCRIPT="$INSTALL_DIR/scripts/test-and-start.sh"
|
||||
mkdir -p "$(dirname "$TEST_START_SCRIPT")"
|
||||
cat > "$TEST_START_SCRIPT" << 'EOF'
|
||||
#!/bin/bash
|
||||
# Script to ensure data directory exists and start the application
|
||||
|
||||
# Define paths
|
||||
APP_DIR="$(dirname "$(dirname "$(readlink -f "$0")")")"
|
||||
DATA_DIR="$APP_DIR/data"
|
||||
|
||||
echo "Starting Transmission RSS Manager..."
|
||||
echo "Application directory: $APP_DIR"
|
||||
echo "Data directory: $DATA_DIR"
|
||||
|
||||
# Ensure the data directory exists
|
||||
if [ ! -d "$DATA_DIR" ]; then
|
||||
echo "Creating data directory: $DATA_DIR"
|
||||
mkdir -p "$DATA_DIR"
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Failed to create data directory. Trying alternative method..."
|
||||
# Try alternative method if standard mkdir fails
|
||||
cd "$APP_DIR" && mkdir -p data
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "ERROR: Both methods to create data directory failed. Please check permissions."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Set permissions
|
||||
chmod -R 755 "$DATA_DIR"
|
||||
|
||||
# Check for RSS files
|
||||
if [ ! -f "$DATA_DIR/rss-feeds.json" ]; then
|
||||
echo "Creating initial empty rss-feeds.json file"
|
||||
echo "[]" > "$DATA_DIR/rss-feeds.json"
|
||||
fi
|
||||
|
||||
if [ ! -f "$DATA_DIR/rss-items.json" ]; then
|
||||
echo "Creating initial empty rss-items.json file"
|
||||
echo "[]" > "$DATA_DIR/rss-items.json"
|
||||
fi
|
||||
|
||||
# Find the node executable path
|
||||
NODE_PATH=$(which node 2>/dev/null)
|
||||
if [ -z "$NODE_PATH" ]; then
|
||||
# If node is not in PATH, try common locations
|
||||
for path in /usr/bin/node /usr/local/bin/node /opt/node/bin/node /usr/lib/node; do
|
||||
if [ -x "$path" ]; then
|
||||
NODE_PATH="$path"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# If we still can't find node, use the default path
|
||||
if [ -z "$NODE_PATH" ]; then
|
||||
NODE_PATH="/usr/bin/node"
|
||||
echo "Warning: Node.js not found in PATH, using default path: $NODE_PATH"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Start the application
|
||||
cd "$APP_DIR" || { echo "Failed to change to application directory"; exit 1; }
|
||||
echo "Starting node.js application with: $NODE_PATH $APP_DIR/server.js"
|
||||
exec "$NODE_PATH" "$APP_DIR/server.js"
|
||||
EOF
|
||||
|
||||
chmod +x "$TEST_START_SCRIPT"
|
||||
log "INFO" "Created test-and-start script at $TEST_START_SCRIPT"
|
||||
|
||||
# Check if service file already exists
|
||||
SERVICE_FILE="/etc/systemd/system/$SERVICE_NAME.service"
|
||||
if [ -f "$SERVICE_FILE" ] && [ "$IS_UPDATE" = true ]; then
|
||||
log "INFO" "Service file already exists. Preserving existing service configuration."
|
||||
|
||||
# Extract existing JWT_SECRET if present to maintain session consistency
|
||||
EXISTING_JWT_SECRET=$(grep "Environment=JWT_SECRET=" "$SERVICE_FILE" | cut -d'=' -f3)
|
||||
|
||||
# Extract existing PORT if it differs from the configured one
|
||||
EXISTING_PORT=$(grep "Environment=PORT=" "$SERVICE_FILE" | cut -d'=' -f3)
|
||||
if [ -n "$EXISTING_PORT" ] && [ "$EXISTING_PORT" != "$PORT" ]; then
|
||||
log "INFO" "Using existing port configuration: $EXISTING_PORT"
|
||||
PORT=$EXISTING_PORT
|
||||
fi
|
||||
|
||||
# Create backup of existing service file
|
||||
backup_file "$SERVICE_FILE"
|
||||
|
||||
# Update the service file while preserving key settings
|
||||
cat > "$SERVICE_FILE" << EOF
|
||||
[Unit]
|
||||
Description=Transmission RSS Manager
|
||||
After=network.target transmission-daemon.service
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=$USER
|
||||
WorkingDirectory=$INSTALL_DIR
|
||||
ExecStart=$TEST_START_SCRIPT
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
Environment=PORT=$PORT
|
||||
Environment=NODE_ENV=production
|
||||
Environment=DEBUG_ENABLED=false
|
||||
Environment=LOG_FILE=$INSTALL_DIR/logs/transmission-rss-manager.log
|
||||
Environment=CONFIG_DIR=$CONFIG_DIR
|
||||
EOF
|
||||
|
||||
# Preserve the existing JWT_SECRET if available
|
||||
if [ -n "$EXISTING_JWT_SECRET" ]; then
|
||||
echo "Environment=JWT_SECRET=$EXISTING_JWT_SECRET" >> "$SERVICE_FILE"
|
||||
else
|
||||
echo "# Generate a random JWT secret for security" >> "$SERVICE_FILE"
|
||||
echo "Environment=JWT_SECRET=$(openssl rand -hex 32)" >> "$SERVICE_FILE"
|
||||
fi
|
||||
|
||||
# Close the service file definition
|
||||
cat >> "$SERVICE_FILE" << EOF
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
else
|
||||
# For fresh installations, create a new service file
|
||||
log "INFO" "Creating new service file"
|
||||
|
||||
# Create backup of existing service file if it exists
|
||||
if [ -f "$SERVICE_FILE" ]; then
|
||||
backup_file "$SERVICE_FILE"
|
||||
fi
|
||||
|
||||
# Create systemd service file
|
||||
cat > "$SERVICE_FILE" << EOF
|
||||
[Unit]
|
||||
Description=Transmission RSS Manager
|
||||
After=network.target transmission-daemon.service
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
User=$USER
|
||||
WorkingDirectory=$INSTALL_DIR
|
||||
ExecStart=$TEST_START_SCRIPT
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
StandardOutput=journal
|
||||
StandardError=journal
|
||||
Environment=PORT=$PORT
|
||||
Environment=NODE_ENV=production
|
||||
Environment=DEBUG_ENABLED=false
|
||||
Environment=LOG_FILE=$INSTALL_DIR/logs/transmission-rss-manager.log
|
||||
Environment=CONFIG_DIR=$CONFIG_DIR
|
||||
# Generate a random JWT secret for security
|
||||
Environment=JWT_SECRET=$(openssl rand -hex 32)
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
fi
|
||||
|
||||
# Create logs directory
|
||||
mkdir -p "$INSTALL_DIR/logs"
|
||||
chown -R $USER:$USER "$INSTALL_DIR/logs"
|
||||
|
||||
# Check if file was created successfully
|
||||
if [ ! -f "$SERVICE_FILE" ]; then
|
||||
log "ERROR" "Failed to create systemd service file"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log "INFO" "Setting up Nginx reverse proxy..."
|
||||
|
||||
# Check if nginx is installed
|
||||
if ! command -v nginx &> /dev/null; then
|
||||
log "ERROR" "Nginx is not installed"
|
||||
log "INFO" "Skipping Nginx configuration. Please configure your web server manually."
|
||||
|
||||
# Reload systemd and enable service
|
||||
systemctl daemon-reload
|
||||
systemctl enable "$SERVICE_NAME"
|
||||
|
||||
log "INFO" "Systemd service has been created and enabled."
|
||||
log "INFO" "The service will start automatically after installation."
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Detect nginx configuration directory
|
||||
NGINX_AVAILABLE_DIR=""
|
||||
NGINX_ENABLED_DIR=""
|
||||
|
||||
if [ -d "/etc/nginx/sites-available" ] && [ -d "/etc/nginx/sites-enabled" ]; then
|
||||
# Debian/Ubuntu style
|
||||
NGINX_AVAILABLE_DIR="/etc/nginx/sites-available"
|
||||
NGINX_ENABLED_DIR="/etc/nginx/sites-enabled"
|
||||
elif [ -d "/etc/nginx/conf.d" ]; then
|
||||
# CentOS/RHEL style
|
||||
NGINX_AVAILABLE_DIR="/etc/nginx/conf.d"
|
||||
NGINX_ENABLED_DIR="/etc/nginx/conf.d"
|
||||
else
|
||||
log "WARN" "Unable to determine Nginx configuration directory"
|
||||
log "INFO" "Please configure Nginx manually"
|
||||
|
||||
# Reload systemd and enable service
|
||||
systemctl daemon-reload
|
||||
systemctl enable "$SERVICE_NAME"
|
||||
|
||||
log "INFO" "Systemd service has been created and enabled."
|
||||
log "INFO" "The service will start automatically after installation."
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Check if default nginx file exists, back it up if it does
|
||||
if [ -f "$NGINX_ENABLED_DIR/default" ]; then
|
||||
backup_file "$NGINX_ENABLED_DIR/default"
|
||||
if [ -f "$NGINX_ENABLED_DIR/default.bak" ]; then
|
||||
log "INFO" "Backed up default nginx configuration."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Create nginx configuration file
|
||||
NGINX_CONFIG_FILE="$NGINX_AVAILABLE_DIR/$SERVICE_NAME.conf"
|
||||
cat > "$NGINX_CONFIG_FILE" << EOF
|
||||
server {
|
||||
listen 80;
|
||||
server_name _;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:$PORT;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade \$http_upgrade;
|
||||
proxy_set_header Connection 'upgrade';
|
||||
proxy_set_header Host \$host;
|
||||
proxy_cache_bypass \$http_upgrade;
|
||||
proxy_set_header X-Real-IP \$remote_addr;
|
||||
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto \$scheme;
|
||||
}
|
||||
}
|
||||
EOF
|
||||
|
||||
log "INFO" "Nginx configured to proxy connections from port 80 to port $PORT"
|
||||
log "INFO" "You can access Transmission RSS Manager at http://your-server-ip/ (port 80) via Nginx"
|
||||
|
||||
# Check if Debian/Ubuntu style (need symlink between available and enabled)
|
||||
if [ "$NGINX_AVAILABLE_DIR" != "$NGINX_ENABLED_DIR" ]; then
|
||||
# Create symbolic link to enable the site (if it doesn't already exist)
|
||||
if [ ! -h "$NGINX_ENABLED_DIR/$SERVICE_NAME.conf" ]; then
|
||||
ln -sf "$NGINX_CONFIG_FILE" "$NGINX_ENABLED_DIR/"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Test nginx configuration
|
||||
if nginx -t; then
|
||||
# Reload nginx
|
||||
systemctl reload nginx
|
||||
log "INFO" "Nginx configuration has been set up successfully."
|
||||
else
|
||||
log "ERROR" "Nginx configuration test failed. Please check the configuration manually."
|
||||
log "WARN" "You may need to correct the configuration before the web interface will be accessible."
|
||||
fi
|
||||
|
||||
# Check for port conflicts
|
||||
if ss -lnt | grep ":$PORT " &> /dev/null; then
|
||||
log "WARN" "Port $PORT is already in use. This may cause conflicts with the service."
|
||||
log "WARN" "The service will fail to start. Please stop any service using port $PORT and try again."
|
||||
else
|
||||
log "INFO" "You can access the web interface at: http://localhost:$PORT or http://your-server-ip:$PORT"
|
||||
log "INFO" "You may need to configure your firewall to allow access to port $PORT"
|
||||
fi
|
||||
|
||||
# Reload systemd
|
||||
systemctl daemon-reload
|
||||
|
||||
# Enable the service to start on boot
|
||||
systemctl enable "$SERVICE_NAME"
|
||||
|
||||
log "INFO" "Systemd service has been created and enabled."
|
||||
log "INFO" "The service will start automatically after installation."
|
||||
}
|
@@ -21,6 +21,11 @@ function setup_service() {
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$CONFIG_DIR" ]; then
|
||||
log "ERROR" "CONFIG_DIR variable is not set"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$PORT" ]; then
|
||||
log "ERROR" "PORT variable is not set"
|
||||
exit 1
|
||||
@@ -71,6 +76,7 @@ Environment=PORT=$PORT
|
||||
Environment=NODE_ENV=production
|
||||
Environment=DEBUG_ENABLED=false
|
||||
Environment=LOG_FILE=$INSTALL_DIR/logs/transmission-rss-manager.log
|
||||
Environment=CONFIG_DIR=$CONFIG_DIR
|
||||
EOF
|
||||
|
||||
# Preserve the existing JWT_SECRET if available
|
||||
@@ -117,6 +123,7 @@ Environment=PORT=$PORT
|
||||
Environment=NODE_ENV=production
|
||||
Environment=DEBUG_ENABLED=false
|
||||
Environment=LOG_FILE=$INSTALL_DIR/logs/transmission-rss-manager.log
|
||||
Environment=CONFIG_DIR=$CONFIG_DIR
|
||||
# Generate a random JWT secret for security
|
||||
Environment=JWT_SECRET=$(openssl rand -hex 32)
|
||||
|
||||
|
1
modules/transmission-client
Symbolic link
1
modules/transmission-client
Symbolic link
@@ -0,0 +1 @@
|
||||
transmission-client.js
|
@@ -28,8 +28,14 @@ class TransmissionClient {
|
||||
this.dirMappings = config.remoteConfig.directoryMapping;
|
||||
}
|
||||
|
||||
// Initialize the connection
|
||||
this.initializeConnection();
|
||||
// Initialize the connection - but don't throw if it fails initially
|
||||
// This allows the object to be created even if the connection fails
|
||||
try {
|
||||
this.initializeConnection();
|
||||
} catch (error) {
|
||||
console.error("Failed to initialize Transmission connection:", error.message);
|
||||
// Don't throw - allow methods to handle connection retry logic
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -39,8 +45,11 @@ class TransmissionClient {
|
||||
const { host, port, username, password, path: rpcPath } = this.config.transmissionConfig;
|
||||
|
||||
try {
|
||||
// Only default to localhost if host is empty/null/undefined
|
||||
const connectionHost = (host === undefined || host === null || host === '') ? 'localhost' : host;
|
||||
|
||||
this.client = new Transmission({
|
||||
host: host || 'localhost',
|
||||
host: connectionHost,
|
||||
port: port || 9091,
|
||||
username: username || '',
|
||||
password: password || '',
|
||||
@@ -48,7 +57,7 @@ class TransmissionClient {
|
||||
timeout: 30000 // 30 seconds
|
||||
});
|
||||
|
||||
console.log(`Initialized Transmission client connection to ${host}:${port}${rpcPath}`);
|
||||
console.log(`Initialized Transmission client connection to ${connectionHost}:${port}${rpcPath}`);
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize Transmission client:', error);
|
||||
throw error;
|
||||
@@ -61,13 +70,17 @@ class TransmissionClient {
|
||||
*/
|
||||
async getStatus() {
|
||||
try {
|
||||
// Use the session-stats method for basic connectivity check
|
||||
const sessionInfo = await this.client.sessionStats();
|
||||
const version = await this.client.sessionGet();
|
||||
|
||||
// Use the session-get method to get version info
|
||||
// Note: In transmission-promise, this is 'session' not 'sessionGet'
|
||||
const session = await this.client.session();
|
||||
|
||||
return {
|
||||
connected: true,
|
||||
version: version.version,
|
||||
rpcVersion: version['rpc-version'],
|
||||
version: session.version || "Unknown",
|
||||
rpcVersion: session['rpc-version'] || "Unknown",
|
||||
downloadSpeed: sessionInfo.downloadSpeed,
|
||||
uploadSpeed: sessionInfo.uploadSpeed,
|
||||
torrentCount: sessionInfo.torrentCount,
|
||||
@@ -112,6 +125,15 @@ class TransmissionClient {
|
||||
*/
|
||||
async addTorrent(url, options = {}) {
|
||||
try {
|
||||
// Verify client is initialized
|
||||
if (!this.client) {
|
||||
await this.initializeConnection();
|
||||
|
||||
if (!this.client) {
|
||||
throw new Error("Failed to initialize Transmission client");
|
||||
}
|
||||
}
|
||||
|
||||
const downloadDir = options.downloadDir || null;
|
||||
const result = await this.client.addUrl(url, {
|
||||
"download-dir": downloadDir,
|
||||
@@ -443,7 +465,8 @@ class TransmissionClient {
|
||||
*/
|
||||
async setSessionParams(params) {
|
||||
try {
|
||||
await this.client.sessionSet(params);
|
||||
// In transmission-promise, the method is sessionUpdate not sessionSet
|
||||
await this.client.sessionUpdate(params);
|
||||
return {
|
||||
success: true,
|
||||
message: 'Session parameters updated successfully'
|
||||
|
1
modules/transmissionClient
Symbolic link
1
modules/transmissionClient
Symbolic link
@@ -0,0 +1 @@
|
||||
transmission-client.js
|
1
modules/transmissionClient.js
Symbolic link
1
modules/transmissionClient.js
Symbolic link
@@ -0,0 +1 @@
|
||||
transmission-client.js
|
@@ -97,6 +97,76 @@ function create_dir_if_not_exists() {
|
||||
}
|
||||
|
||||
# Function to finalize the setup (permissions, etc.)
|
||||
# Function to ensure NPM packages are properly installed
|
||||
function ensure_npm_packages() {
|
||||
local install_dir=$1
|
||||
|
||||
# First ensure the installation directory exists
|
||||
if [ ! -d "$install_dir" ]; then
|
||||
log "INFO" "Creating installation directory: $install_dir"
|
||||
mkdir -p "$install_dir"
|
||||
if [ $? -ne 0 ]; then
|
||||
log "ERROR" "Failed to create installation directory: $install_dir"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Ensure data directory exists
|
||||
if [ ! -d "$install_dir/data" ]; then
|
||||
log "INFO" "Creating data directory: $install_dir/data"
|
||||
mkdir -p "$install_dir/data"
|
||||
if [ $? -ne 0 ]; then
|
||||
log "ERROR" "Failed to create data directory: $install_dir/data"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Initialize empty data files
|
||||
echo "[]" > "$install_dir/data/rss-feeds.json"
|
||||
echo "[]" > "$install_dir/data/rss-items.json"
|
||||
log "INFO" "Initialized empty data files"
|
||||
fi
|
||||
|
||||
# Ensure package.json exists in the installation directory
|
||||
if [ ! -f "$install_dir/package.json" ]; then
|
||||
log "INFO" "Copying package.json to installation directory..."
|
||||
cp "$SCRIPT_DIR/package.json" "$install_dir/package.json"
|
||||
if [ $? -ne 0 ]; then
|
||||
log "ERROR" "Failed to copy package.json to installation directory"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Install NPM packages if not already installed or if it's an update
|
||||
if [ ! -d "$install_dir/node_modules" ] || [ "$IS_UPDATE" = "true" ]; then
|
||||
log "INFO" "Installing NPM packages in $install_dir..."
|
||||
|
||||
# Save current directory
|
||||
local current_dir=$(pwd)
|
||||
|
||||
# Change to install directory and install packages
|
||||
cd "$install_dir"
|
||||
if [ $? -ne 0 ]; then
|
||||
log "ERROR" "Failed to change to installation directory: $install_dir"
|
||||
return 1
|
||||
fi
|
||||
|
||||
npm install
|
||||
if [ $? -ne 0 ]; then
|
||||
log "ERROR" "NPM installation failed in $install_dir"
|
||||
cd "$current_dir" # Return to original directory
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Return to original directory
|
||||
cd "$current_dir"
|
||||
log "INFO" "NPM packages successfully installed in $install_dir"
|
||||
else
|
||||
log "INFO" "NPM packages appear to be already installed in $install_dir, skipping"
|
||||
fi
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
function finalize_setup() {
|
||||
log "INFO" "Setting up final permissions and configurations..."
|
||||
|
||||
@@ -104,6 +174,26 @@ function finalize_setup() {
|
||||
mkdir -p "$INSTALL_DIR/logs"
|
||||
log "INFO" "Created logs directory: $INSTALL_DIR/logs"
|
||||
|
||||
# Ensure CONFIG_DIR exists
|
||||
if [ ! -d "$CONFIG_DIR" ]; then
|
||||
mkdir -p "$CONFIG_DIR"
|
||||
log "INFO" "Created configuration directory: $CONFIG_DIR"
|
||||
chown -R "$USER:$USER" "$CONFIG_DIR"
|
||||
fi
|
||||
|
||||
# Check if the config symlink exists, create it if not
|
||||
if [ ! -L "$INSTALL_DIR/config.json" ] || [ ! -e "$INSTALL_DIR/config.json" ]; then
|
||||
# If there's a real file at INSTALL_DIR/config.json (not a symlink), move it to CONFIG_DIR
|
||||
if [ -f "$INSTALL_DIR/config.json" ] && [ ! -L "$INSTALL_DIR/config.json" ]; then
|
||||
log "INFO" "Moving existing config.json to $CONFIG_DIR"
|
||||
mv "$INSTALL_DIR/config.json" "$CONFIG_DIR/config.json"
|
||||
fi
|
||||
|
||||
# Create the symlink
|
||||
ln -sf "$CONFIG_DIR/config.json" "$INSTALL_DIR/config.json"
|
||||
log "INFO" "Created symlink from $INSTALL_DIR/config.json to $CONFIG_DIR/config.json"
|
||||
fi
|
||||
|
||||
# Set proper ownership for the installation directory
|
||||
chown -R $USER:$USER $INSTALL_DIR
|
||||
|
||||
@@ -119,12 +209,13 @@ function finalize_setup() {
|
||||
create_dir_if_not_exists "$MEDIA_DIR/magazines" "$USER:$USER"
|
||||
fi
|
||||
|
||||
# Install NPM packages
|
||||
log "INFO" "Installing NPM packages..."
|
||||
cd $INSTALL_DIR && npm install
|
||||
# Install npm packages
|
||||
ensure_npm_packages "$INSTALL_DIR" || {
|
||||
log "ERROR" "Failed to install NPM packages"
|
||||
}
|
||||
|
||||
# Handle configuration file
|
||||
if ! update_config_file "$INSTALL_DIR/config.json" "$IS_UPDATE"; then
|
||||
if ! update_config_file "$CONFIG_DIR/config.json" "$IS_UPDATE"; then
|
||||
log "INFO" "Creating default configuration file..."
|
||||
|
||||
# Create the users array content for JSON
|
||||
@@ -133,9 +224,15 @@ function finalize_setup() {
|
||||
USER_JSON="{ \"username\": \"${ADMIN_USERNAME}\", \"password\": \"${ADMIN_PASSWORD}\", \"role\": \"admin\" }"
|
||||
fi
|
||||
|
||||
cat > $INSTALL_DIR/config.json << EOF
|
||||
# Make sure CONFIG_DIR exists
|
||||
mkdir -p "$CONFIG_DIR"
|
||||
|
||||
# Get version from package.json dynamically
|
||||
VERSION=$(grep -oP '"version": "\K[^"]+' "${SCRIPT_DIR}/package.json" 2>/dev/null || echo "2.0.9")
|
||||
|
||||
cat > $CONFIG_DIR/config.json << EOF
|
||||
{
|
||||
"version": "1.2.0",
|
||||
"version": "$VERSION",
|
||||
"transmissionConfig": {
|
||||
"host": "${TRANSMISSION_HOST}",
|
||||
"port": ${TRANSMISSION_PORT},
|
||||
@@ -188,7 +285,12 @@ function finalize_setup() {
|
||||
"logLevel": "info"
|
||||
}
|
||||
EOF
|
||||
chown $USER:$USER $INSTALL_DIR/config.json
|
||||
# Set ownership for the config file
|
||||
chown $USER:$USER $CONFIG_DIR/config.json
|
||||
|
||||
# Ensure symlink exists from INSTALL_DIR to CONFIG_DIR
|
||||
ln -sf "$CONFIG_DIR/config.json" "$INSTALL_DIR/config.json"
|
||||
log "INFO" "Created symlink from $INSTALL_DIR/config.json to $CONFIG_DIR/config.json"
|
||||
log "INFO" "Default configuration created successfully"
|
||||
fi
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "transmission-rss-manager",
|
||||
"version": "2.0.1",
|
||||
"version": "2.0.12",
|
||||
"description": "A comprehensive web-based tool to automate and manage your Transmission torrent downloads with RSS feed integration and intelligent media organization",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
|
@@ -137,8 +137,8 @@
|
||||
<div class="mt-2 testing-controls">
|
||||
<small><a href="#" id="toggle-test-update-button">Toggle Test Update</a></small>
|
||||
</div>
|
||||
<div id="update-available" class="mt-3 d-none">
|
||||
<div class="alert alert-info">
|
||||
<div id="update-available" class="mt-3">
|
||||
<div class="alert alert-info update-alert" style="display: none;">
|
||||
<i class="fas fa-arrow-circle-up"></i>
|
||||
<span>A new version is available!</span>
|
||||
<button id="btn-update-now" class="btn btn-sm btn-primary ml-2">
|
||||
@@ -545,7 +545,7 @@
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<p>Transmission RSS Manager v2.0.0</p>
|
||||
<p>Transmission RSS Manager <span id="footer-version">v2.0.10</span></p>
|
||||
</div>
|
||||
<div class="col-md-6 text-right">
|
||||
<p><a href="https://git.powerdata.dk/masterdraco/transmission-rss-manager" target="_blank" rel="noopener noreferrer">GitHub</a> | <a href="#" id="show-about-modal">About</a></p>
|
||||
@@ -592,6 +592,49 @@
|
||||
|
||||
<h4>Version History</h4>
|
||||
<div class="version-history">
|
||||
<div class="version">
|
||||
<h5>v2.0.11 - March 2025</h5>
|
||||
<ul>
|
||||
<li><strong>Fixed</strong>: Update button persistence with floating notification</li>
|
||||
<li><strong>Fixed</strong>: Version display issues with direct package.json reading</li>
|
||||
<li><strong>Fixed</strong>: Update process for better version reporting</li>
|
||||
<li><strong>Fixed</strong>: Conflict between test mode and actual update status</li>
|
||||
<li><strong>Added</strong>: Refresh button on update notification</li>
|
||||
<li><strong>Added</strong>: Clear test mode indicators to prevent confusion</li>
|
||||
<li><strong>Improved</strong>: Enhanced cache busting for version checking</li>
|
||||
<li><strong>Improved</strong>: Better user feedback during update process</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="version">
|
||||
<h5>v2.0.10 - March 2025</h5>
|
||||
<ul>
|
||||
<li><strong>Fixed</strong>: fs.existsSync error in update check</li>
|
||||
<li><strong>Fixed</strong>: Update button now stays visible when update is available</li>
|
||||
<li><strong>Fixed</strong>: Footer version now shows correct running version</li>
|
||||
<li><strong>Improved</strong>: Better error handling for git repository checks</li>
|
||||
<li><strong>Improved</strong>: More robust file system operations for update detection</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="version">
|
||||
<h5>v2.0.9 - March 2025</h5>
|
||||
<ul>
|
||||
<li><strong>Fixed</strong>: Update button now appears properly on dashboard</li>
|
||||
<li><strong>Fixed</strong>: Remote Transmission connection issues resolved</li>
|
||||
<li><strong>Fixed</strong>: Improved connection test with better error handling</li>
|
||||
<li><strong>Added</strong>: System status and update endpoints for version checking</li>
|
||||
<li><strong>Improved</strong>: Update detection and notification on dashboard</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="version">
|
||||
<h5>v2.0.6 - March 2025</h5>
|
||||
<ul>
|
||||
<li><strong>Fixed</strong>: Remote Transmission configuration storage and application</li>
|
||||
<li><strong>Fixed</strong>: Config directory issues in /etc/transmission-rss-manager</li>
|
||||
<li><strong>Fixed</strong>: Input handling for automated/piped installations</li>
|
||||
<li><strong>Improved</strong>: Non-interactive mode for scripted installations</li>
|
||||
<li><strong>Added</strong>: Enhanced debug logging for installation process</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="version">
|
||||
<h5>v2.0.0 - March 2025</h5>
|
||||
<ul>
|
||||
@@ -628,7 +671,7 @@
|
||||
</div>
|
||||
|
||||
<div class="text-center mt-4">
|
||||
<p><strong>Transmission RSS Manager v2.0.0</strong></p>
|
||||
<p><strong id="about-version">Transmission RSS Manager v2.0.11</strong></p>
|
||||
<p>© 2025 PowerData.dk - All Rights Reserved</p>
|
||||
<p><a href="https://powerdata.dk" target="_blank">Visit PowerData.dk</a></p>
|
||||
</div>
|
||||
@@ -639,6 +682,60 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Update Alert Custom Styles -->
|
||||
<style>
|
||||
/* Custom styles for update alert to ensure it's visible */
|
||||
.update-alert {
|
||||
display: none;
|
||||
margin-top: 10px !important;
|
||||
border: 2px solid #007bff !important;
|
||||
background-color: #cce5ff !important;
|
||||
color: #004085 !important;
|
||||
font-weight: bold !important;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.2) !important;
|
||||
position: relative !important;
|
||||
z-index: 100 !important;
|
||||
}
|
||||
.update-alert span {
|
||||
color: #004085 !important;
|
||||
font-weight: bold !important;
|
||||
}
|
||||
|
||||
/* Floating update notification that's impossible to miss */
|
||||
#floating-update-notification {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
width: 300px;
|
||||
padding: 15px;
|
||||
background-color: #ff5555;
|
||||
color: white;
|
||||
border: 3px solid #cc0000;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0 20px rgba(0,0,0,0.5);
|
||||
z-index: 10000;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
}
|
||||
#floating-update-notification button {
|
||||
margin-top: 10px;
|
||||
padding: 5px 10px;
|
||||
background-color: white;
|
||||
color: #cc0000;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
}
|
||||
#floating-update-notification button:hover {
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Removed floating notification completely -->
|
||||
<!-- The floating notification was removed to fix persistent display issues -->
|
||||
|
||||
<!-- JavaScript Files -->
|
||||
<script src="/js/system-status.js"></script>
|
||||
<script src="/js/app.js"></script>
|
||||
|
@@ -16,15 +16,30 @@ function initSystemStatus() {
|
||||
|
||||
// Load system status
|
||||
function loadSystemStatus() {
|
||||
fetch('/api/system/status', {
|
||||
// Add cache-busting parameter
|
||||
const cacheBuster = `?_=${new Date().getTime()}`;
|
||||
fetch('/api/system/status' + cacheBuster, {
|
||||
headers: authHeaders()
|
||||
})
|
||||
.then(handleResponse)
|
||||
.then(data => {
|
||||
if (data.status === 'success') {
|
||||
// Update version display
|
||||
versionElement.textContent = data.data.version;
|
||||
uptimeElement.textContent = data.data.uptime;
|
||||
|
||||
// Also update footer version
|
||||
const footerVersion = document.getElementById('footer-version');
|
||||
if (footerVersion) {
|
||||
footerVersion.textContent = 'v' + data.data.version;
|
||||
}
|
||||
|
||||
// Update version in about modal too if it exists
|
||||
const aboutVersionElement = document.getElementById('about-version');
|
||||
if (aboutVersionElement) {
|
||||
aboutVersionElement.textContent = 'Transmission RSS Manager v' + data.data.version;
|
||||
}
|
||||
|
||||
// Update transmission status with icon
|
||||
if (data.data.transmissionStatus === 'Connected') {
|
||||
transmissionStatusElement.innerHTML = '<i class="fas fa-check-circle text-success"></i> Connected';
|
||||
@@ -41,38 +56,203 @@ function initSystemStatus() {
|
||||
});
|
||||
}
|
||||
|
||||
// More robust update check status tracking
|
||||
const UPDATE_KEY = 'trm_update_available';
|
||||
const CURRENT_VERSION_KEY = 'trm_current_version';
|
||||
const REMOTE_VERSION_KEY = 'trm_remote_version';
|
||||
|
||||
// Force clear any existing update notification state
|
||||
localStorage.removeItem(UPDATE_KEY);
|
||||
localStorage.removeItem(CURRENT_VERSION_KEY);
|
||||
localStorage.removeItem(REMOTE_VERSION_KEY);
|
||||
|
||||
let updateCheckInProgress = false;
|
||||
|
||||
// Function to show update alert
|
||||
function showUpdateAlert(currentVersion, remoteVersion) {
|
||||
// Set status text in the system status panel
|
||||
updateStatusElement.innerHTML = '<i class="fas fa-exclamation-circle text-warning"></i> Update available';
|
||||
|
||||
// Show only the original alert box in the dashboard
|
||||
try {
|
||||
const alertBox = updateAvailableDiv.querySelector('.alert');
|
||||
if (alertBox) {
|
||||
alertBox.style.display = 'block';
|
||||
const spanElement = alertBox.querySelector('span');
|
||||
if (spanElement) {
|
||||
spanElement.textContent = `A new version is available: ${currentVersion} → ${remoteVersion}`;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error showing original alert box:', e);
|
||||
}
|
||||
|
||||
// We've removed the floating notification entirely, so this part is skipped
|
||||
console.log('Update alert shown in dashboard:', currentVersion, '->', remoteVersion);
|
||||
|
||||
// Store in localStorage
|
||||
localStorage.setItem(UPDATE_KEY, 'true');
|
||||
localStorage.setItem(CURRENT_VERSION_KEY, currentVersion);
|
||||
localStorage.setItem(REMOTE_VERSION_KEY, remoteVersion);
|
||||
}
|
||||
|
||||
// Function to hide update alert
|
||||
function hideUpdateAlert() {
|
||||
// Hide original alert
|
||||
try {
|
||||
const alertBox = updateAvailableDiv.querySelector('.alert');
|
||||
if (alertBox) {
|
||||
alertBox.style.display = 'none';
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error hiding original alert:', e);
|
||||
}
|
||||
|
||||
// Clear localStorage
|
||||
localStorage.removeItem(UPDATE_KEY);
|
||||
localStorage.removeItem(CURRENT_VERSION_KEY);
|
||||
localStorage.removeItem(REMOTE_VERSION_KEY);
|
||||
|
||||
console.log('Update alert hidden');
|
||||
}
|
||||
|
||||
// Check localStorage on init and set up MutationObserver to prevent hiding
|
||||
(function checkStoredUpdateStatus() {
|
||||
const isUpdateAvailable = localStorage.getItem(UPDATE_KEY) === 'true';
|
||||
if (isUpdateAvailable) {
|
||||
const currentVersion = localStorage.getItem(CURRENT_VERSION_KEY);
|
||||
const remoteVersion = localStorage.getItem(REMOTE_VERSION_KEY);
|
||||
if (currentVersion && remoteVersion) {
|
||||
showUpdateAlert(currentVersion, remoteVersion);
|
||||
|
||||
// Set up mutation observer to detect and revert any attempts to hide the update alert
|
||||
const alertBox = updateAvailableDiv.querySelector('.alert');
|
||||
if (alertBox) {
|
||||
const observer = new MutationObserver(function(mutations) {
|
||||
mutations.forEach(function(mutation) {
|
||||
if (mutation.type === 'attributes' &&
|
||||
(mutation.attributeName === 'style' ||
|
||||
mutation.attributeName === 'class')) {
|
||||
|
||||
// If display is being changed to hide the element, force it back to visible
|
||||
if (alertBox.style.display !== 'block' ||
|
||||
alertBox.classList.contains('d-none') ||
|
||||
alertBox.style.visibility === 'hidden' ||
|
||||
alertBox.style.opacity === '0') {
|
||||
|
||||
console.log('Detected attempt to hide update button, forcing display');
|
||||
showUpdateAlert(currentVersion, remoteVersion);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Observe style and class attribute changes
|
||||
observer.observe(alertBox, {
|
||||
attributes: true,
|
||||
attributeFilter: ['style', 'class']
|
||||
});
|
||||
|
||||
// Store observer in window object to prevent garbage collection
|
||||
window._updateButtonObserver = observer;
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
// Check for updates
|
||||
function checkForUpdates() {
|
||||
updateStatusElement.innerHTML = '<i class="fas fa-circle-notch fa-spin"></i> Checking...';
|
||||
updateAvailableDiv.classList.add('d-none');
|
||||
updateCheckInProgress = true;
|
||||
|
||||
// Add test=true parameter to force update availability for testing
|
||||
const testMode = localStorage.getItem('showUpdateButton') === 'true';
|
||||
const url = testMode ? '/api/system/check-updates?test=true' : '/api/system/check-updates';
|
||||
const cacheBuster = `_=${new Date().getTime()}`;
|
||||
const url = testMode
|
||||
? `/api/system/check-updates?test=true&${cacheBuster}`
|
||||
: `/api/system/check-updates?${cacheBuster}`;
|
||||
|
||||
// Set a timeout to detect network issues
|
||||
const timeoutId = setTimeout(() => {
|
||||
updateStatusElement.innerHTML = '<i class="fas fa-times-circle text-danger"></i> Check timed out';
|
||||
updateCheckInProgress = false;
|
||||
showNotification('Update check timed out. Please try again later.', 'warning');
|
||||
}, 10000); // 10 second timeout
|
||||
|
||||
// Create a timeout controller
|
||||
const controller = new AbortController();
|
||||
const timeoutId2 = setTimeout(() => controller.abort(), 15000);
|
||||
|
||||
fetch(url, {
|
||||
headers: authHeaders()
|
||||
headers: authHeaders(),
|
||||
// Add a fetch timeout using abort controller
|
||||
signal: controller.signal // 15 second timeout
|
||||
})
|
||||
.then(handleResponse)
|
||||
.then(response => {
|
||||
clearTimeout(timeoutId2);
|
||||
clearTimeout(timeoutId);
|
||||
return response;
|
||||
})
|
||||
.catch(error => {
|
||||
clearTimeout(timeoutId2);
|
||||
clearTimeout(timeoutId);
|
||||
throw error;
|
||||
})
|
||||
.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 => {
|
||||
updateCheckInProgress = false;
|
||||
|
||||
if (data.status === 'success') {
|
||||
if (data.data.updateAvailable) {
|
||||
updateStatusElement.innerHTML = '<i class="fas fa-exclamation-circle text-warning"></i> Update available';
|
||||
updateAvailableDiv.classList.remove('d-none');
|
||||
updateAvailableDiv.querySelector('span').textContent =
|
||||
`A new version is available: ${data.data.currentVersion} → ${data.data.remoteVersion}`;
|
||||
if (data.data && data.data.updateAvailable) {
|
||||
// Show update alert with version info
|
||||
showUpdateAlert(data.data.currentVersion, data.data.remoteVersion);
|
||||
|
||||
// Log to console for debugging
|
||||
console.log('Update available detected:', data.data.currentVersion, '->', data.data.remoteVersion);
|
||||
} else {
|
||||
// No update available
|
||||
updateStatusElement.innerHTML = '<i class="fas fa-check-circle text-success"></i> Up to date';
|
||||
hideUpdateAlert();
|
||||
|
||||
// Force reload system status to ensure version is current
|
||||
setTimeout(() => loadSystemStatus(), 1000);
|
||||
}
|
||||
} else {
|
||||
// Error status but with a response
|
||||
updateStatusElement.innerHTML = '<i class="fas fa-times-circle text-danger"></i> Check failed';
|
||||
showNotification(data.message || 'Failed to check for updates', 'danger');
|
||||
// Don't clear update status on error - keep any previous update notification
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
updateCheckInProgress = false;
|
||||
clearTimeout(timeoutId);
|
||||
console.error('Error checking for updates:', error);
|
||||
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');
|
||||
}
|
||||
|
||||
// Don't clear update status on error - keep any previous update notification
|
||||
});
|
||||
}
|
||||
|
||||
@@ -83,37 +263,227 @@ function initSystemStatus() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Show loading state
|
||||
updateButton.disabled = true;
|
||||
updateButton.innerHTML = '<i class="fas fa-circle-notch fa-spin"></i> Updating...';
|
||||
// Disable test mode whenever we try to apply an update
|
||||
localStorage.setItem('showUpdateButton', 'false');
|
||||
|
||||
// Update toggle button text if it exists
|
||||
const testToggle = document.getElementById('toggle-test-update-button');
|
||||
if (testToggle) {
|
||||
testToggle.innerText = 'Enable Test Update';
|
||||
}
|
||||
|
||||
// Show loading state on both update buttons
|
||||
// Original button
|
||||
if (updateButton) {
|
||||
updateButton.disabled = true;
|
||||
updateButton.innerHTML = '<i class="fas fa-circle-notch fa-spin"></i> Updating...';
|
||||
}
|
||||
|
||||
// Floating notification button
|
||||
const floatingButton = document.getElementById('floating-update-button');
|
||||
if (floatingButton) {
|
||||
floatingButton.disabled = true;
|
||||
floatingButton.textContent = 'Updating...';
|
||||
}
|
||||
|
||||
showNotification('Applying update. Please wait...', 'info');
|
||||
|
||||
// Set a timeout for the update process
|
||||
const updateTimeoutId = setTimeout(() => {
|
||||
// Re-enable original button
|
||||
if (updateButton) {
|
||||
updateButton.disabled = false;
|
||||
updateButton.innerHTML = '<i class="fas fa-download"></i> Update Now';
|
||||
}
|
||||
|
||||
// Re-enable floating button
|
||||
if (floatingButton) {
|
||||
floatingButton.disabled = false;
|
||||
floatingButton.textContent = 'Update Now';
|
||||
}
|
||||
|
||||
showNotification('Update process timed out. Please try again or check server logs.', 'warning');
|
||||
}, 60000); // 60 second timeout for the entire update process
|
||||
|
||||
// Create a timeout controller
|
||||
const updateController = new AbortController();
|
||||
const updateTimeoutId2 = setTimeout(() => updateController.abort(), 45000);
|
||||
|
||||
fetch('/api/system/update', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...authHeaders()
|
||||
}
|
||||
},
|
||||
signal: updateController.signal // 45 second timeout
|
||||
})
|
||||
.then(handleResponse)
|
||||
.then(response => {
|
||||
clearTimeout(updateTimeoutId2);
|
||||
return response;
|
||||
})
|
||||
.catch(error => {
|
||||
clearTimeout(updateTimeoutId2);
|
||||
throw error;
|
||||
})
|
||||
.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') {
|
||||
// Check if there's an update message to determine if an update was actually applied
|
||||
const updateApplied = data.message && data.message.includes('Update applied successfully');
|
||||
const noNewUpdate = data.data && data.data.output && data.data.output.includes('already have the latest version');
|
||||
|
||||
// Hide update notification
|
||||
hideUpdateAlert();
|
||||
|
||||
if (noNewUpdate) {
|
||||
// If no update was needed, show a different message
|
||||
showNotification('You already have the latest version. No update was needed.', 'info');
|
||||
|
||||
// Re-enable both buttons
|
||||
if (updateButton) {
|
||||
updateButton.disabled = false;
|
||||
updateButton.innerHTML = '<i class="fas fa-download"></i> Check Again';
|
||||
}
|
||||
|
||||
const floatingButton = document.getElementById('floating-update-button');
|
||||
if (floatingButton) {
|
||||
floatingButton.disabled = false;
|
||||
floatingButton.textContent = 'Check Again';
|
||||
}
|
||||
|
||||
// Update page to show current version without reloading
|
||||
loadSystemStatus();
|
||||
|
||||
// Double-check system status again after a delay to ensure version is updated
|
||||
setTimeout(() => {
|
||||
loadSystemStatus();
|
||||
checkForUpdates(); // Run check again to update status text
|
||||
}, 2000);
|
||||
return;
|
||||
}
|
||||
|
||||
// Show success notification
|
||||
showNotification('Update applied successfully. The page will reload in 30 seconds.', 'success');
|
||||
|
||||
// Update both buttons with countdown
|
||||
let secondsLeft = 30;
|
||||
|
||||
// Function to update the countdown text
|
||||
function updateCountdown() {
|
||||
// Update original button if it exists
|
||||
if (updateButton) {
|
||||
updateButton.innerHTML = `<i class="fas fa-sync"></i> Reloading in ${secondsLeft}s...`;
|
||||
}
|
||||
|
||||
// Update floating button if it exists
|
||||
const floatingButton = document.getElementById('floating-update-button');
|
||||
if (floatingButton) {
|
||||
floatingButton.textContent = `Reloading in ${secondsLeft}s...`;
|
||||
}
|
||||
}
|
||||
|
||||
// Initial text update
|
||||
updateCountdown();
|
||||
|
||||
// Start countdown
|
||||
const countdownInterval = setInterval(() => {
|
||||
secondsLeft--;
|
||||
updateCountdown();
|
||||
|
||||
if (secondsLeft <= 0) {
|
||||
clearInterval(countdownInterval);
|
||||
|
||||
// Clear localStorage to ensure a clean reload
|
||||
localStorage.removeItem(UPDATE_KEY);
|
||||
localStorage.removeItem(CURRENT_VERSION_KEY);
|
||||
localStorage.removeItem(REMOTE_VERSION_KEY);
|
||||
|
||||
// Also ensure floating notification is completely removed
|
||||
const floatingNotification = document.getElementById('floating-update-notification');
|
||||
if (floatingNotification) {
|
||||
floatingNotification.style.display = 'none';
|
||||
floatingNotification.removeAttribute('style');
|
||||
}
|
||||
|
||||
// Force a clean reload
|
||||
window.location.href = window.location.href.split('#')[0] + '?t=' + new Date().getTime();
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
// Set a timer to reload the page after the service has time to restart
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
clearInterval(countdownInterval);
|
||||
|
||||
// Clear localStorage to ensure a clean reload
|
||||
localStorage.removeItem(UPDATE_KEY);
|
||||
localStorage.removeItem(CURRENT_VERSION_KEY);
|
||||
localStorage.removeItem(REMOTE_VERSION_KEY);
|
||||
|
||||
// Also ensure floating notification is completely removed
|
||||
const floatingNotification = document.getElementById('floating-update-notification');
|
||||
if (floatingNotification) {
|
||||
floatingNotification.style.display = 'none';
|
||||
floatingNotification.removeAttribute('style');
|
||||
}
|
||||
|
||||
// Force a clean reload with cache-busting parameter
|
||||
window.location.href = window.location.href.split('#')[0] + '?t=' + new Date().getTime();
|
||||
}, 30000);
|
||||
} else {
|
||||
updateButton.disabled = false;
|
||||
updateButton.innerHTML = '<i class="fas fa-download"></i> Update Now';
|
||||
// Enable both buttons on failure
|
||||
if (updateButton) {
|
||||
updateButton.disabled = false;
|
||||
updateButton.innerHTML = '<i class="fas fa-download"></i> Update Now';
|
||||
}
|
||||
|
||||
const floatingButton = document.getElementById('floating-update-button');
|
||||
if (floatingButton) {
|
||||
floatingButton.disabled = false;
|
||||
floatingButton.textContent = 'Update Now';
|
||||
}
|
||||
|
||||
showNotification(data.message || 'Failed to apply update', 'danger');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
clearTimeout(updateTimeoutId);
|
||||
console.error('Error applying update:', error);
|
||||
updateButton.disabled = false;
|
||||
updateButton.innerHTML = '<i class="fas fa-download"></i> Update Now';
|
||||
showNotification('Failed to connect to server', 'danger');
|
||||
|
||||
// Re-enable both buttons on error
|
||||
if (updateButton) {
|
||||
updateButton.disabled = false;
|
||||
updateButton.innerHTML = '<i class="fas fa-download"></i> Update Now';
|
||||
}
|
||||
|
||||
const floatingButton = document.getElementById('floating-update-button');
|
||||
if (floatingButton) {
|
||||
floatingButton.disabled = false;
|
||||
floatingButton.textContent = 'Update Now';
|
||||
}
|
||||
|
||||
// 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');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -129,12 +499,73 @@ function initSystemStatus() {
|
||||
updateButton.addEventListener('click', applyUpdate);
|
||||
}
|
||||
|
||||
// Add handler for floating refresh button
|
||||
const floatingRefreshButton = document.getElementById('floating-refresh-button');
|
||||
if (floatingRefreshButton) {
|
||||
floatingRefreshButton.addEventListener('click', () => {
|
||||
// Force a hard refresh of everything
|
||||
floatingRefreshButton.textContent = 'Refreshing...';
|
||||
floatingRefreshButton.disabled = true;
|
||||
|
||||
// Force reload system status
|
||||
loadSystemStatus();
|
||||
|
||||
// Force a check without the test parameter to get real status
|
||||
const realCheckUrl = `/api/system/check-updates?_=${new Date().getTime()}`;
|
||||
fetch(realCheckUrl, { headers: authHeaders() })
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log('Manual refresh result:', data);
|
||||
|
||||
if (data.status === 'success') {
|
||||
// Check if we're in test mode
|
||||
const isTestMode = localStorage.getItem('showUpdateButton') === 'true';
|
||||
|
||||
// If test mode is enabled but update says no update available, disable test mode
|
||||
if (isTestMode && data.data && !data.data.updateAvailable) {
|
||||
localStorage.setItem('showUpdateButton', 'false');
|
||||
testToggle.innerText = 'Enable Test Update';
|
||||
showNotification('Test mode has been disabled - no real update is available', 'info');
|
||||
hideUpdateAlert();
|
||||
showNotification(`Current version: ${data.data.currentVersion}. You are up to date.`, 'success');
|
||||
}
|
||||
// Regular update handling
|
||||
else if (data.data && data.data.updateAvailable) {
|
||||
showUpdateAlert(data.data.currentVersion, data.data.remoteVersion);
|
||||
showNotification(`Update is available: ${data.data.currentVersion} → ${data.data.remoteVersion}`, 'info');
|
||||
} else {
|
||||
hideUpdateAlert();
|
||||
showNotification(`Current version: ${data.data.currentVersion}. You are up to date.`, 'success');
|
||||
}
|
||||
}
|
||||
|
||||
// Re-enable button
|
||||
floatingRefreshButton.textContent = 'Refresh Status';
|
||||
floatingRefreshButton.disabled = false;
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error during manual refresh:', error);
|
||||
floatingRefreshButton.textContent = 'Refresh Status';
|
||||
floatingRefreshButton.disabled = false;
|
||||
showNotification('Error checking update status', 'danger');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Test mode toggle (for developers)
|
||||
const testToggle = document.getElementById('toggle-test-update-button');
|
||||
if (testToggle) {
|
||||
// Initialize based on current localStorage setting
|
||||
const isTestMode = localStorage.getItem('showUpdateButton') === 'true';
|
||||
|
||||
// If test mode is enabled but we have a version mismatch, update the stored version
|
||||
if (isTestMode && versionElement && versionElement.textContent) {
|
||||
const currentVersion = versionElement.textContent.trim();
|
||||
if (localStorage.getItem(CURRENT_VERSION_KEY) !== currentVersion) {
|
||||
localStorage.setItem(CURRENT_VERSION_KEY, currentVersion);
|
||||
}
|
||||
}
|
||||
|
||||
// Update toggle text
|
||||
testToggle.innerText = isTestMode ? 'Disable Test Update' : 'Enable Test Update';
|
||||
|
||||
@@ -147,16 +578,206 @@ function initSystemStatus() {
|
||||
localStorage.setItem('showUpdateButton', newSetting);
|
||||
testToggle.innerText = newSetting ? 'Disable Test Update' : 'Enable Test Update';
|
||||
|
||||
// Re-check for updates with new setting
|
||||
checkForUpdates();
|
||||
if (newSetting) {
|
||||
// Get the current version from the version element
|
||||
let currentVersion = '2.0.11'; // Default fallback
|
||||
if (versionElement && versionElement.textContent) {
|
||||
currentVersion = versionElement.textContent.trim();
|
||||
}
|
||||
|
||||
showNotification(`Test update button ${newSetting ? 'enabled' : 'disabled'}`, 'info');
|
||||
// If enabling test mode, force show update button
|
||||
showUpdateAlert(currentVersion, '2.1.0-test');
|
||||
updateStatusElement.innerHTML = '<i class="fas fa-exclamation-circle text-warning"></i> Update available (TEST MODE)';
|
||||
|
||||
// Add test mode indicator to floating notification
|
||||
const floatingNotification = document.getElementById('floating-update-notification');
|
||||
if (floatingNotification) {
|
||||
const testBadge = document.createElement('div');
|
||||
testBadge.style.backgroundColor = 'orange';
|
||||
testBadge.style.color = 'black';
|
||||
testBadge.style.padding = '3px 8px';
|
||||
testBadge.style.borderRadius = '4px';
|
||||
testBadge.style.fontWeight = 'bold';
|
||||
testBadge.style.marginBottom = '5px';
|
||||
testBadge.style.fontSize = '12px';
|
||||
testBadge.textContent = 'TEST MODE - NOT A REAL UPDATE';
|
||||
|
||||
// Insert at the top of the notification
|
||||
floatingNotification.insertBefore(testBadge, floatingNotification.firstChild);
|
||||
}
|
||||
|
||||
showNotification('TEST MODE ENABLED - This is not a real update', 'warning');
|
||||
} else {
|
||||
// If disabling test mode, check for real updates
|
||||
hideUpdateAlert();
|
||||
|
||||
// Force a check without the test parameter to get real status
|
||||
const realCheckUrl = '/api/system/check-updates';
|
||||
fetch(realCheckUrl, { headers: authHeaders() })
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
console.log('Real update check result:', data);
|
||||
if (data.status === 'success' && data.data && !data.data.updateAvailable) {
|
||||
showNotification('No actual updates are available.', 'info');
|
||||
} else if (data.status === 'success' && data.data && data.data.updateAvailable) {
|
||||
showUpdateAlert(data.data.currentVersion, data.data.remoteVersion);
|
||||
showNotification(`A real update is available: ${data.data.currentVersion} → ${data.data.remoteVersion}`, 'info');
|
||||
}
|
||||
})
|
||||
.catch(error => console.error('Error checking for real updates:', error));
|
||||
|
||||
showNotification('Test update button disabled', 'info');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Persistent update button - force display every second if update is available
|
||||
function forceShowUpdateButton() {
|
||||
const isUpdateAvailable = localStorage.getItem(UPDATE_KEY) === 'true';
|
||||
|
||||
if (isUpdateAvailable) {
|
||||
// Get the most current version
|
||||
let currentVersion = localStorage.getItem(CURRENT_VERSION_KEY);
|
||||
const remoteVersion = localStorage.getItem(REMOTE_VERSION_KEY);
|
||||
|
||||
// If we have the version element on screen, use that as the source of truth
|
||||
if (versionElement && versionElement.textContent) {
|
||||
const displayedVersion = versionElement.textContent.trim();
|
||||
// Update stored version if different
|
||||
if (displayedVersion !== currentVersion) {
|
||||
localStorage.setItem(CURRENT_VERSION_KEY, displayedVersion);
|
||||
currentVersion = displayedVersion;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentVersion && remoteVersion) {
|
||||
// Check floating notification
|
||||
const floatingNotification = document.getElementById('floating-update-notification');
|
||||
if (floatingNotification && floatingNotification.style.display !== 'block') {
|
||||
console.log('Forcing floating update notification display');
|
||||
|
||||
// Set the version text
|
||||
const versionElement = document.getElementById('floating-update-version');
|
||||
if (versionElement) {
|
||||
versionElement.textContent = `Version ${currentVersion} → ${remoteVersion}`;
|
||||
}
|
||||
|
||||
// Apply strong styling - make sure to completely override any previous styles
|
||||
floatingNotification.setAttribute('style', ''); // Clear any previous styles first
|
||||
floatingNotification.setAttribute('style', ''); // Clear any previous styles first
|
||||
floatingNotification.setAttribute('style',
|
||||
'display: block !important; ' +
|
||||
'visibility: visible !important; ' +
|
||||
'opacity: 1 !important; ' +
|
||||
'position: fixed !important; ' +
|
||||
'top: 20px !important; ' +
|
||||
'right: 20px !important; ' +
|
||||
'width: 300px !important; ' +
|
||||
'padding: 15px !important; ' +
|
||||
'background-color: #ff5555 !important; ' +
|
||||
'color: white !important; ' +
|
||||
'border: 3px solid #cc0000 !important; ' +
|
||||
'border-radius: 5px !important; ' +
|
||||
'box-shadow: 0 0 20px rgba(0,0,0,0.5) !important; ' +
|
||||
'z-index: 10000 !important; ' +
|
||||
'font-weight: bold !important; ' +
|
||||
'text-align: center !important;'
|
||||
);
|
||||
|
||||
// Ensure button has correct event handler
|
||||
const updateButton = document.getElementById('floating-update-button');
|
||||
if (updateButton) {
|
||||
// Remove any existing listeners
|
||||
updateButton.removeEventListener('click', applyUpdate);
|
||||
// Add new listener
|
||||
updateButton.addEventListener('click', applyUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
// Still try the original alert as a fallback
|
||||
try {
|
||||
const alertBox = updateAvailableDiv.querySelector('.alert');
|
||||
if (alertBox && alertBox.style.display !== 'block') {
|
||||
alertBox.style.display = 'block';
|
||||
updateAvailableDiv.style.display = 'block';
|
||||
|
||||
// Update message
|
||||
const spanElement = alertBox.querySelector('span');
|
||||
if (spanElement) {
|
||||
spanElement.textContent = `A new version is available: ${currentVersion} → ${remoteVersion}`;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error forcing original update button:', e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize
|
||||
loadSystemStatus();
|
||||
checkForUpdates();
|
||||
|
||||
// SUPER EMERGENCY FIX: Force hide and remove all update notifications
|
||||
const emergencyFix = () => {
|
||||
// Hard reset all update states
|
||||
localStorage.clear(); // Clear ALL localStorage to be absolutely safe
|
||||
|
||||
// Find and destroy any floating notification elements
|
||||
document.querySelectorAll('[id*="notification"], [id*="update"], [class*="notification"], [class*="update"]').forEach(el => {
|
||||
try {
|
||||
if (el.id !== 'update-status' && !el.id.includes('refresh')) {
|
||||
el.style.cssText = 'display: none !important; visibility: hidden !important; opacity: 0 !important';
|
||||
el.removeAttribute('style');
|
||||
|
||||
if (el.parentNode) {
|
||||
el.parentNode.removeChild(el);
|
||||
console.log('Element removed from DOM:', el.id || el.className || 'unnamed element');
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error removing element:', e);
|
||||
}
|
||||
});
|
||||
|
||||
// Add a MutationObserver to keep killing any notification elements that might reappear
|
||||
if (!window._notificationKiller) {
|
||||
const observer = new MutationObserver(mutations => {
|
||||
mutations.forEach(mutation => {
|
||||
mutation.addedNodes.forEach(node => {
|
||||
if (node.nodeType === 1) { // Element node
|
||||
if ((node.id && (node.id.includes('notification') || node.id.includes('update'))) ||
|
||||
(node.className && (node.className.includes('notification') || node.className.includes('update')))) {
|
||||
if (node.id !== 'update-status' && !node.id.includes('refresh')) {
|
||||
node.style.cssText = 'display: none !important';
|
||||
if (node.parentNode) {
|
||||
node.parentNode.removeChild(node);
|
||||
console.log('Dynamically added notification killed');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
observer.observe(document.body, {
|
||||
childList: true,
|
||||
subtree: true
|
||||
});
|
||||
|
||||
window._notificationKiller = observer;
|
||||
}
|
||||
|
||||
console.log('Super emergency notification cleanup complete');
|
||||
};
|
||||
|
||||
// Run immediately
|
||||
emergencyFix();
|
||||
|
||||
// Run again after delays to ensure it works
|
||||
setTimeout(emergencyFix, 100);
|
||||
setTimeout(emergencyFix, 500);
|
||||
setTimeout(emergencyFix, 1000);
|
||||
|
||||
// Set interval to refresh uptime every minute
|
||||
setInterval(loadSystemStatus, 60000);
|
||||
|
212
scripts/create-module-links.sh
Executable file
212
scripts/create-module-links.sh
Executable file
@@ -0,0 +1,212 @@
|
||||
#!/bin/bash
|
||||
# Script to create symlinks for all modules in different naming styles
|
||||
# This ensures compatibility with different module import styles
|
||||
|
||||
APP_DIR="$(dirname "$(dirname "$(readlink -f "$0")")")"
|
||||
MODULE_DIR="$APP_DIR/modules"
|
||||
|
||||
echo "Creating module symlinks for compatibility..."
|
||||
echo "Module directory: $MODULE_DIR"
|
||||
|
||||
# Create a function to make bidirectional symlinks
|
||||
create_module_symlinks() {
|
||||
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
|
||||
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
|
||||
|
||||
# Setup production directory if needed
|
||||
setup_production_dir
|
@@ -1,165 +1,140 @@
|
||||
#!/bin/bash
|
||||
# Test and start script for Transmission RSS Manager
|
||||
# This script checks the installation, dependencies, and starts the application
|
||||
# Script to ensure data directory exists and start the application
|
||||
|
||||
# Text formatting
|
||||
BOLD='\033[1m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[0;33m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m' # No Color
|
||||
# Define paths
|
||||
APP_DIR="$(dirname "$(dirname "$(readlink -f "$0")")")"
|
||||
DATA_DIR="$APP_DIR/data"
|
||||
|
||||
# Get directory of this script
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
|
||||
APP_DIR="$(dirname "$SCRIPT_DIR")"
|
||||
|
||||
# Function to check if a command exists
|
||||
command_exists() {
|
||||
command -v "$1" &> /dev/null
|
||||
}
|
||||
|
||||
# Check Node.js and npm
|
||||
check_node() {
|
||||
echo -e "${BOLD}Checking Node.js and npm...${NC}"
|
||||
|
||||
if command_exists node; then
|
||||
NODE_VERSION=$(node -v)
|
||||
echo -e "${GREEN}Node.js is installed: $NODE_VERSION${NC}"
|
||||
else
|
||||
echo -e "${RED}Node.js is not installed. Please install Node.js 14 or later.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if command_exists npm; then
|
||||
NPM_VERSION=$(npm -v)
|
||||
echo -e "${GREEN}npm is installed: $NPM_VERSION${NC}"
|
||||
else
|
||||
echo -e "${RED}npm is not installed. Please install npm.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Check if Transmission is running
|
||||
check_transmission() {
|
||||
echo -e "${BOLD}Checking Transmission...${NC}"
|
||||
|
||||
# Try to get the status of the transmission-daemon service
|
||||
if command_exists systemctl; then
|
||||
if systemctl is-active --quiet transmission-daemon; then
|
||||
echo -e "${GREEN}Transmission daemon is running${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}Warning: Transmission daemon does not appear to be running${NC}"
|
||||
echo -e "${YELLOW}You may need to start it with: sudo systemctl start transmission-daemon${NC}"
|
||||
fi
|
||||
else
|
||||
# Try a different method if systemctl is not available
|
||||
if pgrep -x "transmission-daemon" > /dev/null; then
|
||||
echo -e "${GREEN}Transmission daemon is running${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}Warning: Transmission daemon does not appear to be running${NC}"
|
||||
echo -e "${YELLOW}Please start Transmission daemon before using this application${NC}"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# Check dependencies in package.json
|
||||
check_dependencies() {
|
||||
echo -e "${BOLD}Checking dependencies...${NC}"
|
||||
|
||||
# Check if node_modules exists
|
||||
if [ ! -d "$APP_DIR/node_modules" ]; then
|
||||
echo -e "${YELLOW}Node modules not found. Installing dependencies...${NC}"
|
||||
cd "$APP_DIR" && npm install
|
||||
echo "Starting Transmission RSS Manager..."
|
||||
echo "Application directory: $APP_DIR"
|
||||
echo "Data directory: $DATA_DIR"
|
||||
|
||||
# Ensure the data directory exists
|
||||
if [ ! -d "$DATA_DIR" ]; then
|
||||
echo "Creating data directory: $DATA_DIR"
|
||||
mkdir -p "$DATA_DIR"
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Failed to create data directory. Trying alternative method..."
|
||||
# Try alternative method if standard mkdir fails
|
||||
cd "$APP_DIR" && mkdir -p data
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}Failed to install dependencies.${NC}"
|
||||
echo "ERROR: Both methods to create data directory failed. Please check permissions."
|
||||
exit 1
|
||||
else
|
||||
echo -e "${GREEN}Dependencies installed successfully${NC}"
|
||||
fi
|
||||
else
|
||||
echo -e "${GREEN}Dependencies are already installed${NC}"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Set permissions
|
||||
chmod -R 755 "$DATA_DIR" || {
|
||||
echo "Warning: Failed to set permissions on data directory"
|
||||
}
|
||||
|
||||
# Check if config.json exists
|
||||
check_config() {
|
||||
echo -e "${BOLD}Checking configuration...${NC}"
|
||||
|
||||
if [ ! -f "$APP_DIR/config.json" ]; then
|
||||
echo -e "${RED}Configuration file not found: $APP_DIR/config.json${NC}"
|
||||
echo -e "${YELLOW}Please run the installer or create a config.json file${NC}"
|
||||
# Check for RSS files
|
||||
if [ ! -f "$DATA_DIR/rss-feeds.json" ]; then
|
||||
echo "Creating initial empty rss-feeds.json file"
|
||||
echo "[]" > "$DATA_DIR/rss-feeds.json" || {
|
||||
echo "ERROR: Failed to create rss-feeds.json file"
|
||||
exit 1
|
||||
else
|
||||
echo -e "${GREEN}Configuration file found${NC}"
|
||||
fi
|
||||
}
|
||||
}
|
||||
fi
|
||||
|
||||
# Start the application
|
||||
start_app() {
|
||||
echo -e "${BOLD}Starting Transmission RSS Manager...${NC}"
|
||||
if [ ! -f "$DATA_DIR/rss-items.json" ]; then
|
||||
echo "Creating initial empty rss-items.json file"
|
||||
echo "[]" > "$DATA_DIR/rss-items.json" || {
|
||||
echo "ERROR: Failed to create rss-items.json file"
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
|
||||
# Check if running as a service
|
||||
if command_exists systemctl; then
|
||||
if systemctl is-active --quiet transmission-rss-manager; then
|
||||
echo -e "${YELLOW}Transmission RSS Manager is already running as a service${NC}"
|
||||
echo -e "${YELLOW}To restart it, use: sudo systemctl restart transmission-rss-manager${NC}"
|
||||
exit 0
|
||||
# Find the node executable path
|
||||
NODE_PATH=$(which node 2>/dev/null)
|
||||
if [ -z "$NODE_PATH" ]; then
|
||||
# If node is not in PATH, try common locations
|
||||
for path in /usr/bin/node /usr/local/bin/node /opt/node/bin/node /usr/lib/node; do
|
||||
if [ -x "$path" ]; then
|
||||
NODE_PATH="$path"
|
||||
break
|
||||
fi
|
||||
fi
|
||||
|
||||
# Start the application
|
||||
cd "$APP_DIR"
|
||||
|
||||
# Parse arguments
|
||||
FOREGROUND=false
|
||||
DEBUG=false
|
||||
|
||||
while [[ "$#" -gt 0 ]]; do
|
||||
case $1 in
|
||||
--foreground|-f) FOREGROUND=true ;;
|
||||
--debug|-d) DEBUG=true ;;
|
||||
*) echo "Unknown parameter: $1"; exit 1 ;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
if [ "$FOREGROUND" = true ]; then
|
||||
echo -e "${GREEN}Starting in foreground mode...${NC}"
|
||||
# If we still can't find node, use the default path
|
||||
if [ -z "$NODE_PATH" ]; then
|
||||
NODE_PATH="/usr/bin/node"
|
||||
echo "Warning: Node.js not found in PATH, using default path: $NODE_PATH"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$DEBUG" = true ]; then
|
||||
echo -e "${YELLOW}Debug mode enabled${NC}"
|
||||
DEBUG_ENABLED=true node server.js
|
||||
else
|
||||
node server.js
|
||||
fi
|
||||
# Create module symlinks to ensure compatibility
|
||||
echo "Creating module symlinks for compatibility..."
|
||||
MODULE_DIR="$APP_DIR/modules"
|
||||
|
||||
# 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" ]; 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" ]; 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" ]; 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
|
||||
for module in "$MODULE_DIR"/[a-z]*[A-Z]*.js; do
|
||||
if [ -f "$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" ]; 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" ]; 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" ]; then
|
||||
ln -sf "$BASE_NAME" "$MODULE_DIR/$NO_EXT_HYPHEN"
|
||||
echo "Created symlink: $NO_EXT_HYPHEN -> $BASE_NAME"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
else
|
||||
echo -e "${GREEN}Starting in background mode...${NC}"
|
||||
|
||||
if [ "$DEBUG" = true ]; then
|
||||
echo -e "${YELLOW}Debug mode enabled${NC}"
|
||||
DEBUG_ENABLED=true nohup node server.js > logs/output.log 2>&1 &
|
||||
else
|
||||
nohup node server.js > logs/output.log 2>&1 &
|
||||
fi
|
||||
|
||||
echo $! > "$APP_DIR/transmission-rss-manager.pid"
|
||||
echo -e "${GREEN}Application started with PID: $!${NC}"
|
||||
echo -e "${GREEN}Logs available at: $APP_DIR/logs/output.log${NC}"
|
||||
echo "Warning: Module directory not found at $MODULE_DIR"
|
||||
fi
|
||||
}
|
||||
|
||||
# Main script
|
||||
echo -e "${BOLD}==================================================${NC}"
|
||||
echo -e "${BOLD} Transmission RSS Manager - Test & Start ${NC}"
|
||||
echo -e "${BOLD}==================================================${NC}"
|
||||
echo
|
||||
|
||||
# Run checks
|
||||
check_node
|
||||
check_transmission
|
||||
check_dependencies
|
||||
check_config
|
||||
# Execute the symlink creation function
|
||||
create_module_symlinks
|
||||
|
||||
# Start the application
|
||||
start_app "$@"
|
||||
cd "$APP_DIR" || { echo "Failed to change to application directory"; exit 1; }
|
||||
echo "Starting node.js application with: $NODE_PATH $APP_DIR/server.js"
|
||||
exec "$NODE_PATH" "$APP_DIR/server.js"
|
@@ -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
|
||||
|
536
server.js
536
server.js
@@ -5,7 +5,8 @@
|
||||
|
||||
const express = require('express');
|
||||
const path = require('path');
|
||||
const fs = require('fs').promises;
|
||||
const fsPromises = require('fs').promises;
|
||||
const fs = require('fs'); // Regular fs module for synchronous operations
|
||||
const bodyParser = require('body-parser');
|
||||
const cors = require('cors');
|
||||
const morgan = require('morgan');
|
||||
@@ -13,21 +14,96 @@ const http = require('http');
|
||||
const https = require('https');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const bcrypt = require('bcrypt');
|
||||
const { exec } = require('child_process');
|
||||
const util = require('util');
|
||||
const execAsync = util.promisify(exec);
|
||||
const semver = require('semver'); // For semantic version comparison
|
||||
|
||||
// Import custom modules
|
||||
const RssFeedManager = require('./modules/rss-feed-manager.js');
|
||||
const TransmissionClient = require('./modules/transmission-client.js');
|
||||
const PostProcessor = require('./modules/post-processor.js');
|
||||
let RssFeedManager, TransmissionClient, PostProcessor;
|
||||
|
||||
/**
|
||||
* Helper function to try multiple module paths
|
||||
* This function tries to require a module using different naming conventions
|
||||
* to work around issues with module resolution in different Node.js environments
|
||||
*
|
||||
* @param {string} baseName - The base module name without extension or path
|
||||
* @returns {Object} The loaded module
|
||||
* @throws {Error} If module cannot be loaded from any path
|
||||
*/
|
||||
function loadModule(baseName) {
|
||||
// Generate all possible module paths
|
||||
const paths = [
|
||||
`./modules/${baseName}.js`, // With extension
|
||||
`./modules/${baseName}`, // Without extension
|
||||
|
||||
// Convert hyphenated to camelCase
|
||||
`./modules/${baseName.replace(/-([a-z])/g, (_, c) => c.toUpperCase())}.js`,
|
||||
`./modules/${baseName.replace(/-([a-z])/g, (_, c) => c.toUpperCase())}`,
|
||||
|
||||
// Convert camelCase to hyphenated
|
||||
`./modules/${baseName.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase()}.js`,
|
||||
`./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 {
|
||||
return require(modulePath);
|
||||
} catch (err) {
|
||||
if (err.code !== 'MODULE_NOT_FOUND' || !err.message.includes(modulePath)) {
|
||||
// This is a real error in the module, not just not finding it
|
||||
throw err;
|
||||
}
|
||||
lastError = err;
|
||||
}
|
||||
}
|
||||
|
||||
// If we get here, we couldn't load the module from any path
|
||||
const error = new Error(`Could not load module '${baseName}' from any path. Original error: ${lastError.message}`);
|
||||
error.original = lastError;
|
||||
throw error;
|
||||
}
|
||||
|
||||
// Try loading modules with improved error reporting
|
||||
try {
|
||||
RssFeedManager = loadModule('rss-feed-manager');
|
||||
console.log('Successfully loaded RssFeedManager module');
|
||||
|
||||
TransmissionClient = loadModule('transmission-client');
|
||||
console.log('Successfully loaded TransmissionClient module');
|
||||
|
||||
PostProcessor = loadModule('post-processor');
|
||||
console.log('Successfully loaded PostProcessor module');
|
||||
} catch (err) {
|
||||
console.error('Fatal error loading required module:', err.message);
|
||||
console.error('Please make sure all module files exist and are valid JavaScript');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Constants and configuration
|
||||
const DEFAULT_CONFIG_PATH = path.join(__dirname, 'config.json');
|
||||
const DEFAULT_CONFIG_PATH = '/etc/transmission-rss-manager/config.json';
|
||||
const FALLBACK_CONFIG_PATH = path.join(__dirname, 'config.json');
|
||||
const DEFAULT_PORT = 3000;
|
||||
const JWT_SECRET = process.env.JWT_SECRET || 'transmission-rss-manager-secret';
|
||||
const JWT_EXPIRY = '24h';
|
||||
|
||||
// Get the version from package.json (single source of truth)
|
||||
const PACKAGE_JSON = require('./package.json');
|
||||
const APP_VERSION = PACKAGE_JSON.version;
|
||||
// Re-read the package.json file each time to ensure we get the latest version
|
||||
const APP_VERSION = (() => {
|
||||
try {
|
||||
// Use synchronous file read to ensure we have the version before continuing
|
||||
const packageJsonPath = path.join(__dirname, 'package.json');
|
||||
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
||||
return packageJson.version;
|
||||
} catch (err) {
|
||||
console.error('Error reading package.json version:', err);
|
||||
// Fallback to requiring package.json if file read fails
|
||||
const PACKAGE_JSON = require('./package.json');
|
||||
return PACKAGE_JSON.version;
|
||||
}
|
||||
})();
|
||||
|
||||
// Create Express app
|
||||
const app = express();
|
||||
@@ -97,6 +173,7 @@ async function loadConfig() {
|
||||
// Define default configuration
|
||||
const defaultConfig = {
|
||||
version: APP_VERSION, // Use the version from package.json
|
||||
installPath: __dirname, // Store the path to the installation directory
|
||||
transmissionConfig: {
|
||||
host: 'localhost',
|
||||
port: 9091,
|
||||
@@ -147,9 +224,11 @@ async function loadConfig() {
|
||||
logLevel: "info"
|
||||
};
|
||||
|
||||
// Try primary config location first
|
||||
try {
|
||||
// Try to read existing config
|
||||
const configData = await fs.readFile(DEFAULT_CONFIG_PATH, 'utf8');
|
||||
// Try to read existing config from primary location
|
||||
console.log(`Trying to load config from: ${DEFAULT_CONFIG_PATH}`);
|
||||
const configData = await fsPromises.readFile(DEFAULT_CONFIG_PATH, 'utf8');
|
||||
const loadedConfig = JSON.parse(configData);
|
||||
|
||||
// Use recursive merge function to merge configs
|
||||
@@ -162,17 +241,68 @@ async function loadConfig() {
|
||||
await saveConfig(mergedConfig);
|
||||
}
|
||||
|
||||
console.log(`Config loaded from ${DEFAULT_CONFIG_PATH}`);
|
||||
return mergedConfig;
|
||||
} catch (readError) {
|
||||
// If file not found, create default config
|
||||
if (readError.code === 'ENOENT') {
|
||||
console.log('Config file not found, creating default configuration');
|
||||
await saveConfig(defaultConfig);
|
||||
return defaultConfig;
|
||||
} catch (primaryError) {
|
||||
// If file not found in primary location, try fallback location
|
||||
if (primaryError.code === 'ENOENT') {
|
||||
console.log(`Config not found at ${DEFAULT_CONFIG_PATH}, trying fallback location...`);
|
||||
|
||||
try {
|
||||
const fallbackData = await fsPromises.readFile(FALLBACK_CONFIG_PATH, 'utf8');
|
||||
const fallbackConfig = JSON.parse(fallbackData);
|
||||
|
||||
// Merge configs
|
||||
const mergedConfig = mergeConfigs(defaultConfig, fallbackConfig);
|
||||
|
||||
// If version is different, save updated config
|
||||
if (fallbackConfig.version !== APP_VERSION) {
|
||||
console.log(`Updating config from version ${fallbackConfig.version || 'unknown'} to ${APP_VERSION}`);
|
||||
mergedConfig.version = APP_VERSION;
|
||||
}
|
||||
|
||||
// Try to save to primary location, but don't fail if we can't
|
||||
try {
|
||||
// Create directory if it doesn't exist
|
||||
await fsPromises.mkdir(path.dirname(DEFAULT_CONFIG_PATH), { recursive: true });
|
||||
await fsPromises.writeFile(DEFAULT_CONFIG_PATH, JSON.stringify(mergedConfig, null, 2), 'utf8');
|
||||
console.log(`Migrated config from ${FALLBACK_CONFIG_PATH} to ${DEFAULT_CONFIG_PATH}`);
|
||||
} catch (saveError) {
|
||||
console.warn(`Could not save config to ${DEFAULT_CONFIG_PATH}: ${saveError.message}`);
|
||||
console.warn('Continuing with fallback config location');
|
||||
}
|
||||
|
||||
console.log(`Config loaded from fallback location: ${FALLBACK_CONFIG_PATH}`);
|
||||
return mergedConfig;
|
||||
} catch (fallbackError) {
|
||||
// If file not found in fallback location, create default config
|
||||
if (fallbackError.code === 'ENOENT') {
|
||||
console.log('Config file not found in either location, creating default configuration');
|
||||
|
||||
// Try to save to primary location first
|
||||
try {
|
||||
await fsPromises.mkdir(path.dirname(DEFAULT_CONFIG_PATH), { recursive: true });
|
||||
await fsPromises.writeFile(DEFAULT_CONFIG_PATH, JSON.stringify(defaultConfig, null, 2), 'utf8');
|
||||
console.log(`Created default config at ${DEFAULT_CONFIG_PATH}`);
|
||||
} catch (saveError) {
|
||||
console.warn(`Could not save config to ${DEFAULT_CONFIG_PATH}: ${saveError.message}`);
|
||||
console.warn('Saving to fallback location instead');
|
||||
|
||||
// Save to fallback location instead
|
||||
await fsPromises.writeFile(FALLBACK_CONFIG_PATH, JSON.stringify(defaultConfig, null, 2), 'utf8');
|
||||
console.log(`Created default config at ${FALLBACK_CONFIG_PATH}`);
|
||||
}
|
||||
|
||||
return defaultConfig;
|
||||
}
|
||||
|
||||
// For other errors, rethrow
|
||||
throw fallbackError;
|
||||
}
|
||||
}
|
||||
|
||||
// For other errors, rethrow
|
||||
throw readError;
|
||||
throw primaryError;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading configuration:', error);
|
||||
@@ -231,8 +361,21 @@ function mergeConfigs(defaultConfig, userConfig) {
|
||||
*/
|
||||
async function saveConfig(config) {
|
||||
try {
|
||||
await fs.writeFile(DEFAULT_CONFIG_PATH, JSON.stringify(config, null, 2), 'utf8');
|
||||
console.log('Configuration saved');
|
||||
// Always try to save to the primary config location first
|
||||
try {
|
||||
// Make sure directory exists
|
||||
await fsPromises.mkdir(path.dirname(DEFAULT_CONFIG_PATH), { recursive: true });
|
||||
await fsPromises.writeFile(DEFAULT_CONFIG_PATH, JSON.stringify(config, null, 2), 'utf8');
|
||||
console.log(`Configuration saved to ${DEFAULT_CONFIG_PATH}`);
|
||||
return;
|
||||
} catch (primaryError) {
|
||||
console.warn(`Could not save config to ${DEFAULT_CONFIG_PATH}: ${primaryError.message}`);
|
||||
console.warn('Trying fallback location...');
|
||||
|
||||
// If we couldn't save to the primary location, try the fallback
|
||||
await fsPromises.writeFile(FALLBACK_CONFIG_PATH, JSON.stringify(config, null, 2), 'utf8');
|
||||
console.log(`Configuration saved to fallback location: ${FALLBACK_CONFIG_PATH}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error saving configuration:', error);
|
||||
throw error;
|
||||
@@ -256,8 +399,8 @@ async function startServer() {
|
||||
config.securitySettings?.sslKeyPath) {
|
||||
try {
|
||||
const sslOptions = {
|
||||
key: await fs.readFile(config.securitySettings.sslKeyPath),
|
||||
cert: await fs.readFile(config.securitySettings.sslCertPath)
|
||||
key: await fsPromises.readFile(config.securitySettings.sslKeyPath),
|
||||
cert: await fsPromises.readFile(config.securitySettings.sslCertPath)
|
||||
};
|
||||
|
||||
server = https.createServer(sslOptions, app);
|
||||
@@ -329,7 +472,7 @@ app.get('/api/status', authenticateJWT, async (req, res) => {
|
||||
res.json({
|
||||
success: true,
|
||||
status: 'running',
|
||||
version: '1.2.0',
|
||||
version: APP_VERSION, // Use the dynamic APP_VERSION from package.json
|
||||
transmissionConnected: transmissionStatus.connected,
|
||||
transmissionVersion: transmissionStatus.version,
|
||||
transmissionStats: {
|
||||
@@ -379,9 +522,45 @@ app.post('/api/config', authenticateJWT, async (req, res) => {
|
||||
// Merge the new config with the existing one
|
||||
const newConfig = { ...config, ...req.body };
|
||||
|
||||
// Keep passwords if they're not provided
|
||||
if (newConfig.transmissionConfig && !newConfig.transmissionConfig.password && config.transmissionConfig) {
|
||||
newConfig.transmissionConfig.password = config.transmissionConfig.password;
|
||||
// Preserve existing Transmission configuration values when not explicitly provided
|
||||
if (newConfig.transmissionConfig && config.transmissionConfig) {
|
||||
// First create a copy of the existing configuration
|
||||
const preservedTransConfig = { ...config.transmissionConfig };
|
||||
|
||||
// Only update values that are explicitly provided and not empty
|
||||
if (!req.body.transmissionConfig?.host) {
|
||||
newConfig.transmissionConfig.host = preservedTransConfig.host;
|
||||
}
|
||||
|
||||
if (!req.body.transmissionConfig?.port) {
|
||||
newConfig.transmissionConfig.port = preservedTransConfig.port;
|
||||
}
|
||||
|
||||
if (!req.body.transmissionConfig?.path) {
|
||||
newConfig.transmissionConfig.path = preservedTransConfig.path;
|
||||
}
|
||||
|
||||
if (!req.body.transmissionConfig?.username) {
|
||||
newConfig.transmissionConfig.username = preservedTransConfig.username;
|
||||
}
|
||||
|
||||
// Always preserve password if not provided
|
||||
if (!newConfig.transmissionConfig.password) {
|
||||
newConfig.transmissionConfig.password = preservedTransConfig.password;
|
||||
}
|
||||
}
|
||||
|
||||
// Preserve remote configuration settings if not explicitly provided
|
||||
if (newConfig.remoteConfig && config.remoteConfig) {
|
||||
// Make sure isRemote setting is preserved if not explicitly set
|
||||
if (req.body.remoteConfig?.isRemote === undefined) {
|
||||
newConfig.remoteConfig.isRemote = config.remoteConfig.isRemote;
|
||||
}
|
||||
|
||||
// Preserve directory mappings if not provided
|
||||
if (!req.body.remoteConfig?.directoryMapping && config.remoteConfig.directoryMapping) {
|
||||
newConfig.remoteConfig.directoryMapping = { ...config.remoteConfig.directoryMapping };
|
||||
}
|
||||
}
|
||||
|
||||
// Keep user passwords
|
||||
@@ -760,7 +939,15 @@ app.post('/api/transmission/remove', authenticateJWT, async (req, res) => {
|
||||
|
||||
app.post('/api/transmission/test', authenticateJWT, async (req, res) => {
|
||||
try {
|
||||
const { host, port, username, password } = req.body;
|
||||
const { host, port, username, password, path: rpcPath } = req.body;
|
||||
|
||||
// Debug info
|
||||
console.log('Testing Transmission connection with params:', {
|
||||
host: host || 'from config',
|
||||
port: port || 'from config',
|
||||
username: username ? 'provided' : 'from config',
|
||||
path: rpcPath || 'from config',
|
||||
});
|
||||
|
||||
// Create a temporary client for testing
|
||||
const testConfig = {
|
||||
@@ -769,26 +956,48 @@ app.post('/api/transmission/test', authenticateJWT, async (req, res) => {
|
||||
port: port || config.transmissionConfig.port,
|
||||
username: username || config.transmissionConfig.username,
|
||||
password: password || config.transmissionConfig.password,
|
||||
path: config.transmissionConfig.path
|
||||
path: rpcPath || config.transmissionConfig.path
|
||||
},
|
||||
// Also include remoteConfig to ensure proper remote handling
|
||||
remoteConfig: {
|
||||
// If host is provided and different from localhost, set isRemote to true
|
||||
isRemote: host && host !== 'localhost' ? true : config.remoteConfig?.isRemote || false,
|
||||
directoryMapping: config.remoteConfig?.directoryMapping || {}
|
||||
}
|
||||
};
|
||||
|
||||
const testClient = new TransmissionClient(testConfig);
|
||||
const status = await testClient.getStatus();
|
||||
// Log the actual test config (without password)
|
||||
console.log('Test configuration:', {
|
||||
host: testConfig.transmissionConfig.host,
|
||||
port: testConfig.transmissionConfig.port,
|
||||
path: testConfig.transmissionConfig.path,
|
||||
isRemote: testConfig.remoteConfig.isRemote
|
||||
});
|
||||
|
||||
if (status.connected) {
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Successfully connected to Transmission server',
|
||||
data: {
|
||||
version: status.version,
|
||||
rpcVersion: status.rpcVersion
|
||||
}
|
||||
});
|
||||
} else {
|
||||
res.status(400).json({
|
||||
try {
|
||||
const testClient = new TransmissionClient(testConfig);
|
||||
const status = await testClient.getStatus();
|
||||
|
||||
if (status.connected) {
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Successfully connected to Transmission server',
|
||||
data: {
|
||||
version: status.version,
|
||||
rpcVersion: status.rpcVersion
|
||||
}
|
||||
});
|
||||
} else {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: `Failed to connect: ${status.error}`
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error testing Transmission connection:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: `Failed to connect: ${status.error}`
|
||||
message: `Error testing connection: ${error.message}`
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -890,17 +1099,17 @@ async function getMediaLibrary(searchQuery) {
|
||||
|
||||
try {
|
||||
// Check if directory exists
|
||||
await fs.access(destinationPath);
|
||||
await fsPromises.access(destinationPath);
|
||||
|
||||
// Get directory listing
|
||||
const files = await fs.readdir(destinationPath, { withFileTypes: true });
|
||||
const files = await fsPromises.readdir(destinationPath, { withFileTypes: true });
|
||||
|
||||
// Process each file/directory
|
||||
for (const file of files) {
|
||||
const fullPath = path.join(destinationPath, file.name);
|
||||
|
||||
try {
|
||||
const stats = await fs.stat(fullPath);
|
||||
const stats = await fsPromises.stat(fullPath);
|
||||
|
||||
// Create an item object
|
||||
const item = {
|
||||
@@ -1041,6 +1250,247 @@ app.get('/api/auth/validate', authenticateJWT, (req, res) => {
|
||||
});
|
||||
});
|
||||
|
||||
// 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
|
||||
const uptimeSeconds = Math.floor(process.uptime());
|
||||
const hours = Math.floor(uptimeSeconds / 3600);
|
||||
const minutes = Math.floor((uptimeSeconds % 3600) / 60);
|
||||
const seconds = uptimeSeconds % 60;
|
||||
const uptime = `${hours}h ${minutes}m ${seconds}s`;
|
||||
|
||||
// Check transmission connection
|
||||
let transmissionStatus = 'Connected';
|
||||
try {
|
||||
const status = await transmissionClient.sessionGet();
|
||||
if (!status || (status.connected === false)) {
|
||||
transmissionStatus = 'Disconnected';
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Transmission connection error:', err);
|
||||
transmissionStatus = 'Disconnected';
|
||||
}
|
||||
|
||||
// Read version directly from package.json to ensure it's always current
|
||||
let currentVersion = APP_VERSION;
|
||||
try {
|
||||
const packageJsonPath = path.join(__dirname, 'package.json');
|
||||
const packageJsonContent = await fsPromises.readFile(packageJsonPath, 'utf8');
|
||||
const packageData = JSON.parse(packageJsonContent);
|
||||
currentVersion = packageData.version;
|
||||
|
||||
// Log the version for debugging
|
||||
console.log(`System status endpoint returning version: ${currentVersion}`);
|
||||
} catch (err) {
|
||||
console.error('Error reading package.json in status endpoint:', err);
|
||||
// Fall back to the cached APP_VERSION
|
||||
}
|
||||
|
||||
res.json({
|
||||
status: 'success',
|
||||
data: {
|
||||
version: currentVersion,
|
||||
uptime,
|
||||
transmissionStatus
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error getting system status:', error);
|
||||
res.status(500).json({
|
||||
status: 'error',
|
||||
message: 'Failed to get system status'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Check for updates
|
||||
app.get('/api/system/check-updates', authenticateJWT, async (req, res) => {
|
||||
try {
|
||||
// Check if git is available and if this is a git repository
|
||||
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 fsPromises.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 as a Git repository. Please use the bootstrap installer.'
|
||||
});
|
||||
}
|
||||
|
||||
// Get current version
|
||||
const currentVersion = APP_VERSION;
|
||||
|
||||
// Check for test mode flag which forces update availability for testing
|
||||
const testMode = req.query.test === 'true';
|
||||
|
||||
if (testMode) {
|
||||
// In test mode, always return that an update is available
|
||||
return res.json({
|
||||
status: 'success',
|
||||
data: {
|
||||
updateAvailable: true,
|
||||
currentVersion,
|
||||
remoteVersion: '2.1.0-test',
|
||||
commitsBehind: 1,
|
||||
testMode: true
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
// Normal mode - fetch latest updates without applying them
|
||||
await execAsync('git fetch');
|
||||
|
||||
// Check if we're behind the remote repository
|
||||
const { stdout } = await execAsync('git rev-list HEAD..origin/main --count');
|
||||
const behindCount = parseInt(stdout.trim());
|
||||
|
||||
if (behindCount > 0) {
|
||||
// Get the new version from the remote package.json
|
||||
const { stdout: remotePackageJson } = await execAsync('git show origin/main:package.json');
|
||||
const remotePackage = JSON.parse(remotePackageJson);
|
||||
const remoteVersion = remotePackage.version;
|
||||
|
||||
// Compare versions semantically - only consider it an update if remote version is higher
|
||||
const isNewerVersion = semver.gt(remoteVersion, currentVersion);
|
||||
|
||||
return res.json({
|
||||
status: 'success',
|
||||
data: {
|
||||
updateAvailable: isNewerVersion,
|
||||
currentVersion,
|
||||
remoteVersion,
|
||||
commitsBehind: behindCount,
|
||||
newerVersion: isNewerVersion
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return res.json({
|
||||
status: 'success',
|
||||
data: {
|
||||
updateAvailable: false,
|
||||
currentVersion
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (gitError) {
|
||||
console.error('Git error checking for updates:', gitError);
|
||||
|
||||
// Even if git commands fail, return a valid response with error information
|
||||
return res.json({
|
||||
status: 'error',
|
||||
message: 'Error checking git repository: ' + gitError.message,
|
||||
data: {
|
||||
updateAvailable: false,
|
||||
currentVersion,
|
||||
error: true,
|
||||
errorDetails: gitError.message
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error checking for updates:', error);
|
||||
res.status(500).json({
|
||||
status: 'error',
|
||||
message: 'Failed to check for updates: ' + error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Apply updates
|
||||
app.post('/api/system/update', authenticateJWT, async (req, res) => {
|
||||
try {
|
||||
// Check if git is available and if this is a git repository
|
||||
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 fsPromises.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 as a Git repository. Please use the bootstrap installer.'
|
||||
});
|
||||
}
|
||||
|
||||
// Run the update script
|
||||
const updateScriptPath = path.join(__dirname, 'scripts', 'update.sh');
|
||||
|
||||
// Make sure the update script is executable
|
||||
await execAsync(`chmod +x ${updateScriptPath}`);
|
||||
|
||||
// Execute the update script
|
||||
const { stdout, stderr } = await execAsync(updateScriptPath);
|
||||
|
||||
// If we get here, the update was successful
|
||||
// The service will be restarted by the update script
|
||||
res.json({
|
||||
status: 'success',
|
||||
message: 'Update applied successfully. The service will restart.',
|
||||
data: {
|
||||
output: stdout,
|
||||
errors: stderr
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error applying update:', error);
|
||||
res.status(500).json({
|
||||
status: 'error',
|
||||
message: 'Failed to apply update: ' + error.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Catch-all route for SPA
|
||||
app.get('*', (req, res) => {
|
||||
res.sendFile(path.join(__dirname, 'public', 'index.html'));
|
||||
|
48
temp_work/SUMMARY.md
Normal file
48
temp_work/SUMMARY.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# Changes for Moving Config File to /etc/transmission-rss-manager
|
||||
|
||||
## Overview
|
||||
This update moves the configuration file from the application directory to `/etc/transmission-rss-manager/config.json` for better system organization, while maintaining backward compatibility by checking the original location as a fallback.
|
||||
|
||||
## Changes Made
|
||||
|
||||
### 1. Server.js Updates
|
||||
- Changed default config location to `/etc/transmission-rss-manager/config.json`
|
||||
- Added fallback location (`path.join(__dirname, 'config.json')`) for backward compatibility
|
||||
- Enhanced `loadConfig()` function to try primary location first, then fallback location
|
||||
- Updated `saveConfig()` function to try saving to primary location first, then fallback
|
||||
- Added `installPath` property to configuration to store the application installation path for easier updates
|
||||
|
||||
### 2. Installer Updates
|
||||
- Added `CONFIG_DIR="/etc/transmission-rss-manager"` variable to config-module.sh
|
||||
- Updated `create_directories()` in dependencies-module.sh to create the config directory
|
||||
- Updated permission settings to ensure proper access to config directory
|
||||
- Added checks for the CONFIG_DIR variable to ensure it's set
|
||||
- Modified service-setup-module.sh to include CONFIG_DIR in the environment variables for the systemd service
|
||||
- Updated file-creator-module.sh to write the initial config.json to the new location
|
||||
|
||||
### 3. Documentation Updates
|
||||
- Updated README.md to reflect new config file location
|
||||
- Added entry to the changelog for version 2.0.3
|
||||
- Updated code examples in README
|
||||
- Bumped version from 2.0.2 to 2.0.3 in both README.md and package.json
|
||||
|
||||
## Technical Details
|
||||
|
||||
### Config File Loading Process
|
||||
1. Try to load config from primary location (`/etc/transmission-rss-manager/config.json`)
|
||||
2. If not found, try fallback location (`<install_dir>/config.json`)
|
||||
3. If neither exists, create a new config file at primary location
|
||||
4. If primary location is not writable, fall back to application directory
|
||||
|
||||
### Configuration Storage Logic
|
||||
- Primary storage location: `/etc/transmission-rss-manager/config.json`
|
||||
- Fallback location: `<install_dir>/config.json`
|
||||
- Automatic migration from fallback to primary when possible
|
||||
- Always try to write to primary first, then fallback if needed
|
||||
|
||||
## Benefits
|
||||
- More standard Linux configuration location in /etc
|
||||
- Easier to find and edit configuration
|
||||
- Clear separation between code and configuration
|
||||
- Maintains backward compatibility with existing installations
|
||||
- Simplified update process by storing installation path
|
131
temp_work/app-update.js
Normal file
131
temp_work/app-update.js
Normal file
@@ -0,0 +1,131 @@
|
||||
// Client-side JavaScript for system status and updates
|
||||
// Add this to the public/js/app.js file
|
||||
|
||||
// System status and updates functionality
|
||||
function initSystemStatus() {
|
||||
// Elements
|
||||
const versionElement = document.getElementById('system-version');
|
||||
const uptimeElement = document.getElementById('uptime');
|
||||
const transmissionStatusElement = document.getElementById('transmission-status');
|
||||
const updateStatusElement = document.getElementById('update-status');
|
||||
const updateAvailableDiv = document.getElementById('update-available');
|
||||
const updateButton = document.getElementById('btn-update-now');
|
||||
const refreshButton = document.getElementById('btn-refresh-status');
|
||||
|
||||
// Load system status
|
||||
function loadSystemStatus() {
|
||||
fetch('/api/system/status')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.status === 'success') {
|
||||
versionElement.textContent = data.data.version;
|
||||
uptimeElement.textContent = data.data.uptime;
|
||||
|
||||
// Update transmission status with icon
|
||||
if (data.data.transmissionStatus === 'Connected') {
|
||||
transmissionStatusElement.innerHTML = '<i class="fas fa-check-circle text-success"></i> Connected';
|
||||
} else {
|
||||
transmissionStatusElement.innerHTML = '<i class="fas fa-times-circle text-danger"></i> Disconnected';
|
||||
}
|
||||
} else {
|
||||
showToast('error', 'Failed to load system status');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching system status:', error);
|
||||
showToast('error', 'Failed to connect to server');
|
||||
});
|
||||
}
|
||||
|
||||
// Check for updates
|
||||
function checkForUpdates() {
|
||||
updateStatusElement.innerHTML = '<i class="fas fa-circle-notch fa-spin"></i> Checking...';
|
||||
updateAvailableDiv.classList.add('d-none');
|
||||
|
||||
fetch('/api/system/check-updates')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.status === 'success') {
|
||||
if (data.data.updateAvailable) {
|
||||
updateStatusElement.innerHTML = '<i class="fas fa-exclamation-circle text-warning"></i> Update available';
|
||||
updateAvailableDiv.classList.remove('d-none');
|
||||
updateAvailableDiv.querySelector('span').textContent =
|
||||
`A new version is available: ${data.data.currentVersion} → ${data.data.remoteVersion}`;
|
||||
} else {
|
||||
updateStatusElement.innerHTML = '<i class="fas fa-check-circle text-success"></i> Up to date';
|
||||
}
|
||||
} else {
|
||||
updateStatusElement.innerHTML = '<i class="fas fa-times-circle text-danger"></i> Check failed';
|
||||
showToast('error', data.message || 'Failed to check for updates');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error checking for updates:', error);
|
||||
updateStatusElement.innerHTML = '<i class="fas fa-times-circle text-danger"></i> Check failed';
|
||||
showToast('error', 'Failed to connect to server');
|
||||
});
|
||||
}
|
||||
|
||||
// Apply update
|
||||
function applyUpdate() {
|
||||
// Show confirmation dialog
|
||||
if (!confirm('Are you sure you want to update the application? The service will restart.')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Show loading state
|
||||
updateButton.disabled = true;
|
||||
updateButton.innerHTML = '<i class="fas fa-circle-notch fa-spin"></i> Updating...';
|
||||
showToast('info', 'Applying update. Please wait...');
|
||||
|
||||
fetch('/api/system/update', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.status === 'success') {
|
||||
showToast('success', 'Update applied successfully. The page will reload in 30 seconds.');
|
||||
// Set a timer to reload the page after the service has time to restart
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 30000);
|
||||
} else {
|
||||
updateButton.disabled = false;
|
||||
updateButton.innerHTML = '<i class="fas fa-download"></i> Update Now';
|
||||
showToast('error', data.message || 'Failed to apply update');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error applying update:', error);
|
||||
updateButton.disabled = false;
|
||||
updateButton.innerHTML = '<i class="fas fa-download"></i> Update Now';
|
||||
showToast('error', 'Failed to connect to server');
|
||||
});
|
||||
}
|
||||
|
||||
// Event listeners
|
||||
if (refreshButton) {
|
||||
refreshButton.addEventListener('click', () => {
|
||||
loadSystemStatus();
|
||||
checkForUpdates();
|
||||
});
|
||||
}
|
||||
|
||||
if (updateButton) {
|
||||
updateButton.addEventListener('click', applyUpdate);
|
||||
}
|
||||
|
||||
// Initialize
|
||||
loadSystemStatus();
|
||||
checkForUpdates();
|
||||
|
||||
// Set interval to refresh uptime every minute
|
||||
setInterval(loadSystemStatus, 60000);
|
||||
}
|
||||
|
||||
// Call this function from the main init function
|
||||
// Add this line to your document ready or DOMContentLoaded handler:
|
||||
// initSystemStatus();
|
72
temp_work/bootstrap-installer.sh
Normal file
72
temp_work/bootstrap-installer.sh
Normal file
@@ -0,0 +1,72 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Transmission RSS Manager - Bootstrap Installer
|
||||
# This script downloads the latest version from git and runs the setup
|
||||
|
||||
# Color and formatting
|
||||
GREEN='\033[0;32m'
|
||||
RED='\033[0;31m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
BOLD='\033[1m'
|
||||
|
||||
# Installation directory
|
||||
INSTALL_DIR="/opt/trans-install"
|
||||
REPO_URL="https://git.powerdata.dk/masterdraco/transmission-rss-manager.git"
|
||||
|
||||
# Check if running as root
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo -e "${RED}This script must be run as root or with sudo privileges.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Display welcome message
|
||||
echo -e "${GREEN}${BOLD}Transmission RSS Manager - Bootstrap Installer${NC}"
|
||||
echo -e "This script will install the latest version from the git repository."
|
||||
echo
|
||||
|
||||
# Check for git installation
|
||||
echo -e "${YELLOW}Checking dependencies...${NC}"
|
||||
if ! command -v git &> /dev/null; then
|
||||
echo -e "Git not found. Installing git..."
|
||||
apt-get update
|
||||
apt-get install -y git
|
||||
fi
|
||||
|
||||
# Check if installation directory exists
|
||||
if [ -d "$INSTALL_DIR" ]; then
|
||||
echo -e "${YELLOW}Installation directory already exists.${NC}"
|
||||
read -p "Do you want to remove it and perform a fresh install? (y/n): " choice
|
||||
if [[ "$choice" =~ ^[Yy]$ ]]; then
|
||||
echo "Removing existing installation..."
|
||||
rm -rf "$INSTALL_DIR"
|
||||
else
|
||||
echo -e "${RED}Installation aborted.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# Create installation directory
|
||||
echo -e "${YELLOW}Creating installation directory...${NC}"
|
||||
mkdir -p "$INSTALL_DIR"
|
||||
|
||||
# Clone the repository
|
||||
echo -e "${YELLOW}Cloning the latest version from git...${NC}"
|
||||
git clone "$REPO_URL" "$INSTALL_DIR"
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}Failed to clone the repository.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Run the main installer
|
||||
echo -e "${YELLOW}Running the main installer...${NC}"
|
||||
cd "$INSTALL_DIR"
|
||||
chmod +x main-installer.sh
|
||||
./main-installer.sh
|
||||
|
||||
# Installation complete
|
||||
echo -e "${GREEN}${BOLD}Bootstrap installation complete!${NC}"
|
||||
echo -e "Transmission RSS Manager has been installed in $INSTALL_DIR"
|
||||
echo -e "You can access the web interface at http://localhost:3000"
|
||||
echo
|
||||
echo -e "To update in the future, use the update button in the System Status section of the web interface."
|
374
temp_work/config-module-updated.sh
Normal file
374
temp_work/config-module-updated.sh
Normal file
@@ -0,0 +1,374 @@
|
||||
#!/bin/bash
|
||||
# Configuration module for Transmission RSS Manager Installation
|
||||
|
||||
# Configuration variables with defaults
|
||||
INSTALL_DIR="/opt/transmission-rss-manager"
|
||||
SERVICE_NAME="transmission-rss-manager"
|
||||
PORT=3000
|
||||
|
||||
# Get default user safely - avoid using root
|
||||
get_default_user() {
|
||||
local default_user
|
||||
|
||||
# Try logname first to get the user who invoked sudo
|
||||
if command -v logname &> /dev/null; then
|
||||
default_user=$(logname 2>/dev/null)
|
||||
fi
|
||||
|
||||
# If logname failed, try SUDO_USER
|
||||
if [ -z "$default_user" ] && [ -n "$SUDO_USER" ]; then
|
||||
default_user="$SUDO_USER"
|
||||
fi
|
||||
|
||||
# Fallback to current user if both methods failed
|
||||
if [ -z "$default_user" ]; then
|
||||
default_user="$(whoami)"
|
||||
fi
|
||||
|
||||
# Ensure the user is not root
|
||||
if [ "$default_user" = "root" ]; then
|
||||
echo "nobody"
|
||||
else
|
||||
echo "$default_user"
|
||||
fi
|
||||
}
|
||||
|
||||
# Initialize default user
|
||||
USER=$(get_default_user)
|
||||
|
||||
# Transmission configuration variables
|
||||
TRANSMISSION_REMOTE=false
|
||||
TRANSMISSION_HOST="localhost"
|
||||
TRANSMISSION_PORT=9091
|
||||
TRANSMISSION_USER=""
|
||||
TRANSMISSION_PASS=""
|
||||
TRANSMISSION_RPC_PATH="/transmission/rpc"
|
||||
TRANSMISSION_DOWNLOAD_DIR="/var/lib/transmission-daemon/downloads"
|
||||
TRANSMISSION_DIR_MAPPING="{}"
|
||||
|
||||
# Media path defaults
|
||||
MEDIA_DIR="/mnt/media"
|
||||
ENABLE_BOOK_SORTING=true
|
||||
|
||||
# Helper function to validate port number
|
||||
validate_port() {
|
||||
local port="$1"
|
||||
if [[ "$port" =~ ^[0-9]+$ ]] && [ "$port" -ge 1 ] && [ "$port" -le 65535 ]; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Helper function to validate URL hostname
|
||||
validate_hostname() {
|
||||
local hostname="$1"
|
||||
if [[ "$hostname" =~ ^[a-zA-Z0-9]([a-zA-Z0-9\-\.]+[a-zA-Z0-9])?$ ]]; then
|
||||
return 0
|
||||
elif [[ "$hostname" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
function gather_configuration() {
|
||||
log "INFO" "Starting configuration gathering"
|
||||
echo -e "${BOLD}Installation Configuration:${NC}"
|
||||
echo -e "Please provide the following configuration parameters:"
|
||||
echo
|
||||
|
||||
read -p "Installation directory [$INSTALL_DIR]: " input_install_dir
|
||||
if [ -n "$input_install_dir" ]; then
|
||||
# Validate installation directory
|
||||
if [[ ! "$input_install_dir" =~ ^/ ]]; then
|
||||
log "WARN" "Installation directory must be an absolute path. Using default."
|
||||
else
|
||||
INSTALL_DIR="$input_install_dir"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Using fixed port 3000 to avoid permission issues with ports below 1024
|
||||
log "INFO" "Using port 3000 for the web interface"
|
||||
log "INFO" "This is to avoid permission issues with ports below 1024 (like port 80)"
|
||||
PORT=3000
|
||||
|
||||
# Get user
|
||||
read -p "Run as user [$USER]: " input_user
|
||||
if [ -n "$input_user" ]; then
|
||||
# Check if user exists
|
||||
if id "$input_user" &>/dev/null; then
|
||||
USER="$input_user"
|
||||
else
|
||||
log "WARN" "User $input_user does not exist. Using $USER instead."
|
||||
fi
|
||||
fi
|
||||
|
||||
echo
|
||||
echo -e "${BOLD}Transmission Configuration:${NC}"
|
||||
echo -e "Configure connection to your Transmission client:"
|
||||
echo
|
||||
|
||||
# Ask if Transmission is remote
|
||||
read -p "Is Transmission running on a remote server? (y/n) [n]: " input_remote
|
||||
if [[ $input_remote =~ ^[Yy]$ ]]; then
|
||||
TRANSMISSION_REMOTE=true
|
||||
|
||||
# Get and validate hostname
|
||||
while true; do
|
||||
read -p "Remote Transmission host [localhost]: " input_trans_host
|
||||
if [ -z "$input_trans_host" ]; then
|
||||
break
|
||||
elif validate_hostname "$input_trans_host"; then
|
||||
TRANSMISSION_HOST="$input_trans_host"
|
||||
break
|
||||
else
|
||||
log "WARN" "Invalid hostname format."
|
||||
fi
|
||||
done
|
||||
|
||||
# Get and validate port
|
||||
while true; do
|
||||
read -p "Remote Transmission port [9091]: " input_trans_port
|
||||
if [ -z "$input_trans_port" ]; then
|
||||
break
|
||||
elif validate_port "$input_trans_port"; then
|
||||
TRANSMISSION_PORT="$input_trans_port"
|
||||
break
|
||||
else
|
||||
log "WARN" "Invalid port number. Port must be between 1 and 65535."
|
||||
fi
|
||||
done
|
||||
|
||||
# Get credentials
|
||||
read -p "Remote Transmission username []: " input_trans_user
|
||||
TRANSMISSION_USER=${input_trans_user:-$TRANSMISSION_USER}
|
||||
|
||||
# Use read -s for password to avoid showing it on screen
|
||||
read -s -p "Remote Transmission password []: " input_trans_pass
|
||||
echo # Add a newline after the password input
|
||||
if [ -n "$input_trans_pass" ]; then
|
||||
# TODO: In a production environment, consider encrypting this password
|
||||
TRANSMISSION_PASS="$input_trans_pass"
|
||||
fi
|
||||
|
||||
read -p "Remote Transmission RPC path [/transmission/rpc]: " input_trans_path
|
||||
if [ -n "$input_trans_path" ]; then
|
||||
# Ensure path starts with / for consistency
|
||||
if [[ ! "$input_trans_path" =~ ^/ ]]; then
|
||||
input_trans_path="/$input_trans_path"
|
||||
fi
|
||||
TRANSMISSION_RPC_PATH="$input_trans_path"
|
||||
fi
|
||||
|
||||
# Configure directory mapping for remote setup
|
||||
echo
|
||||
echo -e "${YELLOW}Directory Mapping Configuration${NC}"
|
||||
echo -e "When using a remote Transmission server, you need to map paths between servers."
|
||||
echo -e "For each directory on the remote server, specify the corresponding local directory."
|
||||
echo
|
||||
|
||||
# Get remote download directory
|
||||
read -p "Remote Transmission download directory: " REMOTE_DOWNLOAD_DIR
|
||||
REMOTE_DOWNLOAD_DIR=${REMOTE_DOWNLOAD_DIR:-"/var/lib/transmission-daemon/downloads"}
|
||||
|
||||
# Get local directory that corresponds to remote download directory
|
||||
read -p "Local directory that corresponds to the remote download directory: " LOCAL_DOWNLOAD_DIR
|
||||
LOCAL_DOWNLOAD_DIR=${LOCAL_DOWNLOAD_DIR:-"/mnt/transmission-downloads"}
|
||||
|
||||
# Create mapping JSON - use proper JSON escaping for directory paths
|
||||
REMOTE_DOWNLOAD_DIR_ESCAPED=$(echo "$REMOTE_DOWNLOAD_DIR" | sed 's/\\/\\\\/g' | sed 's/"/\\"/g')
|
||||
LOCAL_DOWNLOAD_DIR_ESCAPED=$(echo "$LOCAL_DOWNLOAD_DIR" | sed 's/\\/\\\\/g' | sed 's/"/\\"/g')
|
||||
|
||||
TRANSMISSION_DIR_MAPPING="{\"$REMOTE_DOWNLOAD_DIR_ESCAPED\": \"$LOCAL_DOWNLOAD_DIR_ESCAPED\"}"
|
||||
|
||||
# Create the local directory
|
||||
if ! mkdir -p "$LOCAL_DOWNLOAD_DIR"; then
|
||||
log "ERROR" "Failed to create local download directory: $LOCAL_DOWNLOAD_DIR"
|
||||
else
|
||||
if ! chown -R "$USER:$USER" "$LOCAL_DOWNLOAD_DIR"; then
|
||||
log "ERROR" "Failed to set permissions on local download directory: $LOCAL_DOWNLOAD_DIR"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Ask if want to add more mappings
|
||||
while true; do
|
||||
read -p "Add another directory mapping? (y/n) [n]: " add_another
|
||||
if [[ ! $add_another =~ ^[Yy]$ ]]; then
|
||||
break
|
||||
fi
|
||||
|
||||
read -p "Remote directory path: " remote_dir
|
||||
read -p "Corresponding local directory path: " local_dir
|
||||
|
||||
if [ -n "$remote_dir" ] && [ -n "$local_dir" ]; then
|
||||
# Escape directory paths for JSON
|
||||
remote_dir_escaped=$(echo "$remote_dir" | sed 's/\\/\\\\/g' | sed 's/"/\\"/g')
|
||||
local_dir_escaped=$(echo "$local_dir" | sed 's/\\/\\\\/g' | sed 's/"/\\"/g')
|
||||
|
||||
# Update mapping JSON (proper JSON manipulation)
|
||||
# Remove the closing brace, add a comma and the new mapping, then close with brace
|
||||
TRANSMISSION_DIR_MAPPING="${TRANSMISSION_DIR_MAPPING%\}}, \"$remote_dir_escaped\": \"$local_dir_escaped\"}"
|
||||
|
||||
# Create the local directory
|
||||
if ! mkdir -p "$local_dir"; then
|
||||
log "ERROR" "Failed to create directory: $local_dir"
|
||||
else
|
||||
if ! chown -R "$USER:$USER" "$local_dir"; then
|
||||
log "WARN" "Failed to set permissions on directory: $local_dir"
|
||||
fi
|
||||
log "INFO" "Mapping added: $remote_dir → $local_dir"
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Set Transmission download dir for configuration
|
||||
TRANSMISSION_DOWNLOAD_DIR=$REMOTE_DOWNLOAD_DIR
|
||||
else
|
||||
# Local Transmission selected
|
||||
echo -e "${YELLOW}You've selected to use a local Transmission installation.${NC}"
|
||||
|
||||
# Check if Transmission is already installed
|
||||
if command -v transmission-daemon &> /dev/null; then
|
||||
echo -e "${GREEN}Transmission is already installed on this system.${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}NOTE: Transmission does not appear to be installed on this system.${NC}"
|
||||
echo -e "${YELLOW}You will be prompted to install it during the dependency installation step.${NC}"
|
||||
fi
|
||||
|
||||
# Get and validate port
|
||||
while true; do
|
||||
read -p "Local Transmission port [9091]: " input_trans_port
|
||||
if [ -z "$input_trans_port" ]; then
|
||||
break
|
||||
elif validate_port "$input_trans_port"; then
|
||||
TRANSMISSION_PORT="$input_trans_port"
|
||||
break
|
||||
else
|
||||
log "WARN" "Invalid port number. Port must be between 1 and 65535."
|
||||
fi
|
||||
done
|
||||
|
||||
# Get credentials if any
|
||||
read -p "Local Transmission username (leave empty if authentication is disabled) []: " input_trans_user
|
||||
TRANSMISSION_USER=${input_trans_user:-$TRANSMISSION_USER}
|
||||
|
||||
if [ -n "$input_trans_user" ]; then
|
||||
# Use read -s for password to hide it
|
||||
read -s -p "Local Transmission password []: " input_trans_pass
|
||||
echo # Add a newline after the password input
|
||||
if [ -n "$input_trans_pass" ]; then
|
||||
TRANSMISSION_PASS="$input_trans_pass"
|
||||
fi
|
||||
fi
|
||||
|
||||
read -p "Transmission download directory [/var/lib/transmission-daemon/downloads]: " input_trans_dir
|
||||
if [ -n "$input_trans_dir" ]; then
|
||||
if [[ ! "$input_trans_dir" =~ ^/ ]]; then
|
||||
log "WARN" "Download directory must be an absolute path. Using default."
|
||||
else
|
||||
TRANSMISSION_DOWNLOAD_DIR="$input_trans_dir"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
echo
|
||||
echo -e "${BOLD}Media Destination Configuration:${NC}"
|
||||
|
||||
read -p "Media destination base directory [/mnt/media]: " input_media_dir
|
||||
if [ -n "$input_media_dir" ]; then
|
||||
if [[ ! "$input_media_dir" =~ ^/ ]]; then
|
||||
log "WARN" "Media directory must be an absolute path. Using default."
|
||||
else
|
||||
MEDIA_DIR="$input_media_dir"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Ask about enabling book/magazine sorting
|
||||
echo
|
||||
echo -e "${BOLD}Content Type Configuration:${NC}"
|
||||
read -p "Enable book and magazine sorting? (y/n) [y]: " input_book_sorting
|
||||
if [[ $input_book_sorting =~ ^[Nn]$ ]]; then
|
||||
ENABLE_BOOK_SORTING=false
|
||||
else
|
||||
ENABLE_BOOK_SORTING=true
|
||||
fi
|
||||
|
||||
# Security configuration
|
||||
echo
|
||||
echo -e "${BOLD}Security Configuration:${NC}"
|
||||
|
||||
# Ask about enabling authentication
|
||||
read -p "Enable authentication? (y/n) [n]: " input_auth_enabled
|
||||
AUTH_ENABLED=false
|
||||
ADMIN_USERNAME=""
|
||||
ADMIN_PASSWORD=""
|
||||
|
||||
if [[ $input_auth_enabled =~ ^[Yy]$ ]]; then
|
||||
AUTH_ENABLED=true
|
||||
|
||||
# Get admin username and password
|
||||
read -p "Admin username [admin]: " input_admin_username
|
||||
ADMIN_USERNAME=${input_admin_username:-"admin"}
|
||||
|
||||
# Use read -s for password to avoid showing it on screen
|
||||
read -s -p "Admin password: " input_admin_password
|
||||
echo # Add a newline after the password input
|
||||
|
||||
if [ -z "$input_admin_password" ]; then
|
||||
# Generate a random password if none provided
|
||||
ADMIN_PASSWORD=$(openssl rand -base64 12)
|
||||
echo -e "${YELLOW}Generated random admin password: $ADMIN_PASSWORD${NC}"
|
||||
echo -e "${YELLOW}Please save this password somewhere safe!${NC}"
|
||||
else
|
||||
ADMIN_PASSWORD="$input_admin_password"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Ask about enabling HTTPS
|
||||
read -p "Enable HTTPS? (requires SSL certificate) (y/n) [n]: " input_https_enabled
|
||||
HTTPS_ENABLED=false
|
||||
SSL_CERT_PATH=""
|
||||
SSL_KEY_PATH=""
|
||||
|
||||
if [[ $input_https_enabled =~ ^[Yy]$ ]]; then
|
||||
HTTPS_ENABLED=true
|
||||
|
||||
# Get SSL certificate paths
|
||||
read -p "SSL certificate path: " input_ssl_cert_path
|
||||
if [ -n "$input_ssl_cert_path" ]; then
|
||||
# Check if file exists
|
||||
if [ -f "$input_ssl_cert_path" ]; then
|
||||
SSL_CERT_PATH="$input_ssl_cert_path"
|
||||
else
|
||||
log "WARN" "SSL certificate file not found. HTTPS will be disabled."
|
||||
HTTPS_ENABLED=false
|
||||
fi
|
||||
else
|
||||
log "WARN" "SSL certificate path not provided. HTTPS will be disabled."
|
||||
HTTPS_ENABLED=false
|
||||
fi
|
||||
|
||||
# Only ask for key if cert was found
|
||||
if [ "$HTTPS_ENABLED" = true ]; then
|
||||
read -p "SSL key path: " input_ssl_key_path
|
||||
if [ -n "$input_ssl_key_path" ]; then
|
||||
# Check if file exists
|
||||
if [ -f "$input_ssl_key_path" ]; then
|
||||
SSL_KEY_PATH="$input_ssl_key_path"
|
||||
else
|
||||
log "WARN" "SSL key file not found. HTTPS will be disabled."
|
||||
HTTPS_ENABLED=false
|
||||
fi
|
||||
else
|
||||
log "WARN" "SSL key path not provided. HTTPS will be disabled."
|
||||
HTTPS_ENABLED=false
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
echo
|
||||
log "INFO" "Configuration gathering complete"
|
||||
echo -e "${GREEN}Configuration complete!${NC}"
|
||||
echo
|
||||
}
|
184
temp_work/dependencies-module-updated.sh
Normal file
184
temp_work/dependencies-module-updated.sh
Normal file
@@ -0,0 +1,184 @@
|
||||
#!/bin/bash
|
||||
# Dependencies module for Transmission RSS Manager Installation
|
||||
|
||||
function install_dependencies() {
|
||||
log "INFO" "Installing dependencies..."
|
||||
|
||||
# Check for package manager
|
||||
if command -v apt-get &> /dev/null; then
|
||||
# Update package index
|
||||
apt-get update
|
||||
|
||||
# Install Node.js and npm if not already installed
|
||||
if ! command_exists node; then
|
||||
log "INFO" "Installing Node.js and npm..."
|
||||
apt-get install -y ca-certificates curl gnupg
|
||||
mkdir -p /etc/apt/keyrings
|
||||
|
||||
# Check if download succeeds
|
||||
if ! curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg; then
|
||||
log "ERROR" "Failed to download Node.js GPG key"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_18.x nodistro main" > /etc/apt/sources.list.d/nodesource.list
|
||||
|
||||
# Update again after adding repo
|
||||
apt-get update
|
||||
|
||||
# Install nodejs
|
||||
if ! apt-get install -y nodejs; then
|
||||
log "ERROR" "Failed to install Node.js"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
log "INFO" "Node.js is already installed."
|
||||
fi
|
||||
|
||||
# Check if we need to install Transmission (only if local transmission was selected and not remote)
|
||||
if [ "$TRANSMISSION_REMOTE" = false ] && ([ "$TRANSMISSION_HOST" = "localhost" ] || [ "$TRANSMISSION_HOST" = "127.0.0.1" ]); then
|
||||
if ! command_exists transmission-daemon; then
|
||||
log "INFO" "Local Transmission installation selected, but transmission-daemon is not installed."
|
||||
read -p "Would you like to install Transmission now? (y/n): " install_transmission
|
||||
|
||||
if [[ "$install_transmission" =~ ^[Yy]$ ]]; then
|
||||
log "INFO" "Installing Transmission..."
|
||||
if ! apt-get install -y transmission-daemon; then
|
||||
log "ERROR" "Failed to install Transmission"
|
||||
log "WARN" "You will need to install Transmission manually before using this application."
|
||||
else
|
||||
# Stop transmission-daemon to allow configuration changes
|
||||
systemctl stop transmission-daemon
|
||||
|
||||
# Set default settings
|
||||
TRANSMISSION_SETTINGS_DIR="/var/lib/transmission-daemon/info"
|
||||
if [ -f "$TRANSMISSION_SETTINGS_DIR/settings.json" ]; then
|
||||
# Backup original settings
|
||||
cp "$TRANSMISSION_SETTINGS_DIR/settings.json" "$TRANSMISSION_SETTINGS_DIR/settings.json.bak"
|
||||
|
||||
# Update RPC settings to allow our app to connect
|
||||
sed -i 's/"rpc-authentication-required": true,/"rpc-authentication-required": false,/g' "$TRANSMISSION_SETTINGS_DIR/settings.json"
|
||||
sed -i 's/"rpc-whitelist-enabled": true,/"rpc-whitelist-enabled": false,/g' "$TRANSMISSION_SETTINGS_DIR/settings.json"
|
||||
|
||||
log "INFO" "Transmission has been configured for local access."
|
||||
else
|
||||
log "WARN" "Could not find Transmission settings file. You may need to configure Transmission manually."
|
||||
fi
|
||||
|
||||
# Start transmission-daemon
|
||||
systemctl start transmission-daemon
|
||||
log "INFO" "Transmission has been installed and started."
|
||||
fi
|
||||
else
|
||||
log "WARN" "Transmission installation skipped. You will need to install it manually."
|
||||
fi
|
||||
else
|
||||
log "INFO" "Transmission is already installed."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Install additional dependencies
|
||||
log "INFO" "Installing additional dependencies..."
|
||||
# Try to install unrar-free if unrar is not available
|
||||
if ! apt-get install -y unrar 2>/dev/null; then
|
||||
log "INFO" "unrar not available, trying unrar-free instead..."
|
||||
apt-get install -y unrar-free
|
||||
fi
|
||||
|
||||
# Install other dependencies
|
||||
apt-get install -y unzip p7zip-full
|
||||
|
||||
# Try to install nginx
|
||||
apt-get install -y nginx || log "WARN" "Nginx installation failed, web interface may not be accessible"
|
||||
else
|
||||
log "ERROR" "This installer requires apt-get package manager"
|
||||
log "INFO" "Please install the following dependencies manually:"
|
||||
log "INFO" "- Node.js (v18.x)"
|
||||
log "INFO" "- npm"
|
||||
log "INFO" "- unrar"
|
||||
log "INFO" "- unzip"
|
||||
log "INFO" "- p7zip-full"
|
||||
log "INFO" "- nginx"
|
||||
if [ "$TRANSMISSION_REMOTE" = false ] && ([ "$TRANSMISSION_HOST" = "localhost" ] || [ "$TRANSMISSION_HOST" = "127.0.0.1" ]); then
|
||||
log "INFO" "- transmission-daemon"
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if all dependencies were installed successfully
|
||||
local dependencies=("node" "npm" "unzip" "nginx")
|
||||
local missing_deps=()
|
||||
|
||||
# Add transmission to dependencies check if local installation was selected and not remote
|
||||
if [ "$TRANSMISSION_REMOTE" = false ] && ([ "$TRANSMISSION_HOST" = "localhost" ] || [ "$TRANSMISSION_HOST" = "127.0.0.1" ]); then
|
||||
dependencies+=("transmission-daemon")
|
||||
fi
|
||||
|
||||
for dep in "${dependencies[@]}"; do
|
||||
if ! command_exists "$dep"; then
|
||||
missing_deps+=("$dep")
|
||||
fi
|
||||
done
|
||||
|
||||
# Check for either unrar or unrar-free
|
||||
if ! command_exists "unrar" && ! command_exists "unrar-free"; then
|
||||
missing_deps+=("unrar")
|
||||
fi
|
||||
|
||||
# Check for either 7z or 7za (from p7zip-full)
|
||||
if ! command_exists "7z" && ! command_exists "7za"; then
|
||||
missing_deps+=("p7zip")
|
||||
fi
|
||||
|
||||
if [ ${#missing_deps[@]} -eq 0 ]; then
|
||||
log "INFO" "All dependencies installed successfully."
|
||||
else
|
||||
log "ERROR" "Failed to install some dependencies: ${missing_deps[*]}"
|
||||
log "WARN" "Please install them manually and rerun this script."
|
||||
|
||||
# More helpful information based on which deps are missing
|
||||
if [[ " ${missing_deps[*]} " =~ " node " ]]; then
|
||||
log "INFO" "To install Node.js manually, visit: https://nodejs.org/en/download/"
|
||||
fi
|
||||
|
||||
if [[ " ${missing_deps[*]} " =~ " nginx " ]]; then
|
||||
log "INFO" "To install nginx manually: sudo apt-get install nginx"
|
||||
fi
|
||||
|
||||
if [[ " ${missing_deps[*]} " =~ " transmission-daemon " ]]; then
|
||||
log "INFO" "To install Transmission manually: sudo apt-get install transmission-daemon"
|
||||
log "INFO" "After installation, you may need to configure it by editing /var/lib/transmission-daemon/info/settings.json"
|
||||
fi
|
||||
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
function create_directories() {
|
||||
log "INFO" "Creating installation directories..."
|
||||
|
||||
# Check if INSTALL_DIR is defined
|
||||
if [ -z "$INSTALL_DIR" ]; then
|
||||
log "ERROR" "INSTALL_DIR is not defined"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Create directories and check for errors
|
||||
DIRECTORIES=(
|
||||
"$INSTALL_DIR"
|
||||
"$INSTALL_DIR/logs"
|
||||
"$INSTALL_DIR/public/js"
|
||||
"$INSTALL_DIR/public/css"
|
||||
"$INSTALL_DIR/modules"
|
||||
"$INSTALL_DIR/data"
|
||||
)
|
||||
|
||||
for dir in "${DIRECTORIES[@]}"; do
|
||||
if ! mkdir -p "$dir"; then
|
||||
log "ERROR" "Failed to create directory: $dir"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
log "INFO" "Directories created successfully."
|
||||
}
|
241
temp_work/main-installer-modified.sh
Normal file
241
temp_work/main-installer-modified.sh
Normal file
@@ -0,0 +1,241 @@
|
||||
#!/bin/bash
|
||||
# Transmission RSS Manager Modular Installer
|
||||
# Modified to work with the git-based approach
|
||||
|
||||
# Set script to exit on error
|
||||
set -e
|
||||
|
||||
# Text formatting
|
||||
BOLD='\033[1m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[0;33m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Print header
|
||||
echo -e "${BOLD}==================================================${NC}"
|
||||
echo -e "${BOLD} Transmission RSS Manager Installer ${NC}"
|
||||
echo -e "${BOLD} Version 1.2.0 - Git Edition ${NC}"
|
||||
echo -e "${BOLD}==================================================${NC}"
|
||||
echo
|
||||
|
||||
# Check if script is run with sudo
|
||||
if [ "$EUID" -ne 0 ]; then
|
||||
echo -e "${RED}Please run as root (use sudo)${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get current directory
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
|
||||
|
||||
# Check for installation type
|
||||
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}"
|
||||
fi
|
||||
export IS_UPDATE
|
||||
|
||||
# Check if required module files exist
|
||||
REQUIRED_MODULES=(
|
||||
"${SCRIPT_DIR}/modules/config-module.sh"
|
||||
"${SCRIPT_DIR}/modules/utils-module.sh"
|
||||
"${SCRIPT_DIR}/modules/dependencies-module.sh"
|
||||
"${SCRIPT_DIR}/modules/service-setup-module.sh"
|
||||
)
|
||||
|
||||
for module in "${REQUIRED_MODULES[@]}"; do
|
||||
if [ ! -f "$module" ]; then
|
||||
echo -e "${RED}Error: Required module file not found: $module${NC}"
|
||||
echo -e "${YELLOW}The module files should be included in the git repository.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
# Source the module files
|
||||
source "${SCRIPT_DIR}/modules/utils-module.sh" # Load utilities first for logging
|
||||
source "${SCRIPT_DIR}/modules/config-module.sh"
|
||||
source "${SCRIPT_DIR}/modules/dependencies-module.sh"
|
||||
source "${SCRIPT_DIR}/modules/service-setup-module.sh"
|
||||
|
||||
# Function to handle cleanup on error
|
||||
function cleanup_on_error() {
|
||||
log "ERROR" "Installation failed: $1"
|
||||
log "INFO" "Cleaning up..."
|
||||
|
||||
# Add any cleanup steps here if needed
|
||||
|
||||
log "INFO" "You can try running the installer again after fixing the issues."
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Set trap for error handling
|
||||
trap 'cleanup_on_error "$BASH_COMMAND"' ERR
|
||||
|
||||
# Execute the installation steps in sequence
|
||||
log "INFO" "Starting installation process..."
|
||||
|
||||
# Step 1: Gather configuration from user
|
||||
log "INFO" "Gathering configuration..."
|
||||
gather_configuration || {
|
||||
log "ERROR" "Configuration gathering failed"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Step 2: Install dependencies
|
||||
log "INFO" "Installing dependencies..."
|
||||
install_dependencies || {
|
||||
log "ERROR" "Dependency installation failed"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Step 3: Create installation directories
|
||||
log "INFO" "Creating directories..."
|
||||
create_directories || {
|
||||
log "ERROR" "Directory creation failed"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Step 4: Create configuration files only (no application files since they're from git)
|
||||
log "INFO" "Creating configuration files..."
|
||||
create_config_files || {
|
||||
log "ERROR" "Configuration file creation failed"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Step 5: Create service files and install the service
|
||||
log "INFO" "Setting up service..."
|
||||
setup_service || {
|
||||
log "ERROR" "Service setup failed"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Step 6: Install npm dependencies
|
||||
log "INFO" "Installing npm dependencies..."
|
||||
cd "$SCRIPT_DIR"
|
||||
npm install || {
|
||||
log "ERROR" "NPM installation failed"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Step 7: Set up update script
|
||||
log "INFO" "Setting up update script..."
|
||||
mkdir -p "${SCRIPT_DIR}/scripts"
|
||||
cp "${SCRIPT_DIR}/scripts/update.sh" "${SCRIPT_DIR}/scripts/update.sh" 2>/dev/null || {
|
||||
# If copy fails, it probably doesn't exist, so we'll create it
|
||||
cat > "${SCRIPT_DIR}/scripts/update.sh" << 'EOL'
|
||||
#!/bin/bash
|
||||
|
||||
# Transmission RSS Manager - Update Script
|
||||
# This script pulls the latest version from git and runs necessary updates
|
||||
|
||||
# Color and formatting
|
||||
GREEN='\033[0;32m'
|
||||
RED='\033[0;31m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
BOLD='\033[1m'
|
||||
|
||||
# Installation directory (should be current directory)
|
||||
INSTALL_DIR=$(pwd)
|
||||
|
||||
# Check if we're in the right directory
|
||||
if [ ! -f "$INSTALL_DIR/package.json" ] || [ ! -d "$INSTALL_DIR/modules" ]; then
|
||||
echo -e "${RED}Error: This script must be run from the installation directory.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get the current version
|
||||
CURRENT_VERSION=$(grep -oP '"version": "\K[^"]+' package.json)
|
||||
echo -e "${YELLOW}Current version: ${BOLD}$CURRENT_VERSION${NC}"
|
||||
|
||||
# Check for git repository
|
||||
if [ ! -d ".git" ]; then
|
||||
echo -e "${RED}Error: This installation was not set up using git.${NC}"
|
||||
echo -e "Please use the bootstrap installer to perform a fresh installation."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Stash any local changes
|
||||
echo -e "${YELLOW}Backing up any local configuration changes...${NC}"
|
||||
git stash -q
|
||||
|
||||
# Pull the latest changes
|
||||
echo -e "${YELLOW}Pulling latest updates from git...${NC}"
|
||||
git pull
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}Failed to pull updates. Restoring original state...${NC}"
|
||||
git stash pop -q
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get the new version
|
||||
NEW_VERSION=$(grep -oP '"version": "\K[^"]+' package.json)
|
||||
echo -e "${GREEN}New version: ${BOLD}$NEW_VERSION${NC}"
|
||||
|
||||
# Check if update is needed
|
||||
if [ "$CURRENT_VERSION" == "$NEW_VERSION" ]; then
|
||||
echo -e "${GREEN}You already have the latest version.${NC}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Install any new npm dependencies
|
||||
echo -e "${YELLOW}Installing dependencies...${NC}"
|
||||
npm install
|
||||
|
||||
# Apply any local configuration changes
|
||||
if git stash list | grep -q "stash@{0}"; then
|
||||
echo -e "${YELLOW}Restoring local configuration changes...${NC}"
|
||||
git stash pop -q
|
||||
# Handle conflicts if any
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}There were conflicts when restoring your configuration.${NC}"
|
||||
echo -e "Please check the files and resolve conflicts manually."
|
||||
echo -e "Your original configuration is saved in .git/refs/stash"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Restart the service
|
||||
echo -e "${YELLOW}Restarting service...${NC}"
|
||||
if command -v systemctl &> /dev/null; then
|
||||
sudo systemctl restart transmission-rss-manager
|
||||
else
|
||||
echo -e "${RED}Could not restart service automatically.${NC}"
|
||||
echo -e "Please restart the service manually."
|
||||
fi
|
||||
|
||||
# Update complete
|
||||
echo -e "${GREEN}${BOLD}Update complete!${NC}"
|
||||
echo -e "Updated from version $CURRENT_VERSION to $NEW_VERSION"
|
||||
echo -e "Changes will take effect immediately."
|
||||
EOL
|
||||
|
||||
chmod +x "${SCRIPT_DIR}/scripts/update.sh"
|
||||
}
|
||||
|
||||
# Step 8: Final setup and permissions
|
||||
log "INFO" "Finalizing setup..."
|
||||
finalize_setup || {
|
||||
log "ERROR" "Setup finalization failed"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Installation complete
|
||||
echo
|
||||
echo -e "${BOLD}${GREEN}==================================================${NC}"
|
||||
echo -e "${BOLD}${GREEN} Installation Complete! ${NC}"
|
||||
echo -e "${BOLD}${GREEN}==================================================${NC}"
|
||||
echo -e "You can access the web interface at: ${BOLD}http://localhost:$PORT${NC} or ${BOLD}http://your-server-ip:$PORT${NC}"
|
||||
echo -e "You may need to configure your firewall to allow access to port $PORT"
|
||||
echo
|
||||
echo -e "${BOLD}Useful Commands:${NC}"
|
||||
echo -e " To check the service status: ${YELLOW}systemctl status $SERVICE_NAME${NC}"
|
||||
echo -e " To view logs: ${YELLOW}journalctl -u $SERVICE_NAME${NC}"
|
||||
echo -e " To restart the service: ${YELLOW}systemctl restart $SERVICE_NAME${NC}"
|
||||
echo -e " To update the application: ${YELLOW}Use the Update button in the System Status section${NC}"
|
||||
echo
|
||||
echo -e "Thank you for installing Transmission RSS Manager!"
|
||||
echo -e "${BOLD}==================================================${NC}"
|
84
temp_work/scripts/update.sh
Normal file
84
temp_work/scripts/update.sh
Normal file
@@ -0,0 +1,84 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Transmission RSS Manager - Update Script
|
||||
# This script pulls the latest version from git and runs necessary updates
|
||||
|
||||
# Color and formatting
|
||||
GREEN='\033[0;32m'
|
||||
RED='\033[0;31m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
BOLD='\033[1m'
|
||||
|
||||
# Installation directory (should be current directory)
|
||||
INSTALL_DIR=$(pwd)
|
||||
|
||||
# Check if we're in the right directory
|
||||
if [ ! -f "$INSTALL_DIR/package.json" ] || [ ! -d "$INSTALL_DIR/modules" ]; then
|
||||
echo -e "${RED}Error: This script must be run from the installation directory.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get the current version
|
||||
CURRENT_VERSION=$(grep -oP '"version": "\K[^"]+' package.json)
|
||||
echo -e "${YELLOW}Current version: ${BOLD}$CURRENT_VERSION${NC}"
|
||||
|
||||
# Check for git repository
|
||||
if [ ! -d ".git" ]; then
|
||||
echo -e "${RED}Error: This installation was not set up using git.${NC}"
|
||||
echo -e "Please use the bootstrap installer to perform a fresh installation."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Stash any local changes
|
||||
echo -e "${YELLOW}Backing up any local configuration changes...${NC}"
|
||||
git stash -q
|
||||
|
||||
# Pull the latest changes
|
||||
echo -e "${YELLOW}Pulling latest updates from git...${NC}"
|
||||
git pull
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}Failed to pull updates. Restoring original state...${NC}"
|
||||
git stash pop -q
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get the new version
|
||||
NEW_VERSION=$(grep -oP '"version": "\K[^"]+' package.json)
|
||||
echo -e "${GREEN}New version: ${BOLD}$NEW_VERSION${NC}"
|
||||
|
||||
# Check if update is needed
|
||||
if [ "$CURRENT_VERSION" == "$NEW_VERSION" ]; then
|
||||
echo -e "${GREEN}You already have the latest version.${NC}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Install any new npm dependencies
|
||||
echo -e "${YELLOW}Installing dependencies...${NC}"
|
||||
npm install
|
||||
|
||||
# Apply any local configuration changes
|
||||
if git stash list | grep -q "stash@{0}"; then
|
||||
echo -e "${YELLOW}Restoring local configuration changes...${NC}"
|
||||
git stash pop -q
|
||||
# Handle conflicts if any
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}There were conflicts when restoring your configuration.${NC}"
|
||||
echo -e "Please check the files and resolve conflicts manually."
|
||||
echo -e "Your original configuration is saved in .git/refs/stash"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Restart the service
|
||||
echo -e "${YELLOW}Restarting service...${NC}"
|
||||
if command -v systemctl &> /dev/null; then
|
||||
sudo systemctl restart transmission-rss-manager
|
||||
else
|
||||
echo -e "${RED}Could not restart service automatically.${NC}"
|
||||
echo -e "Please restart the service manually."
|
||||
fi
|
||||
|
||||
# Update complete
|
||||
echo -e "${GREEN}${BOLD}Update complete!${NC}"
|
||||
echo -e "Updated from version $CURRENT_VERSION to $NEW_VERSION"
|
||||
echo -e "Changes will take effect immediately."
|
146
temp_work/server-endpoints.js
Normal file
146
temp_work/server-endpoints.js
Normal file
@@ -0,0 +1,146 @@
|
||||
// Version and update endpoints to be added to server.js
|
||||
|
||||
// Add these imports at the top of server.js
|
||||
const { exec } = require('child_process');
|
||||
const { promisify } = require('util');
|
||||
const execAsync = promisify(exec);
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Add these endpoints
|
||||
|
||||
// Get system status including version and uptime
|
||||
app.get('/api/system/status', async (req, res) => {
|
||||
try {
|
||||
// Get package.json for version info
|
||||
const packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf8'));
|
||||
const version = packageJson.version;
|
||||
|
||||
// Get system uptime
|
||||
const uptimeSeconds = Math.floor(process.uptime());
|
||||
const hours = Math.floor(uptimeSeconds / 3600);
|
||||
const minutes = Math.floor((uptimeSeconds % 3600) / 60);
|
||||
const seconds = uptimeSeconds % 60;
|
||||
const uptime = `${hours}h ${minutes}m ${seconds}s`;
|
||||
|
||||
// Check transmission connection
|
||||
let transmissionStatus = 'Connected';
|
||||
try {
|
||||
await transmissionClient.sessionGet();
|
||||
} catch (err) {
|
||||
transmissionStatus = 'Disconnected';
|
||||
}
|
||||
|
||||
res.json({
|
||||
status: 'success',
|
||||
data: {
|
||||
version,
|
||||
uptime,
|
||||
transmissionStatus
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error getting system status:', error);
|
||||
res.status(500).json({
|
||||
status: 'error',
|
||||
message: 'Failed to get system status'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Check for updates
|
||||
app.get('/api/system/check-updates', async (req, res) => {
|
||||
try {
|
||||
// Check if git is available and if this is a git repository
|
||||
const isGitRepo = fs.existsSync(path.join(__dirname, '.git'));
|
||||
if (!isGitRepo) {
|
||||
return res.json({
|
||||
status: 'error',
|
||||
message: 'This installation is not set up for updates. Please use the bootstrap installer.'
|
||||
});
|
||||
}
|
||||
|
||||
// Get current version
|
||||
const packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf8'));
|
||||
const currentVersion = packageJson.version;
|
||||
|
||||
// Fetch latest updates without applying them
|
||||
await execAsync('git fetch');
|
||||
|
||||
// Check if we're behind the remote repository
|
||||
const { stdout } = await execAsync('git rev-list HEAD..origin/main --count');
|
||||
const behindCount = parseInt(stdout.trim());
|
||||
|
||||
if (behindCount > 0) {
|
||||
// Get the new version from the remote package.json
|
||||
const { stdout: remotePackageJson } = await execAsync('git show origin/main:package.json');
|
||||
const remotePackage = JSON.parse(remotePackageJson);
|
||||
const remoteVersion = remotePackage.version;
|
||||
|
||||
return res.json({
|
||||
status: 'success',
|
||||
data: {
|
||||
updateAvailable: true,
|
||||
currentVersion,
|
||||
remoteVersion,
|
||||
commitsBehind: behindCount
|
||||
}
|
||||
});
|
||||
} else {
|
||||
return res.json({
|
||||
status: 'success',
|
||||
data: {
|
||||
updateAvailable: false,
|
||||
currentVersion
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error checking for updates:', error);
|
||||
res.status(500).json({
|
||||
status: 'error',
|
||||
message: 'Failed to check for updates'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Apply updates
|
||||
app.post('/api/system/update', async (req, res) => {
|
||||
try {
|
||||
// Check if git is available and if this is a git repository
|
||||
const isGitRepo = fs.existsSync(path.join(__dirname, '.git'));
|
||||
if (!isGitRepo) {
|
||||
return res.status(400).json({
|
||||
status: 'error',
|
||||
message: 'This installation is not set up for updates. Please use the bootstrap installer.'
|
||||
});
|
||||
}
|
||||
|
||||
// Run the update script
|
||||
const updateScriptPath = path.join(__dirname, 'scripts', 'update.sh');
|
||||
|
||||
// Make sure the update script is executable
|
||||
await execAsync(`chmod +x ${updateScriptPath}`);
|
||||
|
||||
// Execute the update script
|
||||
const { stdout, stderr } = await execAsync(updateScriptPath);
|
||||
|
||||
// If we get here, the update was successful
|
||||
// The service will be restarted by the update script
|
||||
res.json({
|
||||
status: 'success',
|
||||
message: 'Update applied successfully. The service will restart.',
|
||||
data: {
|
||||
output: stdout,
|
||||
errors: stderr
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error applying update:', error);
|
||||
res.status(500).json({
|
||||
status: 'error',
|
||||
message: 'Failed to apply update',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
});
|
41
temp_work/system-status.html
Normal file
41
temp_work/system-status.html
Normal file
@@ -0,0 +1,41 @@
|
||||
<!-- This HTML block will be inserted into the index.html dashboard -->
|
||||
<div class="row">
|
||||
<div class="col-md-4 mb-3">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h4><i class="fas fa-info-circle"></i> System Status</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="status-item">
|
||||
<span class="status-label">Version:</span>
|
||||
<span id="system-version" class="status-value">Loading...</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-label">Running since:</span>
|
||||
<span id="uptime" class="status-value">Loading...</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-label">Transmission:</span>
|
||||
<span id="transmission-status" class="status-value">
|
||||
<i class="fas fa-circle-notch fa-spin"></i> Checking...
|
||||
</span>
|
||||
</div>
|
||||
<div class="status-item">
|
||||
<span class="status-label">Update:</span>
|
||||
<span id="update-status" class="status-value">
|
||||
<i class="fas fa-circle-notch fa-spin"></i> Checking...
|
||||
</span>
|
||||
</div>
|
||||
<div id="update-available" class="mt-3 d-none">
|
||||
<div class="alert alert-info">
|
||||
<i class="fas fa-arrow-circle-up"></i>
|
||||
<span>A new version is available!</span>
|
||||
<button id="btn-update-now" class="btn btn-sm btn-primary ml-2">
|
||||
<i class="fas fa-download"></i> Update Now
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
84
temp_work/update.sh
Normal file
84
temp_work/update.sh
Normal file
@@ -0,0 +1,84 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Transmission RSS Manager - Update Script
|
||||
# This script pulls the latest version from git and runs necessary updates
|
||||
|
||||
# Color and formatting
|
||||
GREEN='\033[0;32m'
|
||||
RED='\033[0;31m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
BOLD='\033[1m'
|
||||
|
||||
# Installation directory (should be current directory)
|
||||
INSTALL_DIR=$(pwd)
|
||||
|
||||
# Check if we're in the right directory
|
||||
if [ ! -f "$INSTALL_DIR/package.json" ] || [ ! -d "$INSTALL_DIR/modules" ]; then
|
||||
echo -e "${RED}Error: This script must be run from the installation directory.${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get the current version
|
||||
CURRENT_VERSION=$(grep -oP '"version": "\K[^"]+' package.json)
|
||||
echo -e "${YELLOW}Current version: ${BOLD}$CURRENT_VERSION${NC}"
|
||||
|
||||
# Check for git repository
|
||||
if [ ! -d ".git" ]; then
|
||||
echo -e "${RED}Error: This installation was not set up using git.${NC}"
|
||||
echo -e "Please use the bootstrap installer to perform a fresh installation."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Stash any local changes
|
||||
echo -e "${YELLOW}Backing up any local configuration changes...${NC}"
|
||||
git stash -q
|
||||
|
||||
# Pull the latest changes
|
||||
echo -e "${YELLOW}Pulling latest updates from git...${NC}"
|
||||
git pull
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}Failed to pull updates. Restoring original state...${NC}"
|
||||
git stash pop -q
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get the new version
|
||||
NEW_VERSION=$(grep -oP '"version": "\K[^"]+' package.json)
|
||||
echo -e "${GREEN}New version: ${BOLD}$NEW_VERSION${NC}"
|
||||
|
||||
# Check if update is needed
|
||||
if [ "$CURRENT_VERSION" == "$NEW_VERSION" ]; then
|
||||
echo -e "${GREEN}You already have the latest version.${NC}"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Install any new npm dependencies
|
||||
echo -e "${YELLOW}Installing dependencies...${NC}"
|
||||
npm install
|
||||
|
||||
# Apply any local configuration changes
|
||||
if git stash list | grep -q "stash@{0}"; then
|
||||
echo -e "${YELLOW}Restoring local configuration changes...${NC}"
|
||||
git stash pop -q
|
||||
# Handle conflicts if any
|
||||
if [ $? -ne 0 ]; then
|
||||
echo -e "${RED}There were conflicts when restoring your configuration.${NC}"
|
||||
echo -e "Please check the files and resolve conflicts manually."
|
||||
echo -e "Your original configuration is saved in .git/refs/stash"
|
||||
fi
|
||||
fi
|
||||
|
||||
# Restart the service
|
||||
echo -e "${YELLOW}Restarting service...${NC}"
|
||||
if command -v systemctl &> /dev/null; then
|
||||
sudo systemctl restart transmission-rss-manager
|
||||
else
|
||||
echo -e "${RED}Could not restart service automatically.${NC}"
|
||||
echo -e "Please restart the service manually."
|
||||
fi
|
||||
|
||||
# Update complete
|
||||
echo -e "${GREEN}${BOLD}Update complete!${NC}"
|
||||
echo -e "Updated from version $CURRENT_VERSION to $NEW_VERSION"
|
||||
echo -e "Changes will take effect immediately."
|
Reference in New Issue
Block a user