Web Design Update
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
+11
@@ -76,6 +76,7 @@ async fn main() {
|
|||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/", get(list_files))
|
.route("/", get(list_files))
|
||||||
|
.route("/image.png", get(serve_image))
|
||||||
.route("/download/:file_id", get(download_file))
|
.route("/download/:file_id", get(download_file))
|
||||||
.route("/health", get(health_check))
|
.route("/health", get(health_check))
|
||||||
.layer(middleware::from_fn_with_state(
|
.layer(middleware::from_fn_with_state(
|
||||||
@@ -207,6 +208,16 @@ async fn health_check() -> impl IntoResponse {
|
|||||||
(StatusCode::OK, "OK")
|
(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<AppState>) -> Html<String> {
|
async fn list_files(State(state): State<AppState>) -> Html<String> {
|
||||||
// Read template from file
|
// Read template from file
|
||||||
let template_path = "templates/index.html";
|
let template_path = "templates/index.html";
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 2.8 MiB |
+28
-12
@@ -32,11 +32,10 @@
|
|||||||
body {
|
body {
|
||||||
font-family: 'VT323', monospace;
|
font-family: 'VT323', monospace;
|
||||||
background-color: var(--mc-bg);
|
background-color: var(--mc-bg);
|
||||||
background-image:
|
background-image: url('/image.png');
|
||||||
linear-gradient(45deg, #1a1a1a 25%, transparent 25%, transparent 75%, #1a1a1a 75%, #1a1a1a),
|
background-size: cover;
|
||||||
linear-gradient(45deg, #1a1a1a 25%, transparent 25%, transparent 75%, #1a1a1a 75%, #1a1a1a);
|
background-position: center;
|
||||||
background-size: 40px 40px;
|
background-attachment: fixed;
|
||||||
background-position: 0 0, 20px 20px;
|
|
||||||
color: var(--mc-text);
|
color: var(--mc-text);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
@@ -48,6 +47,11 @@
|
|||||||
.container {
|
.container {
|
||||||
max-width: 900px;
|
max-width: 900px;
|
||||||
margin: 0 auto;
|
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 {
|
h1 {
|
||||||
@@ -63,6 +67,16 @@
|
|||||||
letter-spacing: 2px;
|
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 Grid - Inventory Style */
|
||||||
.stats {
|
.stats {
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -72,10 +86,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.stat-card {
|
.stat-card {
|
||||||
background: #212121;
|
background: rgba(33, 33, 33, 0.9);
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
border: 2px solid #555;
|
border: 2px solid #000;
|
||||||
box-shadow: inset 2px 2px 0px #000, inset -2px -2px 0px #333;
|
box-shadow: inset 2px 2px 0px #444, inset -2px -2px 0px #111;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -115,8 +129,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.file-item {
|
.file-item {
|
||||||
background: rgba(0, 0, 0, 0.5);
|
background: rgba(20, 20, 20, 0.9);
|
||||||
border: 2px solid #555;
|
border: 2px solid #000;
|
||||||
|
box-shadow: inset 2px 2px 0px #3a3a3a, inset -2px -2px 0px #0a0a0a;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr auto;
|
grid-template-columns: 1fr auto;
|
||||||
@@ -126,8 +141,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.file-item:hover {
|
.file-item:hover {
|
||||||
background: rgba(0, 0, 0, 0.7);
|
background: rgba(30, 30, 30, 0.95);
|
||||||
border-color: #777;
|
border-color: #555;
|
||||||
|
box-shadow: inset 2px 2px 0px #4a4a4a, inset -2px -2px 0px #1a1a1a;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 1. Name */
|
/* 1. Name */
|
||||||
|
|||||||
Reference in New Issue
Block a user