GURT protocol (lib, cli, gdextension, Flumi integration)
This commit is contained in:
1720
protocol/cli/Cargo.lock
generated
Normal file
1720
protocol/cli/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
25
protocol/cli/Cargo.toml
Normal file
25
protocol/cli/Cargo.toml
Normal file
@@ -0,0 +1,25 @@
|
||||
[package]
|
||||
name = "gurty"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
authors = ["FaceDev"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/outpoot/gurted"
|
||||
description = "GURT protocol server CLI tool"
|
||||
|
||||
[[bin]]
|
||||
name = "gurty"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
gurt = { path = "../library" }
|
||||
|
||||
tokio = { version = "1.0", features = ["full"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = "0.3"
|
||||
|
||||
clap = { version = "4.0", features = ["derive"] }
|
||||
colored = "2.0"
|
||||
mime_guess = "2.0"
|
||||
56
protocol/cli/README.md
Normal file
56
protocol/cli/README.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Gurty - a CLI tool to setup your GURT Protocol server
|
||||
|
||||
## Setup for Production
|
||||
|
||||
For production deployments, you'll need to generate your own certificates since traditional Certificate Authorities don't support custom protocols:
|
||||
|
||||
1. **Generate production certificates with OpenSSL:**
|
||||
```bash
|
||||
# Generate private key
|
||||
openssl genpkey -algorithm RSA -out gurt-server.key -pkcs8 -v
|
||||
|
||||
# Generate certificate signing request
|
||||
openssl req -new -key gurt-server.key -out gurt-server.csr
|
||||
|
||||
# Generate self-signed certificate (valid for 365 days)
|
||||
openssl x509 -req -days 365 -in gurt-server.csr -signkey gurt-server.key -out gurt-server.crt
|
||||
|
||||
# Or generate both key and certificate in one step
|
||||
openssl req -x509 -newkey rsa:4096 -keyout gurt-server.key -out gurt-server.crt -days 365 -nodes
|
||||
```
|
||||
|
||||
2. **Deploy with production certificates:**
|
||||
```bash
|
||||
cargo run --release serve --cert gurt-server.crt --key gurt-server.key --host 0.0.0.0 --port 4878
|
||||
```
|
||||
|
||||
## Development Environment Setup
|
||||
|
||||
To set up a development environment for GURT, follow these steps:
|
||||
1. **Install mkcert:**
|
||||
```bash
|
||||
# Windows (with Chocolatey)
|
||||
choco install mkcert
|
||||
|
||||
# Or download from: https://github.com/FiloSottile/mkcert/releases
|
||||
```
|
||||
|
||||
2. **Install local CA in system:**
|
||||
```bash
|
||||
mkcert -install
|
||||
```
|
||||
This installs a local CA in your **system certificate store**.
|
||||
|
||||
3. **Generate localhost certificates:**
|
||||
```bash
|
||||
cd gurted/protocol/cli
|
||||
mkcert localhost 127.0.0.1 ::1
|
||||
```
|
||||
This creates:
|
||||
- `localhost+2.pem` (certificate)
|
||||
- `localhost+2-key.pem` (private key)
|
||||
|
||||
4. **Start GURT server with certificates:**
|
||||
```bash
|
||||
cargo run --release serve --cert localhost+2.pem --key localhost+2-key.pem
|
||||
```
|
||||
307
protocol/cli/src/main.rs
Normal file
307
protocol/cli/src/main.rs
Normal file
@@ -0,0 +1,307 @@
|
||||
use clap::{Parser, Subcommand};
|
||||
use colored::Colorize;
|
||||
use gurt::prelude::*;
|
||||
use std::path::PathBuf;
|
||||
use tracing::error;
|
||||
use tracing_subscriber;
|
||||
|
||||
#[derive(Parser)]
|
||||
#[command(name = "server")]
|
||||
#[command(about = "GURT Protocol Server")]
|
||||
#[command(version = "1.0.0")]
|
||||
struct Cli {
|
||||
#[command(subcommand)]
|
||||
command: Commands,
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
enum Commands {
|
||||
Serve {
|
||||
#[arg(short, long, default_value_t = 4878)]
|
||||
port: u16,
|
||||
|
||||
#[arg(long, default_value = "127.0.0.1")]
|
||||
host: String,
|
||||
|
||||
#[arg(short, long, default_value = ".")]
|
||||
dir: PathBuf,
|
||||
|
||||
#[arg(short, long)]
|
||||
verbose: bool,
|
||||
|
||||
#[arg(long, help = "Path to TLS certificate file")]
|
||||
cert: Option<PathBuf>,
|
||||
|
||||
#[arg(long, help = "Path to TLS private key file")]
|
||||
key: Option<PathBuf>,
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let cli = Cli::parse();
|
||||
|
||||
match cli.command {
|
||||
Commands::Serve { port, host, dir, verbose, cert, key } => {
|
||||
if verbose {
|
||||
tracing_subscriber::fmt()
|
||||
.with_max_level(tracing::Level::DEBUG)
|
||||
.init();
|
||||
} else {
|
||||
tracing_subscriber::fmt()
|
||||
.with_max_level(tracing::Level::INFO)
|
||||
.init();
|
||||
}
|
||||
|
||||
println!("{}", "GURT Protocol Server".bright_cyan().bold());
|
||||
println!("{} {}:{}", "Listening on".bright_blue(), host, port);
|
||||
println!("{} {}", "Serving from".bright_blue(), dir.display());
|
||||
|
||||
let server = create_file_server(dir, cert, key)?;
|
||||
let addr = format!("{}:{}", host, port);
|
||||
|
||||
if let Err(e) = server.listen(&addr).await {
|
||||
error!("Server error: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_file_server(base_dir: PathBuf, cert_path: Option<PathBuf>, key_path: Option<PathBuf>) -> Result<GurtServer> {
|
||||
let base_dir = std::sync::Arc::new(base_dir);
|
||||
|
||||
let server = match (cert_path, key_path) {
|
||||
(Some(cert), Some(key)) => {
|
||||
println!("TLS using certificate: {}", cert.display());
|
||||
GurtServer::with_tls_certificates(
|
||||
cert.to_str().ok_or_else(|| GurtError::invalid_message("Invalid certificate path"))?,
|
||||
key.to_str().ok_or_else(|| GurtError::invalid_message("Invalid key path"))?
|
||||
)?
|
||||
}
|
||||
(Some(_), None) => {
|
||||
return Err(GurtError::invalid_message("Certificate provided but no key file specified (use --key)"));
|
||||
}
|
||||
(None, Some(_)) => {
|
||||
return Err(GurtError::invalid_message("Key provided but no certificate file specified (use --cert)"));
|
||||
}
|
||||
(None, None) => {
|
||||
return Err(GurtError::invalid_message("GURT protocol requires TLS encryption. Please provide --cert and --key parameters."));
|
||||
}
|
||||
};
|
||||
|
||||
let server = server
|
||||
.get("/", {
|
||||
let base_dir = base_dir.clone();
|
||||
move |ctx| {
|
||||
let client_ip = ctx.client_ip();
|
||||
let base_dir = base_dir.clone();
|
||||
async move {
|
||||
// Try to serve index.html if it exists, otherwise show server info
|
||||
let index_path = base_dir.join("index.html");
|
||||
|
||||
if index_path.exists() && index_path.is_file() {
|
||||
match std::fs::read_to_string(&index_path) {
|
||||
Ok(content) => {
|
||||
return Ok(GurtResponse::ok()
|
||||
.with_header("Content-Type", "text/html")
|
||||
.with_string_body(content));
|
||||
}
|
||||
Err(_) => {
|
||||
// Fall through to default page
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Default server info page
|
||||
Ok(GurtResponse::ok()
|
||||
.with_header("Content-Type", "text/html")
|
||||
.with_string_body(format!(r#"
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>GURT Protocol Server</title>
|
||||
<style>
|
||||
body {{ font-sans m-[30px] bg-[#f5f5f5] }}
|
||||
.header {{ text-[#0066cc] }}
|
||||
.status {{ text-[#28a745] font-bold }}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1 class="header">Welcome to the GURT Protocol!</h1>
|
||||
<p class="status">This server is successfully running. We couldn't find index.html though :(</p>
|
||||
<p>Protocol: <strong>GURT/{}</strong></p>
|
||||
<p>Client IP: <strong>{}</strong></p>
|
||||
</body>
|
||||
</html>
|
||||
"#,
|
||||
gurt::GURT_VERSION,
|
||||
client_ip,
|
||||
)))
|
||||
}
|
||||
}
|
||||
})
|
||||
.get("/*", {
|
||||
let base_dir = base_dir.clone();
|
||||
move |ctx| {
|
||||
let base_dir = base_dir.clone();
|
||||
let path = ctx.path().to_string();
|
||||
async move {
|
||||
let mut relative_path = path.strip_prefix('/').unwrap_or(&path).to_string();
|
||||
// Remove any leading slashes to ensure relative path
|
||||
while relative_path.starts_with('/') || relative_path.starts_with('\\') {
|
||||
relative_path = relative_path[1..].to_string();
|
||||
}
|
||||
// If the path is now empty, use "."
|
||||
let relative_path = if relative_path.is_empty() { ".".to_string() } else { relative_path };
|
||||
let file_path = base_dir.join(&relative_path);
|
||||
|
||||
match file_path.canonicalize() {
|
||||
Ok(canonical_path) => {
|
||||
let canonical_base = match base_dir.canonicalize() {
|
||||
Ok(base) => base,
|
||||
Err(_) => {
|
||||
return Ok(GurtResponse::internal_server_error()
|
||||
.with_header("Content-Type", "text/plain")
|
||||
.with_string_body("Server configuration error"));
|
||||
}
|
||||
};
|
||||
|
||||
if !canonical_path.starts_with(&canonical_base) {
|
||||
return Ok(GurtResponse::bad_request()
|
||||
.with_header("Content-Type", "text/plain")
|
||||
.with_string_body("Access denied: Path outside served directory"));
|
||||
}
|
||||
|
||||
if canonical_path.is_file() {
|
||||
match std::fs::read(&canonical_path) {
|
||||
Ok(content) => {
|
||||
let content_type = get_content_type(&canonical_path);
|
||||
Ok(GurtResponse::ok()
|
||||
.with_header("Content-Type", &content_type)
|
||||
.with_body(content))
|
||||
}
|
||||
Err(_) => {
|
||||
Ok(GurtResponse::internal_server_error()
|
||||
.with_header("Content-Type", "text/plain")
|
||||
.with_string_body("Failed to read file"))
|
||||
}
|
||||
}
|
||||
} else if canonical_path.is_dir() {
|
||||
let index_path = canonical_path.join("index.html");
|
||||
if index_path.is_file() {
|
||||
match std::fs::read_to_string(&index_path) {
|
||||
Ok(content) => {
|
||||
Ok(GurtResponse::ok()
|
||||
.with_header("Content-Type", "text/html")
|
||||
.with_string_body(content))
|
||||
}
|
||||
Err(_) => {
|
||||
Ok(GurtResponse::internal_server_error()
|
||||
.with_header("Content-Type", "text/plain")
|
||||
.with_string_body("Failed to read index file"))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match std::fs::read_dir(&canonical_path) {
|
||||
Ok(entries) => {
|
||||
let mut listing = String::from(r#"
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Directory Listing</title>
|
||||
<style>
|
||||
body { font-sans m-[40px] }
|
||||
.file { my-1 }
|
||||
.dir { font-bold text-[#0066cc] }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Directory Listing</h1>
|
||||
<p><a href="../">← Parent Directory</a></p>
|
||||
<div style="flex flex-col gap-2">
|
||||
"#);
|
||||
for entry in entries.flatten() {
|
||||
let file_name = entry.file_name();
|
||||
let name = file_name.to_string_lossy();
|
||||
let is_dir = entry.path().is_dir();
|
||||
let display_name = if is_dir { format!("{}/", name) } else { name.to_string() };
|
||||
let class = if is_dir { "file dir" } else { "file" };
|
||||
|
||||
listing.push_str(&format!(
|
||||
r#" <a style={} href="/{}">{}</a>"#,
|
||||
class, name, display_name
|
||||
));
|
||||
listing.push('\n');
|
||||
}
|
||||
|
||||
listing.push_str("</div></body>\n</html>");
|
||||
|
||||
Ok(GurtResponse::ok()
|
||||
.with_header("Content-Type", "text/html")
|
||||
.with_string_body(listing))
|
||||
}
|
||||
Err(_) => {
|
||||
Ok(GurtResponse::internal_server_error()
|
||||
.with_header("Content-Type", "text/plain")
|
||||
.with_string_body("Failed to read directory"))
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// File not found
|
||||
Ok(GurtResponse::not_found()
|
||||
.with_header("Content-Type", "text/html")
|
||||
.with_string_body(get_404_html()))
|
||||
}
|
||||
}
|
||||
Err(_e) => {
|
||||
Ok(GurtResponse::not_found()
|
||||
.with_header("Content-Type", "text/html")
|
||||
.with_string_body(get_404_html()))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Ok(server)
|
||||
}
|
||||
|
||||
fn get_404_html() -> &'static str {
|
||||
r#"<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>404 Not Found</title>
|
||||
<style>
|
||||
body { font-sans m-[40px] text-center }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>404 Page Not Found</h1>
|
||||
<p>The requested path was not found on this GURT server.</p>
|
||||
<p><a href="/">Back to home</a></p>
|
||||
</body>
|
||||
</html>
|
||||
"#
|
||||
}
|
||||
|
||||
fn get_content_type(path: &std::path::Path) -> String {
|
||||
match path.extension().and_then(|ext| ext.to_str()) {
|
||||
Some("html") | Some("htm") => "text/html".to_string(),
|
||||
Some("css") => "text/css".to_string(),
|
||||
Some("js") => "application/javascript".to_string(),
|
||||
Some("json") => "application/json".to_string(),
|
||||
Some("png") => "image/png".to_string(),
|
||||
Some("jpg") | Some("jpeg") => "image/jpeg".to_string(),
|
||||
Some("gif") => "image/gif".to_string(),
|
||||
Some("svg") => "image/svg+xml".to_string(),
|
||||
Some("ico") => "image/x-icon".to_string(),
|
||||
Some("txt") => "text/plain".to_string(),
|
||||
Some("xml") => "application/xml".to_string(),
|
||||
Some("pdf") => "application/pdf".to_string(),
|
||||
_ => "application/octet-stream".to_string(),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user