first commit

This commit is contained in:
masterdraco
2025-02-26 08:48:34 +01:00
commit 756c6fdd6f
5 changed files with 2557 additions and 0 deletions
+870
View File
@@ -0,0 +1,870 @@
#!/bin/bash
# Transmission RSS Manager Installation Script for Ubuntu
# This script installs all necessary dependencies and sets up the program
# 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}==================================================${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 )"
# Configuration variables
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="{}"
# Ask user for 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]: " MEDIA_DIR
MEDIA_DIR=${MEDIA_DIR:-"/mnt/media"}
echo
echo -e "${YELLOW}Installing dependencies...${NC}"
# Update package index
apt-get update
# Install Node.js and npm if not already installed
if ! command -v node &> /dev/null; 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
fi
# Install additional dependencies
echo "Installing additional dependencies..."
apt-get install -y unrar unzip p7zip-full nginx
# Create installation directory
echo -e "${YELLOW}Creating installation directory...${NC}"
mkdir -p $INSTALL_DIR
mkdir -p $INSTALL_DIR/logs
# Copy application files if they exist
echo -e "${YELLOW}Copying application files...${NC}"
cp $SCRIPT_DIR/*.js $INSTALL_DIR/ 2>/dev/null || :
cp $SCRIPT_DIR/*.css $INSTALL_DIR/ 2>/dev/null || :
cp $SCRIPT_DIR/*.html $INSTALL_DIR/ 2>/dev/null || :
cp $SCRIPT_DIR/*.md $INSTALL_DIR/ 2>/dev/null || :
# Create package.json
echo -e "${YELLOW}Creating package.json...${NC}"
cat > $INSTALL_DIR/package.json << EOF
{
"name": "transmission-rss-manager",
"version": "1.0.0",
"description": "Transmission RSS Manager with post-processing capabilities",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"dependencies": {
"express": "^4.18.2",
"body-parser": "^1.20.2",
"transmission": "^0.4.10",
"adm-zip": "^0.5.10",
"node-fetch": "^2.6.9",
"xml2js": "^0.5.0",
"cors": "^2.8.5"
}
}
EOF
# Create server.js
echo -e "${YELLOW}Creating server.js...${NC}"
cat > $INSTALL_DIR/server.js << EOF
const express = require("express");
const bodyParser = require("body-parser");
const path = require("path");
const fs = require("fs").promises;
const cors = require("cors");
const Transmission = require("transmission");
// Initialize Express app
const app = express();
const PORT = process.env.PORT || $PORT;
// Load configuration
let config = {
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",
software: "$MEDIA_DIR/software"
},
seedingRequirements: {
minRatio: 1.0,
minTimeMinutes: 60,
checkIntervalSeconds: 300
},
processingOptions: {
extractArchives: true,
deleteArchives: true,
createCategoryFolders: true,
ignoreSample: true,
ignoreExtras: true,
renameFiles: true,
autoReplaceUpgrades: true,
removeDuplicates: true,
keepOnlyBestVersion: true
},
downloadDir: "$TRANSMISSION_DOWNLOAD_DIR"
};
// Save config function
async function saveConfig() {
try {
await fs.writeFile(
path.join(__dirname, 'config.json'),
JSON.stringify(config, null, 2),
'utf8'
);
console.log('Configuration saved');
} catch (err) {
console.error('Error saving config:', err.message);
}
}
// Initialize config on startup
async function loadConfig() {
try {
const data = await fs.readFile(path.join(__dirname, 'config.json'), 'utf8');
const loadedConfig = JSON.parse(data);
config = { ...config, ...loadedConfig };
console.log('Configuration loaded');
} catch (err) {
console.error('Error loading config, using defaults:', err.message);
// Save default config
await saveConfig();
}
}
// Initialize Transmission client
let transmissionClient = null;
function initTransmission() {
transmissionClient = new Transmission({
host: config.transmissionConfig.host,
port: config.transmissionConfig.port,
username: config.transmissionConfig.username,
password: config.transmissionConfig.password,
url: config.transmissionConfig.path
});
}
// Enable CORS
app.use(cors());
// Parse JSON bodies
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
// API routes - defined BEFORE static files
app.get("/api/status", (req, res) => {
res.setHeader("Content-Type", "application/json");
res.send(JSON.stringify({
status: "running",
version: "1.0.0",
transmissionConnected: !!transmissionClient
}));
});
app.post("/api/transmission/test", (req, res) => {
const { host, port, username, password } = req.body;
// Create a test client with provided credentials
const testClient = new Transmission({
host: host || config.transmissionConfig.host,
port: port || config.transmissionConfig.port,
username: username || config.transmissionConfig.username,
password: password || config.transmissionConfig.password,
url: config.transmissionConfig.path
});
// Test connection
testClient.sessionStats((err, result) => {
if (err) {
return res.json({
success: false,
message: \`Connection failed: \${err.message}\`
});
}
// If successful, update config
config.transmissionConfig = {
host: host || config.transmissionConfig.host,
port: port || config.transmissionConfig.port,
username: username || config.transmissionConfig.username,
password: password || config.transmissionConfig.password,
path: config.transmissionConfig.path
};
// Save updated config
saveConfig();
// Reinitialize client
initTransmission();
res.json({
success: true,
message: "Connected to Transmission successfully!",
data: {
version: result.version || "Unknown",
rpcVersion: result.rpcVersion || "Unknown"
}
});
});
});
// Get configuration
app.get("/api/config", (req, res) => {
// Don't send password in response
const safeConfig = { ...config };
if (safeConfig.transmissionConfig) {
safeConfig.transmissionConfig = { ...safeConfig.transmissionConfig, password: '••••••••' };
}
res.json(safeConfig);
});
// Update configuration
app.post("/api/config", async (req, res) => {
try {
// Merge new config with existing config
config = {
...config,
...req.body,
// Preserve password if not provided
transmissionConfig: {
...config.transmissionConfig,
...req.body.transmissionConfig,
password: req.body.transmissionConfig?.password || config.transmissionConfig.password
}
};
await saveConfig();
initTransmission();
res.json({ success: true, message: 'Configuration updated' });
} catch (err) {
res.status(500).json({ success: false, message: err.message });
}
});
// Serve static files
app.use(express.static(path.join(__dirname, "public")));
// Catch-all route AFTER static files
app.get("*", (req, res) => {
res.sendFile(path.join(__dirname, "public", "index.html"));
});
// Initialize the application
async function init() {
await loadConfig();
initTransmission();
// Start the server
app.listen(PORT, () => {
console.log(\`Transmission RSS Manager running on port \${PORT}\`);
});
}
// Start the application
init().catch(err => {
console.error('Failed to initialize application:', err);
});
EOF
# Create public directory and index.html
mkdir -p $INSTALL_DIR/public
echo -e "${YELLOW}Creating index.html...${NC}"
cat > $INSTALL_DIR/public/index.html << EOF
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Transmission RSS Manager</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
line-height: 1.6;
color: #333;
background-color: #f5f5f5;
padding: 20px;
}
.app-container {
max-width: 1000px;
margin: 0 auto;
}
h1, h2 {
color: #2c3e50;
}
.panel {
background: white;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: 500;
}
input[type="text"],
input[type="number"],
input[type="password"] {
width: 100%;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
button {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: 500;
background-color: #3498db;
color: white;
}
button:hover {
background-color: #2980b9;
}
.status-message {
padding: 10px 15px;
border-radius: 4px;
background-color: #f8f9fa;
border-left: 4px solid #3498db;
margin-bottom: 20px;
}
.success {
background-color: #d4edda;
border-left: 4px solid #28a745;
}
.error {
background-color: #f8d7da;
border-left: 4px solid #dc3545;
}
.tabs {
display: flex;
border-bottom: 1px solid #ddd;
margin-bottom: 20px;
}
.tab {
padding: 10px 15px;
cursor: pointer;
border-bottom: 2px solid transparent;
}
.tab.active {
border-bottom: 2px solid #3498db;
font-weight: 500;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
</style>
</head>
<body>
<div class="app-container">
<h1>Transmission RSS Manager</h1>
<div class="tabs">
<div class="tab active" data-tab="connection">Connection</div>
<div class="tab" data-tab="config">Configuration</div>
<div class="tab" data-tab="status">Status</div>
</div>
<div id="status-bar" class="status-message">
Checking connection...
</div>
<div class="tab-content active" id="connection-tab">
<div class="panel">
<h2>Connection Settings</h2>
<div class="form-group">
<label for="host">Host:</label>
<input type="text" id="host" value="localhost">
</div>
<div class="form-group">
<label for="port">Port:</label>
<input type="number" id="port" value="9091">
</div>
<div class="form-group">
<label for="username">Username:</label>
<input type="text" id="username">
</div>
<div class="form-group">
<label for="password">Password:</label>
<input type="password" id="password">
</div>
<button type="button" id="connect-btn">Connect to Transmission</button>
</div>
</div>
<div class="tab-content" id="config-tab">
<div class="panel">
<h2>System Configuration</h2>
<p>Configuration options will be available after connecting to Transmission.</p>
</div>
</div>
<div class="tab-content" id="status-tab">
<div class="panel">
<h2>System Status</h2>
<div id="system-info">
<p>Loading system information...</p>
</div>
</div>
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", function() {
const statusBar = document.getElementById("status-bar");
const tabs = document.querySelectorAll(".tab");
const tabContents = document.querySelectorAll(".tab-content");
// Tab switching
tabs.forEach(tab => {
tab.addEventListener("click", function() {
// Remove active class from all tabs
tabs.forEach(t => t.classList.remove("active"));
tabContents.forEach(tc => tc.classList.remove("active"));
// Add active class to clicked tab
this.classList.add("active");
document.getElementById(this.dataset.tab + "-tab").classList.add("active");
});
});
// Check API connection
fetch("/api/status")
.then(response => {
if (!response.ok) {
throw new Error("API response was not ok: " + response.status);
}
return response.json();
})
.then(data => {
statusBar.textContent = "API Status: Connected";
statusBar.classList.add("success");
// Update system info
const systemInfo = document.getElementById("system-info");
systemInfo.innerHTML = \`
<p><strong>Version:</strong> \${data.version || "1.0.0"}</p>
<p><strong>Transmission Connected:</strong> \${data.transmissionConnected ? "Yes" : "No"}</p>
<p><strong>Status:</strong> \${data.status || "Unknown"}</p>
\`;
// Check if already connected to Transmission
if (data.transmissionConnected) {
// Load configuration
loadConfig();
}
})
.catch(error => {
statusBar.textContent = "API Connection Error: " + error.message;
statusBar.classList.add("error");
console.error("Error details:", error);
});
// Connect button functionality
document.getElementById("connect-btn").addEventListener("click", function() {
const host = document.getElementById("host").value;
const port = document.getElementById("port").value;
const username = document.getElementById("username").value;
const password = document.getElementById("password").value;
statusBar.textContent = "Connecting to Transmission...";
statusBar.classList.remove("success", "error");
fetch("/api/transmission/test", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
host, port, username, password
})
})
.then(response => {
if (!response.ok) {
throw new Error("API response was not ok: " + response.status);
}
return response.json();
})
.then(data => {
if (data.success) {
statusBar.textContent = "Connected to Transmission successfully!";
statusBar.classList.add("success");
// Load configuration after successful connection
loadConfig();
} else {
statusBar.textContent = "Connection failed: " + data.message;
statusBar.classList.add("error");
}
})
.catch(error => {
statusBar.textContent = "Connection error: " + error.message;
statusBar.classList.add("error");
console.error("Error details:", error);
});
});
// Load configuration from server
function loadConfig() {
fetch("/api/config")
.then(response => response.json())
.then(config => {
// Update configuration tab
const configTab = document.getElementById("config-tab");
configTab.innerHTML = \`
<div class="panel">
<h2>System Configuration</h2>
<h3>Transmission</h3>
<p><strong>Host:</strong> \${config.transmissionConfig.host}</p>
<p><strong>Port:</strong> \${config.transmissionConfig.port}</p>
<h3>Destination Paths</h3>
<p><strong>Movies:</strong> \${config.destinationPaths.movies}</p>
<p><strong>TV Shows:</strong> \${config.destinationPaths.tvShows}</p>
<p><strong>Music:</strong> \${config.destinationPaths.music}</p>
<h3>Seeding Requirements</h3>
<p><strong>Minimum Ratio:</strong> \${config.seedingRequirements.minRatio}</p>
<p><strong>Minimum Time (minutes):</strong> \${config.seedingRequirements.minTimeMinutes}</p>
<button id="edit-config-btn" class="primary-button">Edit Configuration</button>
</div>
\`;
})
.catch(error => {
console.error("Error loading configuration:", error);
});
}
});
</script>
</body>
</html>
EOF
# Create directories for media
echo -e "${YELLOW}Creating media directories...${NC}"
mkdir -p $MEDIA_DIR/movies
mkdir -p $MEDIA_DIR/tvshows
mkdir -p $MEDIA_DIR/music
mkdir -p $MEDIA_DIR/books
mkdir -p $MEDIA_DIR/software
# Set correct permissions
echo -e "${YELLOW}Setting permissions...${NC}"
chown -R $USER:$USER $INSTALL_DIR
chown -R $USER:$USER $MEDIA_DIR
chmod -R 755 $INSTALL_DIR
chmod -R 755 $MEDIA_DIR
# Create config file
echo -e "${YELLOW}Creating configuration file...${NC}"
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",
"software": "$MEDIA_DIR/software"
},
"seedingRequirements": {
"minRatio": 1.0,
"minTimeMinutes": 60,
"checkIntervalSeconds": 300
},
"processingOptions": {
"extractArchives": true,
"deleteArchives": true,
"createCategoryFolders": true,
"ignoreSample": true,
"ignoreExtras": true,
"renameFiles": true,
"autoReplaceUpgrades": true,
"removeDuplicates": true,
"keepOnlyBestVersion": true
},
"downloadDir": "$TRANSMISSION_DOWNLOAD_DIR"
}
EOF
# Install Node.js dependencies
echo -e "${YELLOW}Installing Node.js dependencies...${NC}"
cd $INSTALL_DIR
npm install
# Create systemd service
echo -e "${YELLOW}Creating systemd service...${NC}"
cat > /etc/systemd/system/$SERVICE_NAME.service << EOF
[Unit]
Description=Transmission RSS Manager
After=network.target
Wants=transmission-daemon.service
[Service]
ExecStart=/usr/bin/node $INSTALL_DIR/server.js
WorkingDirectory=$INSTALL_DIR
Restart=always
User=$USER
Environment=NODE_ENV=production PORT=$PORT
[Install]
WantedBy=multi-user.target
EOF
# Create Nginx configuration
echo -e "${YELLOW}Creating Nginx configuration...${NC}"
cat > /etc/nginx/sites-available/$SERVICE_NAME << EOF
server {
listen 80;
listen [::]:80;
# Change this to your domain if you have one
server_name _;
# Main location for static files
location / {
proxy_pass http://localhost:$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;
}
# Specific location for API calls
location /api/ {
proxy_pass http://localhost:$PORT/api/;
proxy_http_version 1.1;
proxy_set_header Host \$host;
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
# Enable Nginx site
ln -sf /etc/nginx/sites-available/$SERVICE_NAME /etc/nginx/sites-enabled/
# Test Nginx configuration
nginx -t
# Reload Nginx
systemctl reload nginx
# Enable and start the service
echo -e "${YELLOW}Starting service...${NC}"
systemctl daemon-reload
systemctl enable $SERVICE_NAME
systemctl start $SERVICE_NAME
# Print completion message
echo
echo -e "${GREEN}${BOLD}Installation Complete!${NC}"
echo
echo -e "${BOLD}The Transmission RSS Manager has been installed to:${NC} $INSTALL_DIR"
echo -e "${BOLD}Web interface is available at:${NC} http://localhost (or your server IP)"
echo -e "${BOLD}Service status:${NC} $(systemctl is-active $SERVICE_NAME)"
echo
echo -e "${BOLD}To view logs:${NC} journalctl -u $SERVICE_NAME -f"
echo -e "${BOLD}To restart service:${NC} sudo systemctl restart $SERVICE_NAME"
echo
if [ "$TRANSMISSION_REMOTE" = true ]; then
echo -e "${YELLOW}Remote Transmission Configuration:${NC}"
echo -e "Host: $TRANSMISSION_HOST:$TRANSMISSION_PORT"
echo -e "Directory Mapping: Remote paths will be mapped to local paths for processing"
echo
fi
# Final check
if systemctl is-active --quiet $SERVICE_NAME; then
echo -e "${GREEN}Service is running successfully!${NC}"
else
echo -e "${RED}Service failed to start. Check logs with: journalctl -u $SERVICE_NAME -f${NC}"
fi
echo -e "${BOLD}==================================================${NC}"