Skip to content

Instantly share code, notes, and snippets.

@tzhvh
Last active November 9, 2025 03:49
Show Gist options
  • Select an option

  • Save tzhvh/d33340e914596c5044676479700d16ea to your computer and use it in GitHub Desktop.

Select an option

Save tzhvh/d33340e914596c5044676479700d16ea to your computer and use it in GitHub Desktop.
Dolphin context menu installer: KDE file share via HTTP with QR code
#!/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