Instantly share code, notes, and snippets.
Last active
November 9, 2025 03:49
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
-
Save tzhvh/d33340e914596c5044676479700d16ea to your computer and use it in GitHub Desktop.
Dolphin context menu installer: KDE file share via HTTP with QR code
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/bin/bash | |
| ############################################################################### | |
| # KDE Share via HTTP - Complete Installer (Fedora Edition) | |
| # Creates a KDE context menu entry to share files via ephemeral HTTP server | |
| # with QR code for easy mobile access | |
| # | |
| # Target: Fedora with KDE Plasma | |
| # Features: Lazy sudo, per-command authorization, comprehensive error handling, | |
| # HTTP root path redirect, UFW/firewalld auto-configuration | |
| ############################################################################### | |
| # Do NOT use set -e - we handle errors manually | |
| # set -e | |
| echo "========================================" | |
| echo "KDE Share via HTTP - Installer" | |
| echo "Fedora/KDE Edition" | |
| echo "========================================" | |
| echo "" | |
| # Color codes for output | |
| RED='\033[0;31m' | |
| GREEN='\033[0;32m' | |
| YELLOW='\033[1;33m' | |
| BLUE='\033[0;34m' | |
| NC='\033[0m' # No Color | |
| # Configuration | |
| PORT_RANGE_START=25000 | |
| PORT_RANGE_END=25100 | |
| # Logging functions (terminal only) | |
| log_info() { | |
| echo -e "${BLUE}[INFO]${NC} $(date '+%H:%M:%S') - $1" | |
| } | |
| log_success() { | |
| echo -e "${GREEN}[SUCCESS]${NC} $(date '+%H:%M:%S') - $1" | |
| } | |
| log_warning() { | |
| echo -e "${YELLOW}[WARNING]${NC} $(date '+%H:%M:%S') - $1" | |
| } | |
| log_error() { | |
| echo -e "${RED}[ERROR]${NC} $(date '+%H:%M:%S') - $1" | |
| } | |
| # Function to check for errors and exit if any | |
| check_error() { | |
| local exit_code=$? | |
| if [ $exit_code -ne 0 ]; then | |
| log_error "$1 (Exit code: $exit_code)" | |
| log_error "Installation failed." | |
| exit 1 | |
| fi | |
| } | |
| # Function to validate command exists | |
| validate_command() { | |
| local cmd=$1 | |
| local package=$2 | |
| if ! command -v "$cmd" &> /dev/null; then | |
| log_error "Required command '$cmd' not found. Package: $package" | |
| return 1 | |
| fi | |
| log_success "Found command: $cmd" | |
| return 0 | |
| } | |
| # Function to run sudo commands with permission | |
| run_with_sudo() { | |
| local description=$1 | |
| shift | |
| local cmd="$@" | |
| echo "" | |
| echo -e "${YELLOW}⚠️ Elevated privileges required${NC}" | |
| echo "Action: $description" | |
| echo "Command: $cmd" | |
| echo "" | |
| if [ -t 0 ]; then | |
| read -p "Allow this command? (y/N): " -n 1 -r | |
| echo | |
| if [[ ! $REPLY =~ ^[Yy]$ ]]; then | |
| log_error "User denied permission for: $description" | |
| exit 1 | |
| fi | |
| else | |
| log_error "Not in interactive terminal, cannot request permission" | |
| exit 1 | |
| fi | |
| log_info "Executing: $cmd" | |
| eval "$cmd" | |
| return $? | |
| } | |
| # Function to configure firewall | |
| configure_firewall() { | |
| log_info "Checking for active firewalls..." | |
| # Check UFW | |
| if command -v ufw &> /dev/null; then | |
| UFW_STATUS=$(ufw status 2>&1) | |
| if echo "$UFW_STATUS" | grep -q "Status: active"; then | |
| log_info "UFW detected and active. Configuring port range $PORT_RANGE_START-$PORT_RANGE_END..." | |
| run_with_sudo "Allow ports $PORT_RANGE_START:$PORT_RANGE_END/tcp in UFW" \ | |
| "sudo ufw allow $PORT_RANGE_START:$PORT_RANGE_END/tcp" | |
| check_error "Failed to configure UFW" | |
| log_success "UFW configured for KDE Share HTTP" | |
| return 0 | |
| else | |
| log_info "UFW found but not active, skipping configuration" | |
| fi | |
| fi | |
| # Check firewalld | |
| if command -v firewall-cmd &> /dev/null && systemctl is-active --quiet firewalld; then | |
| log_info "Firewalld detected and active. Configuring port range $PORT_RANGE_START-$PORT_RANGE_END..." | |
| run_with_sudo "Allow ports $PORT_RANGE_START-$PORT_RANGE_END/tcp in firewalld" \ | |
| "sudo firewall-cmd --permanent --add-port=$PORT_RANGE_START-$PORT_RANGE_END/tcp" | |
| check_error "Failed to add firewalld port rule" | |
| run_with_sudo "Reload firewalld configuration" \ | |
| "sudo firewall-cmd --reload" | |
| check_error "Failed to reload firewalld" | |
| log_success "Firewalld configured for KDE Share HTTP" | |
| return 0 | |
| elif command -v firewall-cmd &> /dev/null; then | |
| log_info "Firewalld found but not active, skipping configuration" | |
| fi | |
| log_info "No active supported firewall detected (UFW or firewalld)" | |
| log_warning "If you use another firewall, manually open ports $PORT_RANGE_START-$PORT_RANGE_END/tcp" | |
| } | |
| # Detect OS | |
| log_info "Detecting operating system..." | |
| if [ -f /etc/os-release ]; then | |
| . /etc/os-release | |
| log_info "OS: $NAME $VERSION" | |
| log_info "ID: $ID" | |
| else | |
| log_warning "Cannot detect OS version" | |
| fi | |
| # Check if running on KDE | |
| log_info "Checking KDE desktop environment..." | |
| KDE_DETECTED=false | |
| if [ -n "$KDE_SESSION_VERSION" ]; then | |
| log_success "KDE detected (Session version: $KDE_SESSION_VERSION)" | |
| KDE_DETECTED=true | |
| elif [ -n "$KDE_FULL_SESSION" ]; then | |
| log_success "KDE detected (Full session: $KDE_FULL_SESSION)" | |
| KDE_DETECTED=true | |
| elif [ "$DESKTOP_SESSION" = "plasma" ] || [ "$XDG_CURRENT_DESKTOP" = "KDE" ]; then | |
| log_success "KDE detected via desktop session" | |
| KDE_DETECTED=true | |
| fi | |
| if [ "$KDE_DETECTED" = false ]; then | |
| log_warning "KDE desktop environment not detected." | |
| log_warning "Environment variables:" | |
| log_warning " KDE_SESSION_VERSION: ${KDE_SESSION_VERSION:-not set}" | |
| log_warning " KDE_FULL_SESSION: ${KDE_FULL_SESSION:-not set}" | |
| log_warning " DESKTOP_SESSION: ${DESKTOP_SESSION:-not set}" | |
| log_warning " XDG_CURRENT_DESKTOP: ${XDG_CURRENT_DESKTOP:-not set}" | |
| echo "" | |
| echo -e "${YELLOW}Warning: This installer is designed for KDE Plasma.${NC}" | |
| # Check if we are in an interactive terminal | |
| if [ -t 0 ]; then | |
| read -p "Continue anyway? (y/N): " -n 1 -r | |
| echo | |
| if [[ ! $REPLY =~ ^[Yy]$ ]]; then | |
| log_info "User chose to exit" | |
| exit 1 | |
| fi | |
| log_info "User chose to continue despite KDE not detected" | |
| else | |
| log_error "Not running in an interactive terminal and KDE not detected. Exiting." | |
| exit 1 | |
| fi | |
| fi | |
| # Check package manager | |
| log_info "Detecting package manager..." | |
| PKG_MANAGER="" | |
| if command -v dnf &> /dev/null; then | |
| PKG_MANAGER="dnf" | |
| log_success "Package manager: DNF (Fedora/RHEL)" | |
| elif command -v apt &> /dev/null; then | |
| PKG_MANAGER="apt" | |
| log_success "Package manager: APT (Debian/Ubuntu)" | |
| elif command -v pacman &> /dev/null; then | |
| PKG_MANAGER="pacman" | |
| log_success "Package manager: Pacman (Arch)" | |
| else | |
| log_error "No supported package manager found (dnf, apt, or pacman)" | |
| exit 1 | |
| fi | |
| # Install dependencies | |
| echo "" | |
| echo -e "${GREEN}[1/4] Installing dependencies...${NC}" | |
| log_info "Checking dependencies: qrencode python3 zip kdialog" | |
| # Check which packages are missing | |
| MISSING_PACKAGES=() | |
| if ! command -v qrencode &> /dev/null; then | |
| MISSING_PACKAGES+=("qrencode") | |
| fi | |
| if ! command -v python3 &> /dev/null; then | |
| MISSING_PACKAGES+=("python3") | |
| fi | |
| if ! command -v zip &> /dev/null; then | |
| MISSING_PACKAGES+=("zip") | |
| fi | |
| if ! command -v kdialog &> /dev/null; then | |
| case $PKG_MANAGER in | |
| dnf) | |
| MISSING_PACKAGES+=("kde-baseapps") | |
| ;; | |
| *) | |
| MISSING_PACKAGES+=("kdialog") | |
| ;; | |
| esac | |
| fi | |
| if [ ${#MISSING_PACKAGES[@]} -eq 0 ]; then | |
| log_success "All dependencies already installed!" | |
| else | |
| log_info "Missing packages: ${MISSING_PACKAGES[*]}" | |
| case $PKG_MANAGER in | |
| dnf) | |
| log_info "Will install packages using DNF" | |
| run_with_sudo "Install packages: ${MISSING_PACKAGES[*]}" \ | |
| "sudo dnf install -y ${MISSING_PACKAGES[*]}" | |
| check_error "Failed to install packages with dnf" | |
| ;; | |
| apt) | |
| log_info "Will install packages using APT" | |
| run_with_sudo "Update package list" \ | |
| "sudo apt update" | |
| check_error "Failed to update apt" | |
| run_with_sudo "Install packages: ${MISSING_PACKAGES[*]}" \ | |
| "sudo apt install -y ${MISSING_PACKAGES[*]}" | |
| check_error "Failed to install packages with apt" | |
| ;; | |
| pacman) | |
| log_info "Will install packages using Pacman" | |
| run_with_sudo "Install packages: ${MISSING_PACKAGES[*]}" \ | |
| "sudo pacman -S --noconfirm ${MISSING_PACKAGES[*]}" | |
| check_error "Failed to install packages with pacman" | |
| ;; | |
| esac | |
| fi | |
| # Configure firewall | |
| echo "" | |
| echo -e "${GREEN}[2/4] Configuring firewall...${NC}" | |
| configure_firewall | |
| # Verify all required commands are available | |
| echo "" | |
| log_info "Verifying installed dependencies..." | |
| DEPS_OK=true | |
| validate_command "qrencode" "qrencode" || DEPS_OK=false | |
| validate_command "python3" "python3" || DEPS_OK=false | |
| validate_command "zip" "zip" || DEPS_OK=false | |
| validate_command "kdialog" "kdialog or kde-baseapps" || DEPS_OK=false | |
| if [ "$DEPS_OK" = false ]; then | |
| log_error "One or more required dependencies are missing" | |
| exit 1 | |
| fi | |
| # Test Python socket module | |
| log_info "Testing Python socket module..." | |
| if python3 -c "import socket" 2>/dev/null; then | |
| log_success "Python socket module available" | |
| else | |
| log_error "Python socket module not available" | |
| exit 1 | |
| fi | |
| # Create directories | |
| echo "" | |
| echo -e "${GREEN}[3/4] Creating directories...${NC}" | |
| log_info "Creating required directories..." | |
| BIN_DIR="$HOME/.local/bin" | |
| SERVICEMENU_DIR="$HOME/.local/share/kio/servicemenus" | |
| log_info "Creating directory: $BIN_DIR" | |
| if mkdir -p "$BIN_DIR" 2>/dev/null; then | |
| log_success "Created/verified: $BIN_DIR" | |
| else | |
| check_error "Failed to create $BIN_DIR" | |
| fi | |
| log_info "Creating directory: $SERVICEMENU_DIR" | |
| if mkdir -p "$SERVICEMENU_DIR" 2>/dev/null; then | |
| log_success "Created/verified: $SERVICEMENU_DIR" | |
| else | |
| check_error "Failed to create $SERVICEMENU_DIR" | |
| fi | |
| # Check write permissions | |
| log_info "Checking write permissions..." | |
| if [ -w "$BIN_DIR" ]; then | |
| log_success "Write permission OK: $BIN_DIR" | |
| else | |
| log_error "No write permission: $BIN_DIR" | |
| exit 1 | |
| fi | |
| if [ -w "$SERVICEMENU_DIR" ]; then | |
| log_success "Write permission OK: $SERVICEMENU_DIR" | |
| else | |
| log_error "No write permission: $SERVICEMENU_DIR" | |
| exit 1 | |
| fi | |
| # Create the main share script | |
| echo "" | |
| echo -e "${GREEN}[4/4] Creating share script...${NC}" | |
| log_info "Creating main share script..." | |
| SHARE_SCRIPT="$BIN_DIR/kde-share-http.sh" | |
| log_info "Target: $SHARE_SCRIPT" | |
| cat > "$SHARE_SCRIPT" << SCRIPT_EOF | |
| #!/bin/bash | |
| ############################################################################### | |
| # KDE Share via HTTP - Main Script | |
| # Shares files/folders via ephemeral HTTP server with QR code | |
| ############################################################################### | |
| # Configuration | |
| PORT_RANGE_START=25000 | |
| PORT_RANGE_END=25100 | |
| # Simple logging | |
| log_msg() { | |
| echo "[\$(date '+%H:%M:%S')] \$1" | |
| } | |
| log_msg "=== KDE Share via HTTP - Session Start ===" | |
| # Validate required commands | |
| MISSING_DEPS=() | |
| for cmd in kdialog zip qrencode python3; do | |
| if ! command -v "\$cmd" &> /dev/null; then | |
| MISSING_DEPS+=("\$cmd") | |
| log_msg "ERROR: Missing required command: \$cmd" | |
| fi | |
| done | |
| if [ \${#MISSING_DEPS[@]} -gt 0 ]; then | |
| log_msg "ERROR: Missing dependencies: \${MISSING_DEPS[*]}" | |
| kdialog --error "Missing required commands: \${MISSING_DEPS[*]}\\n\\nPlease run the installer again." 2>/dev/null || \ | |
| echo "ERROR: Missing dependencies: \${MISSING_DEPS[*]}" | |
| exit 1 | |
| fi | |
| log_msg "All dependencies validated" | |
| # Check if file/folder was provided | |
| if [ -z "\$1" ]; then | |
| log_msg "ERROR: No file or folder provided" | |
| kdialog --error "No file or folder selected" 2>/dev/null | |
| exit 1 | |
| fi | |
| TARGET="\$1" | |
| log_msg "Target: \$TARGET" | |
| # Validate target exists | |
| if [ ! -e "\$TARGET" ]; then | |
| log_msg "ERROR: Target does not exist: \$TARGET" | |
| kdialog --error "File or folder not found:\\n\$TARGET" 2>/dev/null | |
| exit 1 | |
| fi | |
| # Check read permissions | |
| if [ ! -r "\$TARGET" ]; then | |
| log_msg "ERROR: No read permission for: \$TARGET" | |
| kdialog --error "Cannot read file or folder:\\n\$TARGET\\n\\nPermission denied." 2>/dev/null | |
| exit 1 | |
| fi | |
| BASENAME=\$(basename "\$TARGET") | |
| log_msg "Basename: \$BASENAME" | |
| # Get file/folder size for validation | |
| if [ -d "\$TARGET" ]; then | |
| SIZE=\$(du -sb "\$TARGET" 2>/dev/null | awk '{print \$1}') | |
| log_msg "Directory size: \$SIZE bytes" | |
| else | |
| SIZE=\$(stat -c%s "\$TARGET" 2>/dev/null || stat -f%z "\$TARGET" 2>/dev/null) | |
| log_msg "File size: \$SIZE bytes" | |
| fi | |
| # Warn for large files (>100MB) | |
| if [ -n "\$SIZE" ] && [ "\$SIZE" -gt 104857600 ]; then | |
| log_msg "WARNING: Large file/folder (>100MB)" | |
| SIZE_MB=\$((SIZE / 1048576)) | |
| kdialog --warningyesno "This is a large file/folder (\$SIZE_MB MB).\\n\\nSharing may take time depending on network speed.\\n\\nContinue?" 2>/dev/null | |
| if [ \$? -ne 0 ]; then | |
| log_msg "User cancelled due to large size" | |
| exit 0 | |
| fi | |
| fi | |
| # Create temporary directory | |
| log_msg "Creating temporary directory..." | |
| TMPDIR=\$(mktemp -d 2>&1) | |
| if [ \$? -ne 0 ] || [ ! -d "\$TMPDIR" ]; then | |
| log_msg "ERROR: Failed to create temporary directory" | |
| kdialog --error "Failed to create temporary directory" 2>/dev/null | |
| exit 1 | |
| fi | |
| log_msg "Temporary directory: \$TMPDIR" | |
| # Ensure cleanup on exit | |
| cleanup() { | |
| local exit_code=\$? | |
| log_msg "Cleanup initiated (exit code: \$exit_code)" | |
| if [ -n "\$SERVER_PID" ]; then | |
| log_msg "Killing server PID: \$SERVER_PID" | |
| kill \$SERVER_PID 2>/dev/null || true | |
| sleep 1 | |
| kill -9 \$SERVER_PID 2>/dev/null || true | |
| fi | |
| if [ -d "\$TMPDIR" ]; then | |
| log_msg "Removing temporary directory: \$TMPDIR" | |
| rm -rf "\$TMPDIR" 2>/dev/null || log_msg "WARNING: Failed to remove \$TMPDIR" | |
| fi | |
| log_msg "=== Session End ===" | |
| } | |
| trap cleanup EXIT INT TERM | |
| # Handle folders by zipping | |
| if [ -d "\$TARGET" ]; then | |
| SHARE_NAME="\$BASENAME.zip" | |
| log_msg "Target is directory, will create: \$SHARE_NAME" | |
| # Show progress notification | |
| kdialog --title "Preparing..." --passivepopup "Zipping folder: \$BASENAME\\nThis may take a moment..." 3 2>/dev/null & | |
| # Zip the folder | |
| log_msg "Starting zip operation..." | |
| cd "\$(dirname "\$TARGET")" 2>&1 | |
| if [ \$? -ne 0 ]; then | |
| log_msg "ERROR: Failed to change to directory: \$(dirname "\$TARGET")" | |
| kdialog --error "Failed to access directory" 2>/dev/null | |
| exit 1 | |
| fi | |
| ZIP_OUTPUT=\$(zip -r "\$TMPDIR/\$SHARE_NAME" "\$BASENAME" 2>&1) | |
| ZIP_EXIT=\$? | |
| if [ \$ZIP_EXIT -ne 0 ]; then | |
| log_msg "ERROR: Zip failed with exit code: \$ZIP_EXIT" | |
| log_msg "Zip output: \$ZIP_OUTPUT" | |
| kdialog --error "Failed to create zip file:\\n\\n\${ZIP_OUTPUT:0:200}" 2>/dev/null | |
| exit 1 | |
| fi | |
| # Verify zip was created | |
| if [ ! -f "\$TMPDIR/\$SHARE_NAME" ]; then | |
| log_msg "ERROR: Zip file not found after creation" | |
| kdialog --error "Zip file was not created successfully" 2>/dev/null | |
| exit 1 | |
| fi | |
| ZIP_SIZE=\$(stat -c%s "\$TMPDIR/\$SHARE_NAME" 2>/dev/null || stat -f%z "\$TMPDIR/\$SHARE_NAME" 2>/dev/null) | |
| log_msg "Zip created successfully: \$ZIP_SIZE bytes" | |
| else | |
| # For files, create a symlink | |
| SHARE_NAME="\$BASENAME" | |
| log_msg "Target is file, creating symlink: \$SHARE_NAME" | |
| REAL_PATH=\$(realpath "\$TARGET" 2>&1) | |
| if [ \$? -ne 0 ]; then | |
| log_msg "ERROR: Failed to get realpath: \$REAL_PATH" | |
| kdialog --error "Failed to resolve file path" 2>/dev/null | |
| exit 1 | |
| fi | |
| ln -s "\$REAL_PATH" "\$TMPDIR/\$SHARE_NAME" 2>&1 | |
| if [ \$? -ne 0 ]; then | |
| log_msg "ERROR: Failed to create symlink" | |
| kdialog --error "Failed to prepare file for sharing" 2>/dev/null | |
| exit 1 | |
| fi | |
| log_msg "Symlink created: \$TMPDIR/\$SHARE_NAME -> \$REAL_PATH" | |
| fi | |
| # Find available port in configured range | |
| log_msg "Finding available port in range \$PORT_RANGE_START-\$PORT_RANGE_END..." | |
| PORT=\$(python3 -c ' | |
| import socket | |
| import sys | |
| start = int(sys.argv[1]) | |
| end = int(sys.argv[2]) | |
| for p in range(start, end + 1): | |
| try: | |
| s = socket.socket() | |
| s.bind(("", p)) | |
| s.close() | |
| print(p) | |
| exit(0) | |
| except OSError: | |
| continue | |
| print("NO_FREE_PORT", end="") | |
| exit(1) | |
| ' \$PORT_RANGE_START \$PORT_RANGE_END 2>&1) | |
| if [[ "\$PORT" == "NO_FREE_PORT" || -z "\$PORT" || "\$PORT" -lt \$PORT_RANGE_START || "\$PORT" -gt \$PORT_RANGE_END ]]; then | |
| log_msg "ERROR: No free port found in range \$PORT_RANGE_START-\$PORT_RANGE_END" | |
| kdialog --error "No available port in allowed range (\$PORT_RANGE_START-\$PORT_RANGE_END).\\n\\nToo many shares running or firewall not configured?" 2>/dev/null | |
| exit 1 | |
| fi | |
| log_msg "Selected port: \$PORT" | |
| # Get local IP (try multiple methods) | |
| log_msg "Detecting local IP address..." | |
| LOCAL_IP=\$(hostname -I 2>/dev/null | awk '{print \$1}') | |
| if [ -z "\$LOCAL_IP" ]; then | |
| LOCAL_IP=\$(ip route get 1 2>/dev/null | awk '{print \$7; exit}') | |
| fi | |
| if [ -z "\$LOCAL_IP" ]; then | |
| LOCAL_IP=\$(ip addr show 2>/dev/null | grep 'inet ' | grep -v '127.0.0.1' | head -1 | awk '{print \$2}' | cut -d'/' -f1) | |
| fi | |
| if [ -z "\$LOCAL_IP" ]; then | |
| log_msg "WARNING: Could not detect IP, using localhost" | |
| LOCAL_IP="localhost" | |
| fi | |
| log_msg "Using IP: \$LOCAL_IP" | |
| # Build URL | |
| URL="http://\$LOCAL_IP:\$PORT/\$SHARE_NAME" | |
| log_msg "Share URL: \$URL" | |
| # Generate QR code as UTF8 for direct display | |
| log_msg "Generating QR code (UTF8 format)..." | |
| QR_CODE=\$(qrencode -t UTF8 "\$URL" 2>&1) | |
| QR_EXIT=\$? | |
| if [ \$QR_EXIT -ne 0 ]; then | |
| log_msg "ERROR: QR code generation failed (exit: \$QR_EXIT)" | |
| log_msg "Output: \$QR_CODE" | |
| kdialog --error "Failed to generate QR code:\\n\\n\${QR_CODE:0:200}" 2>/dev/null | |
| exit 1 | |
| fi | |
| if [ -z "\$QR_CODE" ]; then | |
| log_msg "ERROR: QR code output is empty" | |
| kdialog --error "QR code generation produced no output" 2>/dev/null | |
| exit 1 | |
| fi | |
| log_msg "QR code generated successfully" | |
| # Start HTTP server in background with redirect capability | |
| log_msg "Starting HTTP server with redirect..." | |
| cd "\$TMPDIR" 2>&1 | |
| if [ \$? -ne 0 ]; then | |
| log_msg "ERROR: Failed to cd to \$TMPDIR" | |
| kdialog --error "Failed to access temporary directory" 2>/dev/null | |
| exit 1 | |
| fi | |
| # Create custom HTTP server script that redirects root path to the shared file | |
| SERVER_PY="\$TMPDIR/http_server.py" | |
| cat > "\$SERVER_PY" << 'PY_EOF' | |
| import http.server | |
| import socketserver | |
| import sys | |
| import urllib.parse | |
| PORT = int(sys.argv[1]) | |
| FILENAME = sys.argv[2] | |
| class RedirectHandler(http.server.SimpleHTTPRequestHandler): | |
| def do_GET(self): | |
| if self.path == '/': | |
| # Redirect root path to the shared file with proper URL encoding | |
| self.send_response(301) | |
| self.send_header('Location', f'/{urllib.parse.quote(FILENAME)}') | |
| self.end_headers() | |
| else: | |
| # Serve the requested file normally | |
| super().do_GET() | |
| with socketserver.TCPServer(('', PORT), RedirectHandler) as httpd: | |
| httpd.serve_forever() | |
| PY_EOF | |
| # Verify server script was created | |
| if [ ! -f "\$SERVER_PY" ]; then | |
| log_msg "ERROR: Failed to create server script: \$SERVER_PY" | |
| kdialog --error "Failed to create HTTP server script" 2>/dev/null | |
| exit 1 | |
| fi | |
| # Start the custom server | |
| python3 "\$SERVER_PY" \$PORT "\$SHARE_NAME" > /dev/null 2>&1 & | |
| SERVER_PID=\$! | |
| log_msg "Server started with PID: \$SERVER_PID (redirecting / to \$SHARE_NAME)" | |
| # Give server a moment to start | |
| log_msg "Waiting for server to start..." | |
| sleep 2 | |
| # Check if server started successfully | |
| if ! kill -0 \$SERVER_PID 2>/dev/null; then | |
| log_msg "ERROR: Server process died immediately" | |
| kdialog --error "Failed to start HTTP server on port \$PORT\\n\\nPort may be in use or firewall blocking." 2>/dev/null | |
| exit 1 | |
| fi | |
| log_msg "Server appears to be running successfully" | |
| # Display QR code and URL directly in kdialog | |
| log_msg "Showing user interface..." | |
| # Build display message with QR code | |
| DISPLAY_MSG="<html> | |
| <h2>🌐 Sharing: \$SHARE_NAME</h2> | |
| <p><b>Server running at:</b></p> | |
| <pre>\$URL</pre> | |
| <p style='font-size: small;'>💡 Tip: Visiting <b>http://\$LOCAL_IP:\$PORT</b> will auto-redirect to the file</p> | |
| <br/> | |
| <p><b>Scan QR code with your phone:</b></p> | |
| <pre>\$QR_CODE</pre> | |
| <br/> | |
| <p><b>Details:</b></p> | |
| <ul> | |
| <li>Port: <b>\$PORT</b></li> | |
| <li>Local IP: <b>\$LOCAL_IP</b></li> | |
| <li>File will stop being shared when you close this dialog</li> | |
| </ul> | |
| <br/> | |
| <p style='color: red;'><b>⚠️ Anyone on your local network can access this file!</b></p> | |
| </html>" | |
| kdialog --title "Share via HTTP" --msgbox "\$DISPLAY_MSG" 2>/dev/null | |
| DIALOG_RESULT=\$? | |
| log_msg "Dialog closed with result: \$DIALOG_RESULT" | |
| log_msg "User closed dialog, initiating shutdown" | |
| # Cleanup handled by trap | |
| echo "Sharing stopped." | |
| log_msg "Script completed normally" | |
| SCRIPT_EOF | |
| check_error "Failed to write share script to $SHARE_SCRIPT" | |
| # Verify script was created | |
| if [ ! -f "$SHARE_SCRIPT" ]; then | |
| log_error "Script file not found after creation: $SHARE_SCRIPT" | |
| exit 1 | |
| fi | |
| log_success "Share script created: $SHARE_SCRIPT" | |
| # Make script executable | |
| log_info "Making script executable..." | |
| chmod +x "$SHARE_SCRIPT" 2>/dev/null | |
| check_error "Failed to make $SHARE_SCRIPT executable" | |
| # Verify script is executable | |
| if [ ! -x "$SHARE_SCRIPT" ]; then | |
| log_error "Script is not executable after chmod: $SHARE_SCRIPT" | |
| exit 1 | |
| fi | |
| log_success "Script is now executable" | |
| # Create KDE service menu entry | |
| echo "" | |
| echo -e "${GREEN}[5/5] Creating KDE service menu entry...${NC}" | |
| log_info "Creating KDE service menu..." | |
| SERVICE_MENU="$SERVICEMENU_DIR/kde-share-via-http.desktop" | |
| log_info "Target: $SERVICE_MENU" | |
| # Get absolute path for the script | |
| SCRIPT_ABSOLUTE_PATH="$HOME/.local/bin/kde-share-http.sh" | |
| cat > "$SERVICE_MENU" << DESKTOP_EOF | |
| [Desktop Entry] | |
| Type=Service | |
| ServiceTypes=KonqPopupMenu/Plugin | |
| MimeType=application/octet-stream;inode/directory;image/*;video/*;audio/*;text/*;application/pdf;application/zip;application/x-tar;application/x-rar;application/x-7z-compressed; | |
| Actions=shareViaHTTP; | |
| [Desktop Action shareViaHTTP] | |
| Name=Share via HTTP (QR Code) | |
| Name[en_US]=Share via HTTP (QR Code) | |
| Icon=network-server | |
| Exec=/bin/bash $SCRIPT_ABSOLUTE_PATH %f | |
| DESKTOP_EOF | |
| check_error "Failed to create service menu: $SERVICE_MENU" | |
| # Set executable permission on the .desktop file (CRITICAL for KDE) | |
| log_info "Setting executable permission on service menu..." | |
| chmod +x "$SERVICE_MENU" | |
| PERM_EXIT=$? | |
| if [ $PERM_EXIT -ne 0 ]; then | |
| log_error "Failed to set executable permission on $SERVICE_MENU (exit: $PERM_EXIT)" | |
| exit 1 | |
| fi | |
| # Verify permissions were set | |
| if [ ! -x "$SERVICE_MENU" ]; then | |
| log_error "Permission verification failed - file is not executable: $SERVICE_MENU" | |
| ls -l "$SERVICE_MENU" | |
| exit 1 | |
| fi | |
| log_success "Service menu is now executable" | |
| # Verify service menu was created | |
| if [ ! -f "$SERVICE_MENU" ]; then | |
| log_error "Service menu file not found after creation: $SERVICE_MENU" | |
| exit 1 | |
| fi | |
| log_success "Service menu created: $SERVICE_MENU" | |
| # Validate desktop file syntax (optional, don't fail if not available) | |
| if command -v desktop-file-validate &> /dev/null; then | |
| log_info "Validating desktop file..." | |
| VALIDATE_OUTPUT=$(desktop-file-validate "$SERVICE_MENU" 2>&1) | |
| VALIDATE_EXIT=$? | |
| if [ $VALIDATE_EXIT -eq 0 ]; then | |
| log_success "Desktop file validation passed" | |
| else | |
| log_warning "Desktop file validation warnings:" | |
| echo "$VALIDATE_OUTPUT" | |
| fi | |
| fi | |
| # Update KDE service menu cache | |
| echo "" | |
| log_info "Updating KDE service menu cache..." | |
| CACHE_UPDATED=false | |
| if command -v kbuildsycoca6 &> /dev/null; then | |
| log_info "Found kbuildsycoca6 (KDE Frameworks 6)" | |
| kbuildsycoca6 --noincremental 2>&1 | |
| if [ $? -eq 0 ]; then | |
| log_success "KDE cache updated successfully (KF6)" | |
| CACHE_UPDATED=true | |
| else | |
| log_warning "kbuildsycoca6 returned non-zero exit code" | |
| fi | |
| elif command -v kbuildsycoca5 &> /dev/null; then | |
| log_info "Found kbuildsycoca5 (KDE Frameworks 5)" | |
| kbuildsycoca5 --noincremental 2>&1 | |
| if [ $? -eq 0 ]; then | |
| log_success "KDE cache updated successfully (KF5)" | |
| CACHE_UPDATED=true | |
| else | |
| log_warning "kbuildsycoca5 returned non-zero exit code" | |
| fi | |
| else | |
| log_warning "Neither kbuildsycoca5 nor kbuildsycoca6 found" | |
| log_warning "You may need to log out and back in for changes to take effect" | |
| fi | |
| # Final verification | |
| echo "" | |
| log_info "Running final verification..." | |
| VERIFICATION_OK=true | |
| if [ ! -x "$SHARE_SCRIPT" ]; then | |
| log_error "Share script is not executable: $SHARE_SCRIPT" | |
| VERIFICATION_OK=false | |
| fi | |
| if [ ! -f "$SERVICE_MENU" ]; then | |
| log_error "Service menu file missing: $SERVICE_MENU" | |
| VERIFICATION_OK=false | |
| fi | |
| if [ "$VERIFICATION_OK" = false ]; then | |
| log_error "Verification failed" | |
| exit 1 | |
| fi | |
| log_success "All verification checks passed" | |
| # Summary | |
| echo "" | |
| echo -e "${GREEN}========================================" | |
| echo "Installation Complete!" | |
| echo "========================================${NC}" | |
| echo "" | |
| echo "✅ Dependencies installed and verified" | |
| echo "✅ Firewall configured (UFW/firewalld port range: $PORT_RANGE_START-$PORT_RANGE_END)" | |
| echo "✅ Share script created at: $SHARE_SCRIPT" | |
| echo "✅ KDE service menu entry created at: $SERVICE_MENU" | |
| echo "✅ HTTP server redirects root path to shared file" | |
| echo " (e.g., http://IP:PORT/ → http://IP:PORT/filename)" | |
| if [ "$CACHE_UPDATED" = true ]; then | |
| echo "✅ KDE cache updated" | |
| else | |
| echo "⚠️ KDE cache update may be required" | |
| fi | |
| echo "" | |
| echo "📱 How to use:" | |
| echo " 1. Right-click any file or folder in Dolphin (KDE file manager)" | |
| echo " 2. Look for 'Share → Share via HTTP (QR Code)'" | |
| echo " 3. Scan the QR code with your phone camera" | |
| echo " 4. Download the file from your phone!" | |
| echo " 5. OR simply visit http://IP:PORT/ to auto-download the file" | |
| echo "" | |
| echo -e "${YELLOW}⚠️ Security Note:${NC}" | |
| echo " • Files are shared on your local network only" | |
| echo " • Anyone on the same WiFi/network can access shared files" | |
| echo " • The server stops automatically when you close the dialog" | |
| echo " • Each share session creates a new temporary server" | |
| echo " • Port range $PORT_RANGE_START-$PORT_RANGE_END is opened in firewall" | |
| echo "" | |
| echo "🔧 Manual testing:" | |
| echo " $SHARE_SCRIPT /path/to/file" | |
| echo "" | |
| if [ "$CACHE_UPDATED" = false ]; then | |
| echo -e "${YELLOW}⚠️ Note: You may need to restart Dolphin or log out/in for the${NC}" | |
| echo -e "${YELLOW} context menu entry to appear.${NC}" | |
| echo "" | |
| fi | |
| echo "" | |
| echo -e "${GREEN}🔧 To activate the changes:${NC}" | |
| echo " 1. Update KDE cache:" | |
| if command -v kbuildsycoca6 &> /dev/null; then | |
| echo " kbuildsycoca6 --noincremental" | |
| elif command -v kbuildsycoca5 &> /dev/null; then | |
| echo " kbuildsycoca5 --noincremental" | |
| fi | |
| echo " 2. Restart Dolphin:" | |
| echo " killall dolphin && dolphin &" | |
| echo "" | |
| echo -e "${GREEN}Installation completed successfully!${NC}" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment