Compare commits

...

2 Commits

Author SHA1 Message Date
3cfc5f3489 Update README.md with v2.0.2 changes and fix documentation 2025-03-05 00:36:29 +00:00
f5404c241d Fix remote transmission detection in installer
- Fix bug in dependencies-module.sh that would prompt to install transmission-daemon for remote installations
- Add checks for TRANSMISSION_REMOTE flag to correctly handle remote vs local installations
- Update version to 2.0.2

🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-03-05 00:35:42 +00:00
13 changed files with 1443 additions and 6 deletions

View File

@ -1,9 +1,14 @@
# Transmission RSS Manager v2.0.1
# Transmission RSS Manager v2.0.2
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!
## Changelog
### 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

View File

@ -35,8 +35,8 @@ 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 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
@ -109,8 +109,8 @@ function install_dependencies() {
local dependencies=("node" "npm" "unzip" "nginx")
local missing_deps=()
# Add transmission to dependencies check if local installation was selected
if [ "$TRANSMISSION_HOST" = "localhost" ] || [ "$TRANSMISSION_HOST" = "127.0.0.1" ]; then
# 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

View File

@ -1,6 +1,6 @@
{
"name": "transmission-rss-manager",
"version": "2.0.1",
"version": "2.0.2",
"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": {

75
temp_work/SUMMARY.md Normal file
View File

@ -0,0 +1,75 @@
# Git-Based Installation and Update System
We've restructured the Transmission RSS Manager to use a git-based approach for installation and updates. Here's how the new system works:
## New Architecture
1. **Bootstrap Installer**
- Small, self-contained installer script
- Installs git if needed
- Clones the main repository
- Runs the main installer
2. **Main Installer**
- Modified to work with git-managed files
- Doesn't need to generate application files
- Only creates config files and sets up services
3. **Update System**
- Web interface shows current version
- Checks for updates automatically
- One-click update process
- Preserves user configuration
## Key Files
1. **bootstrap-installer.sh**
- Entry point for new installations
- Minimal script that pulls everything else from git
2. **update.sh**
- Located in the scripts/ directory
- Handles git pull and service restart
- Preserves local configuration changes
3. **System Status UI**
- Added to the dashboard
- Shows version, uptime, and connection status
- Notifies when updates are available
4. **Server Endpoints**
- `/api/system/status` - Gets current version and system status
- `/api/system/check-updates` - Checks if updates are available
- `/api/system/update` - Triggers the update process
## Benefits
1. **Simplified Maintenance**
- Single source of truth in the git repository
- No need to embed code in installation scripts
- Easy to make changes and publish updates
2. **Better User Experience**
- Users can see when updates are available
- One-click update process
- No need to manually download and run scripts
3. **Version Control**
- Clear versioning visible to users
- Update history preserved in git
- Easy rollback if needed
## Implementation Notes
This implementation preserves local configuration by:
1. Stashing local changes before pulling updates
2. Applying those changes back after the update
3. Handling any potential conflicts
The service automatically restarts after updates, ensuring users always have the latest version running.
## Next Steps
1. Update the GitHub repository structure to match this new approach
2. Test the installation and update process in a clean environment
3. Consider adding a changelog display in the UI to show what's new in each version

131
temp_work/app-update.js Normal file
View 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();

View 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."

View 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
}

View 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."
}

View 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}"

View 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."

View 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
});
}
});

View 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
View 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."