split install up into smaller files due to size of file

This commit is contained in:
root 2025-02-26 14:15:12 +01:00
parent 94eb96008e
commit d1483ce581
10 changed files with 4284 additions and 2836 deletions

0
README.md Normal file → Executable file
View File

File diff suppressed because it is too large Load Diff

62
main-installer.sh Executable file
View File

@ -0,0 +1,62 @@
#!/bin/bash
# Transmission RSS Manager Modular Installer
# Main installer script that coordinates the installation process
# 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 - Enhanced 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 )"
# Source the module files
source "${SCRIPT_DIR}/modules/config.sh"
source "${SCRIPT_DIR}/modules/utils.sh"
source "${SCRIPT_DIR}/modules/dependencies.sh"
source "${SCRIPT_DIR}/modules/file_creator.sh"
source "${SCRIPT_DIR}/modules/service_setup.sh"
# Execute the installation steps in sequence
echo -e "${YELLOW}Starting installation process...${NC}"
# Step 1: Gather configuration from user
gather_configuration
# Step 2: Install dependencies
install_dependencies
# Step 3: Create installation directories
create_directories
# Step 4: Create configuration files and scripts
create_config_files
# Step 5: Create service files and install the service
setup_service
# Step 6: Final setup and permissions
finalize_setup
echo -e "${GREEN}Installation completed successfully!${NC}"
echo -e "You can access the RSS Manager at ${BOLD}http://localhost:${PORT}${NC} or ${BOLD}http://your-server-ip:${PORT}${NC}"
echo
echo -e "The service is ${BOLD}automatically started${NC} and will ${BOLD}start on boot${NC}."
echo -e "To manually control the service, use: ${BOLD}sudo systemctl [start|stop|restart] ${SERVICE_NAME}${NC}"
echo
echo -e "${BOLD}Thank you for installing Transmission RSS Manager Enhanced Edition!${NC}"

136
modules/config-module.sh Normal file
View File

@ -0,0 +1,136 @@
#!/bin/bash
# Configuration module for Transmission RSS Manager Installation
# Configuration variables with defaults
INSTALL_DIR="/opt/transmission-rss-manager"
SERVICE_NAME="transmission-rss-manager"
USER=$(logname || echo $SUDO_USER)
PORT=3000
# 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
function gather_configuration() {
echo -e "${BOLD}Installation Configuration:${NC}"
echo -e "Please provide the following configuration parameters:"
echo
read -p "Installation directory [$INSTALL_DIR]: " input_install_dir
INSTALL_DIR=${input_install_dir:-$INSTALL_DIR}
read -p "Web interface port [$PORT]: " input_port
PORT=${input_port:-$PORT}
read -p "Run as user [$USER]: " input_user
USER=${input_user:-$USER}
echo
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
TRANSMISSION_REMOTE=true
read -p "Remote Transmission host [localhost]: " input_trans_host
TRANSMISSION_HOST=${input_trans_host:-$TRANSMISSION_HOST}
read -p "Remote Transmission port [9091]: " input_trans_port
TRANSMISSION_PORT=${input_trans_port:-$TRANSMISSION_PORT}
read -p "Remote Transmission username []: " input_trans_user
TRANSMISSION_USER=${input_trans_user:-$TRANSMISSION_USER}
read -p "Remote Transmission password []: " input_trans_pass
TRANSMISSION_PASS=${input_trans_pass:-$TRANSMISSION_PASS}
read -p "Remote Transmission RPC path [/transmission/rpc]: " input_trans_path
TRANSMISSION_RPC_PATH=${input_trans_path:-$TRANSMISSION_RPC_PATH}
# 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
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"
# 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
# Update mapping JSON (remove the last "}" and add the new mapping)
TRANSMISSION_DIR_MAPPING="${TRANSMISSION_DIR_MAPPING%\}}, \"$remote_dir\": \"$local_dir\" }"
# Create the local directory
mkdir -p "$local_dir"
chown -R $USER:$USER "$local_dir"
echo -e "${GREEN}Mapping added: $remote_dir$local_dir${NC}"
fi
done
# Set Transmission download dir for configuration
TRANSMISSION_DOWNLOAD_DIR=$REMOTE_DOWNLOAD_DIR
else
read -p "Transmission download directory [/var/lib/transmission-daemon/downloads]: " input_trans_dir
TRANSMISSION_DOWNLOAD_DIR=${input_trans_dir:-$TRANSMISSION_DOWNLOAD_DIR}
fi
echo
echo -e "${BOLD}Media Destination Configuration:${NC}"
read -p "Media destination base directory [/mnt/media]: " input_media_dir
MEDIA_DIR=${input_media_dir:-$MEDIA_DIR}
# 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
ENABLE_BOOK_SORTING=true
if [[ $input_book_sorting =~ ^[Nn]$ ]]; then
ENABLE_BOOK_SORTING=false
fi
echo
echo -e "${GREEN}Configuration complete!${NC}"
echo
}

View File

@ -0,0 +1,60 @@
#!/bin/bash
# Dependencies module for Transmission RSS Manager Installation
function install_dependencies() {
echo -e "${YELLOW}Installing dependencies...${NC}"
# Update package index
apt-get update
# Install Node.js and npm if not already installed
if ! command_exists node; then
echo "Installing Node.js and npm..."
apt-get install -y ca-certificates curl gnupg
mkdir -p /etc/apt/keyrings
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
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
apt-get update
apt-get install -y nodejs
else
echo "Node.js is already installed."
fi
# Install additional dependencies
echo "Installing additional dependencies..."
apt-get install -y unrar unzip p7zip-full nginx
# Check if all dependencies were installed successfully
local dependencies=("node" "npm" "unrar" "unzip" "7z" "nginx")
local missing_deps=()
for dep in "${dependencies[@]}"; do
if ! command_exists "$dep"; then
missing_deps+=("$dep")
fi
done
if [ ${#missing_deps[@]} -eq 0 ]; then
echo -e "${GREEN}All dependencies installed successfully.${NC}"
else
echo -e "${RED}Failed to install some dependencies: ${missing_deps[*]}${NC}"
echo -e "${YELLOW}Please install them manually and rerun this script.${NC}"
exit 1
fi
}
function create_directories() {
echo -e "${YELLOW}Creating installation directories...${NC}"
# Create main installation directory
mkdir -p $INSTALL_DIR
mkdir -p $INSTALL_DIR/logs
mkdir -p $INSTALL_DIR/public/js
mkdir -p $INSTALL_DIR/public/css
mkdir -p $INSTALL_DIR/modules
# Create directory for file storage
mkdir -p $INSTALL_DIR/data
echo -e "${GREEN}Directories created successfully.${NC}"
}

File diff suppressed because it is too large Load Diff

456
modules/rss-feed-manager.js Normal file
View File

@ -0,0 +1,456 @@
// rssFeedManager.js
const fs = require('fs').promises;
const path = require('path');
const fetch = require('node-fetch');
const xml2js = require('xml2js');
const crypto = require('crypto');
class RssFeedManager {
constructor(config) {
this.config = config;
this.feeds = config.feeds || [];
this.items = [];
this.updateIntervalId = null;
this.updateIntervalMinutes = config.updateIntervalMinutes || 60;
this.parser = new xml2js.Parser({ explicitArray: false });
this.dataPath = path.join(__dirname, '..', 'data');
}
async start() {
if (this.updateIntervalId) {
return;
}
// Run update immediately
await this.updateAllFeeds();
// Then set up interval
this.updateIntervalId = setInterval(async () => {
await this.updateAllFeeds();
}, this.updateIntervalMinutes * 60 * 1000);
console.log(`RSS feed manager started, interval: ${this.updateIntervalMinutes} minutes`);
}
stop() {
if (this.updateIntervalId) {
clearInterval(this.updateIntervalId);
this.updateIntervalId = null;
console.log('RSS feed manager stopped');
return true;
}
return false;
}
async updateAllFeeds() {
console.log('Updating all RSS feeds...');
const results = [];
for (const feed of this.feeds) {
try {
const result = await this.updateFeed(feed);
results.push({
feedId: feed.id,
success: true,
newItems: result.newItems
});
} catch (error) {
console.error(`Error updating feed ${feed.id} (${feed.url}):`, error.message);
results.push({
feedId: feed.id,
success: false,
error: error.message
});
}
}
// Save updated items
await this.saveItems();
console.log('RSS feed update completed');
return results;
}
async updateFeed(feed) {
console.log(`Updating feed: ${feed.name} (${feed.url})`);
try {
const response = await fetch(feed.url);
if (!response.ok) {
throw new Error(`HTTP error ${response.status}: ${response.statusText}`);
}
const xml = await response.text();
const result = await this.parseXml(xml);
const rssItems = this.extractItems(result, feed);
const newItems = this.processNewItems(rssItems, feed);
console.log(`Found ${rssItems.length} items, ${newItems.length} new items in feed: ${feed.name}`);
return {
totalItems: rssItems.length,
newItems: newItems.length
};
} catch (error) {
console.error(`Error updating feed ${feed.url}:`, error);
throw error;
}
}
parseXml(xml) {
return new Promise((resolve, reject) => {
this.parser.parseString(xml, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
}
extractItems(parsedXml, feed) {
try {
// Handle standard RSS 2.0
if (parsedXml.rss && parsedXml.rss.channel) {
const channel = parsedXml.rss.channel;
const items = Array.isArray(channel.item) ? channel.item : [channel.item].filter(Boolean);
return items.map(item => this.normalizeRssItem(item, feed));
}
// Handle Atom
if (parsedXml.feed && parsedXml.feed.entry) {
const entries = Array.isArray(parsedXml.feed.entry) ? parsedXml.feed.entry : [parsedXml.feed.entry].filter(Boolean);
return entries.map(entry => this.normalizeAtomItem(entry, feed));
}
return [];
} catch (error) {
console.error('Error extracting items from XML:', error);
return [];
}
}
normalizeRssItem(item, feed) {
// Create a unique ID for the item
const idContent = `${feed.id}:${item.title}:${item.pubDate || ''}:${item.link || ''}`;
const id = crypto.createHash('md5').update(idContent).digest('hex');
// Extract enclosure (torrent link)
let torrentLink = item.link || '';
let fileSize = 0;
if (item.enclosure) {
torrentLink = item.enclosure.$ ? item.enclosure.$.url : item.enclosure.url || torrentLink;
fileSize = item.enclosure.$ ? parseInt(item.enclosure.$.length || 0, 10) : parseInt(item.enclosure.length || 0, 10);
}
// Handle custom namespaces (common in torrent feeds)
let category = '';
let size = fileSize;
if (item.category) {
category = Array.isArray(item.category) ? item.category[0] : item.category;
}
// Some feeds use torrent:contentLength
if (item['torrent:contentLength']) {
size = parseInt(item['torrent:contentLength'], 10);
}
return {
id,
feedId: feed.id,
title: item.title || 'Untitled',
link: item.link || '',
torrentLink: torrentLink,
pubDate: item.pubDate || new Date().toISOString(),
category: category,
description: item.description || '',
size: size || 0,
downloaded: false,
ignored: false,
added: new Date().toISOString()
};
}
normalizeAtomItem(entry, feed) {
// Create a unique ID for the item
const idContent = `${feed.id}:${entry.title}:${entry.updated || ''}:${entry.id || ''}`;
const id = crypto.createHash('md5').update(idContent).digest('hex');
// Extract link
let link = '';
let torrentLink = '';
if (entry.link) {
if (Array.isArray(entry.link)) {
const links = entry.link;
link = links.find(l => l.$.rel === 'alternate')?.$.href || links[0]?.$.href || '';
torrentLink = links.find(l => l.$.type && l.$.type.includes('torrent'))?.$.href || link;
} else {
link = entry.link.$.href || '';
torrentLink = link;
}
}
return {
id,
feedId: feed.id,
title: entry.title || 'Untitled',
link: link,
torrentLink: torrentLink,
pubDate: entry.updated || entry.published || new Date().toISOString(),
category: entry.category?.$.term || '',
description: entry.summary || entry.content || '',
size: 0, // Atom doesn't typically include file size
downloaded: false,
ignored: false,
added: new Date().toISOString()
};
}
processNewItems(rssItems, feed) {
const newItems = [];
for (const item of rssItems) {
// Check if item already exists in our list
const existingItem = this.items.find(i => i.id === item.id);
if (!existingItem) {
// Add new item to our list
this.items.push(item);
newItems.push(item);
// Auto-download if enabled and matches filters
if (feed.autoDownload && this.matchesFilters(item, feed.filters)) {
this.queueItemForDownload(item);
}
}
}
return newItems;
}
matchesFilters(item, filters) {
if (!filters || filters.length === 0) {
return true;
}
// Check if the item matches any of the filters
return filters.some(filter => {
// Title check
if (filter.title && !item.title.toLowerCase().includes(filter.title.toLowerCase())) {
return false;
}
// Category check
if (filter.category && !item.category.toLowerCase().includes(filter.category.toLowerCase())) {
return false;
}
// Size check
if (filter.minSize && item.size < filter.minSize) {
return false;
}
if (filter.maxSize && item.size > filter.maxSize) {
return false;
}
// All checks passed
return true;
});
}
queueItemForDownload(item) {
// Mark the item as queued for download
console.log(`Auto-downloading item: ${item.title}`);
// This would be implemented to add to Transmission
// But we need a reference to the Transmission client
// In a real implementation, this might publish to a queue that's consumed elsewhere
item.downloadQueued = true;
}
async saveItems() {
try {
// Create data directory if it doesn't exist
await fs.mkdir(this.dataPath, { recursive: true });
// Save items to file
await fs.writeFile(
path.join(this.dataPath, 'rss-items.json'),
JSON.stringify(this.items, null, 2),
'utf8'
);
console.log(`Saved ${this.items.length} RSS items to disk`);
return true;
} catch (error) {
console.error('Error saving RSS items:', error);
return false;
}
}
async saveConfig() {
try {
// Create data directory if it doesn't exist
await fs.mkdir(this.dataPath, { recursive: true });
// Save feeds to file
await fs.writeFile(
path.join(this.dataPath, 'rss-feeds.json'),
JSON.stringify(this.feeds, null, 2),
'utf8'
);
console.log(`Saved ${this.feeds.length} RSS feeds to disk`);
return true;
} catch (error) {
console.error('Error saving RSS feeds:', error);
return false;
}
}
async loadItems() {
try {
const filePath = path.join(this.dataPath, 'rss-items.json');
// Check if file exists
try {
await fs.access(filePath);
} catch (error) {
console.log('No saved RSS items found');
return false;
}
// Load items from file
const data = await fs.readFile(filePath, 'utf8');
this.items = JSON.parse(data);
console.log(`Loaded ${this.items.length} RSS items from disk`);
return true;
} catch (error) {
console.error('Error loading RSS items:', error);
return false;
}
}
// Public API methods
getAllFeeds() {
return this.feeds;
}
addFeed(feedData) {
// Generate an ID for the feed
const id = crypto.randomBytes(8).toString('hex');
const newFeed = {
id,
name: feedData.name,
url: feedData.url,
autoDownload: feedData.autoDownload || false,
filters: feedData.filters || [],
added: new Date().toISOString()
};
this.feeds.push(newFeed);
console.log(`Added new feed: ${newFeed.name} (${newFeed.url})`);
return newFeed;
}
updateFeedConfig(feedId, updates) {
const feedIndex = this.feeds.findIndex(f => f.id === feedId);
if (feedIndex === -1) {
return false;
}
// Update the feed, preserving the id and added date
this.feeds[feedIndex] = {
...this.feeds[feedIndex],
...updates,
id: feedId,
added: this.feeds[feedIndex].added
};
console.log(`Updated feed: ${this.feeds[feedIndex].name}`);
return true;
}
removeFeed(feedId) {
const initialLength = this.feeds.length;
this.feeds = this.feeds.filter(f => f.id !== feedId);
return this.feeds.length !== initialLength;
}
getAllItems() {
return this.items;
}
getUndownloadedItems() {
return this.items.filter(item => !item.downloaded && !item.ignored);
}
filterItems(filters) {
return this.items.filter(item => this.matchesFilters(item, [filters]));
}
async downloadItem(item, transmissionClient) {
if (!item || !item.torrentLink) {
return {
success: false,
message: 'Invalid item or missing torrent link'
};
}
if (!transmissionClient) {
return {
success: false,
message: 'Transmission client not available'
};
}
return new Promise((resolve) => {
transmissionClient.addUrl(item.torrentLink, (err, result) => {
if (err) {
console.error(`Error adding torrent for ${item.title}:`, err);
resolve({
success: false,
message: `Error adding torrent: ${err.message}`,
result: null
});
return;
}
// Mark the item as downloaded
item.downloaded = true;
item.downloadDate = new Date().toISOString();
// Save the updated items
this.saveItems().catch(err => {
console.error('Error saving items after download:', err);
});
console.log(`Successfully added torrent for item: ${item.title}`);
resolve({
success: true,
message: 'Torrent added successfully',
result
});
});
});
}
}
module.exports = RssFeedManager;

View File

@ -0,0 +1,83 @@
#!/bin/bash
# Service setup module for Transmission RSS Manager Installation
# Setup systemd service
function setup_service() {
echo -e "${YELLOW}Setting up systemd service...${NC}"
# Create systemd service file
cat > /etc/systemd/system/$SERVICE_NAME.service << EOF
[Unit]
Description=Transmission RSS Manager
After=network.target
Wants=network-online.target
[Service]
Type=simple
User=$USER
WorkingDirectory=$INSTALL_DIR
ExecStart=/usr/bin/node $INSTALL_DIR/server.js
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
Environment=PORT=$PORT
Environment=NODE_ENV=production
[Install]
WantedBy=multi-user.target
EOF
# Create nginx configuration for proxy
echo -e "${YELLOW}Setting up Nginx reverse proxy...${NC}"
# Check if default nginx file exists, back it up if it does
if [ -f /etc/nginx/sites-enabled/default ]; then
mv /etc/nginx/sites-enabled/default /etc/nginx/sites-enabled/default.bak
echo "Backed up default nginx configuration."
fi
# Create nginx configuration
cat > /etc/nginx/sites-available/$SERVICE_NAME << 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
# Create symbolic link to enable the site
ln -sf /etc/nginx/sites-available/$SERVICE_NAME /etc/nginx/sites-enabled/
# Test nginx configuration
nginx -t
if [ $? -eq 0 ]; then
# Reload nginx
systemctl reload nginx
echo -e "${GREEN}Nginx configuration has been set up successfully.${NC}"
else
echo -e "${RED}Nginx configuration test failed. Please check the configuration manually.${NC}"
echo -e "${YELLOW}You may need to correct the configuration before the web interface will be accessible.${NC}"
fi
# Reload systemd
systemctl daemon-reload
# Enable the service to start on boot
systemctl enable $SERVICE_NAME
echo -e "${GREEN}Systemd service has been created and enabled.${NC}"
echo -e "${YELLOW}The service will start automatically after installation.${NC}"
}

144
modules/utils-module.sh Normal file
View File

@ -0,0 +1,144 @@
#!/bin/bash
# Utilities module for Transmission RSS Manager Installation
# Function to log a message with timestamp
function log() {
local level=$1
local message=$2
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
case $level in
"INFO")
echo -e "${timestamp} ${GREEN}[INFO]${NC} $message"
;;
"WARN")
echo -e "${timestamp} ${YELLOW}[WARN]${NC} $message"
;;
"ERROR")
echo -e "${timestamp} ${RED}[ERROR]${NC} $message"
;;
*)
echo -e "${timestamp} [LOG] $message"
;;
esac
}
# Function to check if a command exists
function command_exists() {
command -v "$1" &> /dev/null
}
# Function to backup a file before modifying it
function backup_file() {
local file=$1
if [ -f "$file" ]; then
local backup="${file}.bak.$(date +%Y%m%d%H%M%S)"
cp "$file" "$backup"
log "INFO" "Created backup of $file at $backup"
fi
}
# Function to create a directory if it doesn't exist
function create_dir_if_not_exists() {
local dir=$1
local owner=$2
if [ ! -d "$dir" ]; then
mkdir -p "$dir"
log "INFO" "Created directory: $dir"
if [ -n "$owner" ]; then
chown -R "$owner" "$dir"
log "INFO" "Set ownership of $dir to $owner"
fi
fi
}
# Function to finalize the setup (permissions, etc.)
function finalize_setup() {
log "INFO" "Setting up final permissions and configurations..."
# Set proper ownership for the installation directory
chown -R $USER:$USER $INSTALL_DIR
# Create media directories with correct permissions
create_dir_if_not_exists "$MEDIA_DIR/movies" "$USER:$USER"
create_dir_if_not_exists "$MEDIA_DIR/tvshows" "$USER:$USER"
create_dir_if_not_exists "$MEDIA_DIR/music" "$USER:$USER"
create_dir_if_not_exists "$MEDIA_DIR/software" "$USER:$USER"
# Create book/magazine directories if enabled
if [ "$ENABLE_BOOK_SORTING" = true ]; then
create_dir_if_not_exists "$MEDIA_DIR/books" "$USER:$USER"
create_dir_if_not_exists "$MEDIA_DIR/magazines" "$USER:$USER"
fi
# Install NPM packages
log "INFO" "Installing NPM packages..."
cd $INSTALL_DIR && npm install
# Start the service
log "INFO" "Starting the service..."
systemctl daemon-reload
systemctl enable $SERVICE_NAME
systemctl start $SERVICE_NAME
# Check if service started successfully
sleep 2
if systemctl is-active --quiet $SERVICE_NAME; then
log "INFO" "Service started successfully!"
else
log "ERROR" "Service failed to start. Check logs with: journalctl -u $SERVICE_NAME"
fi
# Create default configuration if it doesn't exist
if [ ! -f "$INSTALL_DIR/config.json" ]; then
log "INFO" "Creating default configuration file..."
cat > $INSTALL_DIR/config.json << EOF
{
"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
}
EOF
chown $USER:$USER $INSTALL_DIR/config.json
fi
log "INFO" "Setup finalized!"
}

1386
public/index.html Normal file

File diff suppressed because it is too large Load Diff