Skip to content

Zero-touch USB media ingest for Proxmox. Auto-mount, sync to NAS, monitor via React dashboard. Built for Jellyfin/Plex home labs. One-line install.

Notifications You must be signed in to change notification settings

TheLastDruid/mediaIngest

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

119 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸš€ Proxmox USB Media Ingest Station

Transform your Proxmox node into an automated media ingest powerhouse

License: MIT GitHub Stars GitHub Issues PRs Welcome Made with Love

Stop manually mounting drives. Turn your Proxmox node into a dedicated Ingest Station.

Features β€’ Quick Start β€’ How It Works β€’ Screenshots β€’ Contributing

Dashboard Preview


🎯 The Problem

Managing media for self-hosted platforms like Jellyfin, Plex, or Emby shouldn't require:

  • Manually mounting USB drives
  • Navigating terminal commands
  • Tracking sync progress with rsync flags
  • Dealing with NTFS permissions
  • Wondering if files transferred successfully

This project solves all of that.


✨ Features

πŸ”Œ Universal Drive Support

  • Automatically detects any USB storage device
  • High-performance ntfs3 kernel driver (kernel 5.15+)
  • Graceful fallback to ntfs-3g for older systems
  • No manual mounting requiredβ€”ever

πŸ“Š Real-Time Dashboard

  • Modern Bento Grid UI with Framer Motion animations
  • Live progress bars showing rsync transfer speed
  • Dark mode optimized for home lab aesthetics
  • Mobile responsive - monitor from your phone
  • Transfer history with timestamps and status

⚑ Zero-Touch Automation

  • Plug & Play: Insert USB β†’ Auto-mount β†’ Auto-sync β†’ Auto-unmount
  • Intelligent NAS detection - scans /mnt/pve/* and /mnt/*
  • Auto-provisions Media folder with proper permissions
  • Concurrent USB support - multiple drives at once
  • Smart folder detection - Movies, Series, Anime (case-insensitive)

πŸ—οΈ Production-Grade Architecture

  • LXC containerization for isolation and security
  • Privileged container with bind mounts to NAS
  • systemd service integration for reliability
  • udev rules for hardware-level USB detection
  • Comprehensive logging for debugging

🎬 Jellyfin/Plex Ready

  • Designed for Jellyfin media servers
  • Compatible with Plex, Emby, Kodi, and more
  • Organizes content by type (Movies/Series/Anime)
  • Preserves metadata and folder structure
  • --ignore-existing prevents overwrites

πŸ”§ Developer Friendly

  • React 18 + Vite 4 frontend
  • Express 4 backend with WebSocket-like polling
  • Tailwind CSS 3 for styling
  • Framer Motion 10 for animations
  • Single-file installer - no dependencies on host

πŸš€ Quick Start

Prerequisites

  • Proxmox VE 7.0+ (tested on 8.x)
  • NAS mounted to /mnt/pve/* or /mnt/*
  • Internet connection (for initial template download)
  • Root access to Proxmox host

The Magic One-Liner

bash -c "$(wget -qLO - https://raw.githubusercontent.com/TheLastDruid/mediaIngest/main/install.sh)"

That's it. The installer will:

  1. βœ… Scan your mounted storage and let you select destination
  2. βœ… Auto-detect next available container ID
  3. βœ… Download Debian 12 template (if needed)
  4. βœ… Create privileged LXC container with bind mounts
  5. βœ… Deploy React dashboard on port 3000
  6. βœ… Configure udev rules for USB detection
  7. βœ… Start all services automatically

Installation time: 5-10 minutes
User interaction: One menu selection (choose NAS destination)

Alternative Installation (curl)

bash -c "$(curl -fsSL https://raw.githubusercontent.com/TheLastDruid/mediaIngest/main/install.sh)"

πŸ› οΈ How It Works

πŸ” Click to expand architecture details

The Pipeline

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ 1. USB DETECTION (Proxmox Host)                                β”‚
β”‚    β€’ udev rule catches: KERNEL=="sd[a-z]"                       β”‚
β”‚    β€’ Triggers: /usr/local/bin/usb-trigger.sh                    β”‚
β”‚    β€’ Mounts to: /mnt/usb-pass (ntfs3 or ntfs-3g)               β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ 2. CONTAINER EXECUTION (LXC)                                    β”‚
β”‚    β€’ systemd-run launches: ingest-media.sh                      β”‚
β”‚    β€’ Scans USB for: /Media folder (case-insensitive)           β”‚
β”‚    β€’ Syncs: Movies, Series, Anime folders                       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ 3. REAL-TIME DASHBOARD (React + Express)                        β”‚
β”‚    β€’ Backend polls: /var/log/media-ingest.log                   β”‚
β”‚    β€’ Parses rsync output with: tr '\r' '\n'                    β”‚
β”‚    β€’ Frontend refreshes: Every 2 seconds via fetch()            β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                              ↓
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ 4. DESTINATION (Your NAS)                                       β”‚
β”‚    β€’ Files land in: $NAS_PATH/Media/{Movies,Series,Anime}      β”‚
β”‚    β€’ Permissions: 777 (configurable)                            β”‚
β”‚    β€’ Jellyfin/Plex auto-detects new content                     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Security Model

  • Privileged container required for bind mounts
  • Isolated network (DHCP, no exposed services outside LAN)
  • Read-only USB access (syncs don't modify source)
  • No root privileges needed for dashboard access
  • systemd sandboxing for ingest script

Why This Approach?

βœ… Hardware-level detection - udev rules never miss a device
βœ… Container isolation - dashboard crashes don't affect Proxmox
βœ… Scalable - handles multiple USBs with unique systemd units
βœ… Debuggable - comprehensive logs at every stage
βœ… Maintainable - single bash script installer


βš™οΈ Configuration

Change NAS Destination

After installation, edit the LXC container's bind mount:

# Method 1: Reinstall (recommended)
pct destroy [CT_ID]
bash -c "$(wget -qLO - https://raw.githubusercontent.com/TheLastDruid/mediaIngest/main/install.sh)"
# Select different destination from menu

# Method 2: Manual edit
pct set [CT_ID] -mp1 /mnt/pve/NEW_NAS_PATH,mp=/media/nas
pct reboot [CT_ID]

Add Custom Folders

Edit /usr/local/bin/ingest-media.sh inside container:

pct enter [CT_ID]
nano /usr/local/bin/ingest-media.sh

# Add at the end (before "Ingest Complete"):
sync_folder "Documentaries"
sync_folder "Music"
sync_folder "Photos"

Change Dashboard Port

pct enter [CT_ID]
nano /etc/systemd/system/mediaingest-dashboard.service

# Add under [Service]:
Environment="PORT=8080"

systemctl daemon-reload
systemctl restart mediaingest-dashboard

Modify rsync Flags

Default: -rvh -W --inplace --progress --ignore-existing

pct enter [CT_ID]
nano /usr/local/bin/ingest-media.sh

# Change rsync line to:
rsync -rvhz -W --inplace --progress --ignore-existing  # Add compression
rsync -rvhc -W --inplace --progress --ignore-existing  # Add checksums

πŸ“Έ Screenshots

Desktop View

Desktop Dashboard Full-featured Bento Grid dashboard with statistics, storage health, device controls, and transfer history

Mobile View

Mobile Dashboard

Fully responsive mobile interface - monitor transfers from anywhere

Key Features Shown

  • βœ… Real-time transfer statistics (100 files, 141.37 GB transferred)
  • βœ… Storage health monitoring (NAS 12% used, USB 16% used)
  • βœ… Device controls (Samsung T7 Shield detected with Eject/Scan buttons)
  • βœ… Complete transfer history with timestamps and status indicators
  • βœ… Dark mode optimized UI with modern Bento Grid layout
  • βœ… Smooth animations and professional design

πŸ§ͺ Testing & Validation

Test USB Detection

# Monitor udev events
udevadm monitor --environment --udev

# Insert USB and check logs
tail -f /var/log/usb-ingest.log

Test Ingest Script Manually

# Simulate USB insertion
/usr/local/bin/usb-trigger.sh /dev/sdX

# Or inside container
pct enter [CT_ID]
/usr/local/bin/ingest-media.sh

Check Dashboard Health

# Service status
pct exec [CT_ID] -- systemctl status mediaingest-dashboard

# Live logs
pct exec [CT_ID] -- journalctl -u mediaingest-dashboard -f

# Network check
pct exec [CT_ID] -- netstat -tlnp | grep 3000

🀝 Contributing

We welcome contributions from the community! Whether it's:

  • πŸ› Bug reports - Found an issue? Open an issue
  • πŸ’‘ Feature requests - Have an idea? Start a discussion
  • πŸ”§ Pull requests - Want to contribute code? See CONTRIBUTING.md
  • πŸ“š Documentation - Improve the docs or add examples
  • ⭐ Star the repo - Show your support!

Development Setup

# Clone repository
git clone https://github.com/TheLastDruid/mediaIngest.git
cd mediaIngest

# Backend
npm install
npm start  # Port 3000

# Frontend (separate terminal)
cd client
npm install
npm run dev  # Port 5173

Contribution Workflow

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/AmazingFeature)
  3. Commit your changes (git commit -m 'Add some AmazingFeature')
  4. Push to the branch (git push origin feature/AmazingFeature)
  5. Open a Pull Request

See CONTRIBUTING.md for detailed guidelines.


πŸ›‘οΈ Security

This project takes security seriously. If you discover a security vulnerability:

  1. DO NOT open a public issue
  2. Email: Create a private security advisory
  3. Include: Description, steps to reproduce, potential impact

See SECURITY.md for our full security policy.


πŸ“œ License

This project is licensed under the MIT License - see the LICENSE file for details.

Copyright (c) 2025 Spookyfunck

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction...

πŸ™ Acknowledgments

  • tteck - For inspiration from Proxmox VE Helper-Scripts
  • Proxmox Team - For the amazing virtualization platform
  • React & Vite - For the modern frontend tooling
  • Framer Motion - For beautiful animations
  • Tailwind CSS - For rapid UI development
  • AI Assistance - Frontend UI was vibe-coded with AI pair programming
  • Home Lab Community - For testing and feedback

πŸ“¬ Support & Community


πŸ—ΊοΈ Roadmap

  • v3.1 - Multi-language support (i18n)
  • v3.2 - Docker version for non-Proxmox users
  • v3.3 - Plex/Jellyfin API integration (auto-scan)
  • v3.4 - Mobile app (React Native)
  • v4.0 - Cloud storage sync (S3, Backblaze B2)
  • v4.1 - Automatic media renaming (TheMovieDB API)
  • v4.2 - Duplicate detection and deduplication
  • v5.0 - ARM64 support (Raspberry Pi, Apple Silicon)

Vote on features in Discussions!


πŸ“Š Project Stats

GitHub Contributors GitHub Commit Activity GitHub Last Commit Lines of Code


Made with ❀️ by Spookyfunck

If this project saved you time, consider giving it a ⭐!

⬆ Back to Top