diff --git a/README.md b/README.md index e69de29..99f4652 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,82 @@ +# GMMC Media Server + +A high-performance, public media download server built with Rust and Axum. It is designed to serve extremely large files (100GB+) seamlessly and features a retro, Minecraft-themed web dashboard for easy access. + +## Features + +- **Blazing Fast & Lightweight**: Powered by Rust, Axum, and Tokio. +- **Large File Optimization**: Streams files using configurable chunk sizes (default 8MB) to handle massive files with low memory footprint. +- **Resume Support**: Full support for HTTP `Range` requests, allowing paused or interrupted downloads to be resumed. +- **Retro Dashboard**: A dynamic, Minecraft-style UI built with vanilla HTML/CSS and the VT323 font. Includes 1-click copy-to-clipboard commands for terminal downloading. +- **No Authentication Required**: Built for public, hassle-free access. +- **Security & Access Control**: Built-in security headers and optional IP whitelisting. + +## Prerequisites + +- [Rust & Cargo](https://rustup.rs/) (edition 2021) +- A `.env` file for server configuration +- A `media_config.json` file for file registry + +## Configuration + +### 1. Environment Variables (`.env`) +Create a `.env` file in the root directory: +```env +PORT=3000 +HOST=0.0.0.0 +# Request timeout in seconds (default 3600 for 1 hour) +REQUEST_TIMEOUT_SECS=3600 +# Streaming chunk size in KB (default 8192 for 8MB) +CHUNK_SIZE_KB=8192 +# Optional: comma-separated list of allowed IPs +# ALLOWED_IPS=127.0.0.1,192.168.1.100 +# Optional: Domain for generating absolute download URLs +# DOMAIN=https://downloads.example.com +``` + +### 2. Media Registry (`media_config.json`) +Define the files you want to host. Create a `media_config.json` in the root directory: +```json +{ + "files": { + "ubuntu-iso": { + "name": "Ubuntu 24.04 LTS", + "path": "/path/to/ubuntu-24.04-desktop-amd64.iso", + "description": "The latest LTS release of Ubuntu.", + "content_type": "application/x-iso9660-image" + }, + "large-dataset": { + "name": "AI Training Dataset", + "path": "./data/dataset.tar.gz", + "description": "Huge 150GB dataset for machine learning models." + } + } +} +``` + +## Running the Server + +1. Clone the repository and navigate to the project root. +2. Build and run the server: + ```bash + cargo run --release + ``` +3. Open your browser and navigate to `http://localhost:3000` (or your configured `HOST` and `PORT`). + +## Usage + +- **Web Dashboard**: Visit the root URL to view available files, their sizes, and descriptions. Click "Download" or copy the provided cURL commands. +- **Direct Download (Browser)**: `http://localhost:3000/download/{file_id}` +- **cURL Download**: + ```bash + curl -O http://localhost:3000/download/{file_id} + ``` + *(For resumable downloads via cURL, add `-C -`)* + +## Tech Stack + +- **[Rust](https://www.rust-lang.org/)** +- **[Axum](https://github.com/tokio-rs/axum)** (Web Framework) +- **[Tokio](https://tokio.rs/)** (Asynchronous Runtime) +- **[Tower](https://github.com/tower-rs/tower)** (Middleware & Networking) +- **[Serde](https://serde.rs/)** (Serialization/Deserialization) diff --git a/src/main.rs b/src/main.rs index 120b8bb..d486523 100644 --- a/src/main.rs +++ b/src/main.rs @@ -76,6 +76,7 @@ async fn main() { let app = Router::new() .route("/", get(list_files)) + .route("/image.png", get(serve_image)) .route("/download/:file_id", get(download_file)) .route("/health", get(health_check)) .layer(middleware::from_fn_with_state( @@ -207,6 +208,16 @@ async fn health_check() -> impl IntoResponse { (StatusCode::OK, "OK") } +async fn serve_image() -> impl IntoResponse { + match tokio::fs::read("templates/image.png").await { + Ok(bytes) => ( + [(header::CONTENT_TYPE, "image/png")], + bytes, + ).into_response(), + Err(_) => StatusCode::NOT_FOUND.into_response(), + } +} + async fn list_files(State(state): State) -> Html { // Read template from file let template_path = "templates/index.html"; diff --git a/templates/image.png b/templates/image.png new file mode 100644 index 0000000..4519784 Binary files /dev/null and b/templates/image.png differ diff --git a/templates/index.html b/templates/index.html index f6e90ea..74ee41e 100644 --- a/templates/index.html +++ b/templates/index.html @@ -32,11 +32,10 @@ body { font-family: 'VT323', monospace; background-color: var(--mc-bg); - background-image: - linear-gradient(45deg, #1a1a1a 25%, transparent 25%, transparent 75%, #1a1a1a 75%, #1a1a1a), - linear-gradient(45deg, #1a1a1a 25%, transparent 25%, transparent 75%, #1a1a1a 75%, #1a1a1a); - background-size: 40px 40px; - background-position: 0 0, 20px 20px; + background-image: url('/image.png'); + background-size: cover; + background-position: center; + background-attachment: fixed; color: var(--mc-text); margin: 0; padding: 24px; @@ -48,6 +47,11 @@ .container { max-width: 900px; margin: 0 auto; + background: rgba(17, 17, 17, 0.85); + padding: 2rem; + border: 4px solid #000; + box-shadow: inset 4px 4px 0px rgba(255, 255, 255, 0.15), inset -4px -4px 0px rgba(0, 0, 0, 0.6); + backdrop-filter: blur(2px); } h1 { @@ -63,6 +67,16 @@ letter-spacing: 2px; } + a { + color: var(--mc-gold); + text-decoration: none; + text-shadow: var(--mc-text-shadow); + } + + a:hover { + color: var(--mc-green); + } + /* Stats Grid - Inventory Style */ .stats { display: grid; @@ -72,10 +86,10 @@ } .stat-card { - background: #212121; + background: rgba(33, 33, 33, 0.9); padding: 1rem; - border: 2px solid #555; - box-shadow: inset 2px 2px 0px #000, inset -2px -2px 0px #333; + border: 2px solid #000; + box-shadow: inset 2px 2px 0px #444, inset -2px -2px 0px #111; display: flex; flex-direction: column; align-items: center; @@ -115,8 +129,9 @@ } .file-item { - background: rgba(0, 0, 0, 0.5); - border: 2px solid #555; + background: rgba(20, 20, 20, 0.9); + border: 2px solid #000; + box-shadow: inset 2px 2px 0px #3a3a3a, inset -2px -2px 0px #0a0a0a; padding: 1rem; display: grid; grid-template-columns: 1fr auto; @@ -126,8 +141,9 @@ } .file-item:hover { - background: rgba(0, 0, 0, 0.7); - border-color: #777; + background: rgba(30, 30, 30, 0.95); + border-color: #555; + box-shadow: inset 2px 2px 0px #4a4a4a, inset -2px -2px 0px #1a1a1a; } /* 1. Name */