Skip to content

Commit 98f6369

Browse files
authored
Add panamax serve command (panamax-rs#35)
* Initial implementation of panamax serve * Fix up css, finish up backend stuff. Everything works! * Fix up some error handling * Format everything * Clippy everything * One single space. * Minor correction in structopt docstring * Remove some unwraps * Remove old TODO * A few documentation things * Multiple changes Remove console.log Use TlsConfig instead of tuple Use Platform instead of tuple Add some extra commenting * Combine two git handlers into one * Format * Deduplicate crate path handling * Formatting * Clippy
1 parent b66b1e6 commit 98f6369

17 files changed

+1900
-41
lines changed

Cargo.lock

+685-10
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

+10-1
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,19 @@ glob = "0.3.0"
2525
git2 = "0.13.18"
2626
serde_json = "1.0.64"
2727
thiserror = "1.0.24"
28+
tokio = { version = "1.7.1", features = ["full"] }
29+
warp = { version = "0.3.1", features = ["tls"] }
30+
askama = "0.10.5"
31+
askama_warp = "0.11.0"
32+
include_dir = "0.6.0"
33+
bytes = "1.0.1"
34+
tokio-stream = "0.1.6"
35+
tokio-util = "0.6.7"
36+
futures-util = "0.3.15"
2837

2938
[features]
3039
default = []
3140

3241
# For development purposes, reduce the crate download count
3342
# and only download crates that start with z
34-
dev_reduced_crates = []
43+
dev_reduced_crates = []

README.md

+13-2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ Panamax is available as a docker image, so you can run:
2424
$ docker run -it -v /path/to/mirror/:/mirror k3d3/panamax init /mirror
2525
(Modify /path/to/mirror/mirror.toml as needed)
2626
$ docker run -it -v /path/to/mirror/:/mirror k3d3/panamax sync /mirror
27+
(Once synced, serve the mirror)
28+
$ docker run -it -v /path/to/mirror/:/mirror -p8080:8080 k3d3/panamax serve /mirror
2729
```
2830

2931
Alternatively, you can run panamax in a bare-metal environment like below.
@@ -79,9 +81,18 @@ Additionally, this mirror can continually by synchronized in the future - one re
7981

8082
## Server
8183

82-
Panamax grabs the files needed to make a full mirror, however once the mirror directory is at its destination, it needs to be hosted as a server. Panamax doesn't provide this, however it should be fairly simple to host a mirror - everything can be accessed via HTTP, with the exception of the `crates.io-index` which uses git.
84+
Panamax provides a warp-based HTTP(S) server that can handle serving a Rust mirror fast and at scale. This is the recommended way to serve the mirror.
8385

84-
A sample `nginx` configuration file, `nginx.sample.conf` has been provided in the repository which will handle hosting a mirror server. Use this in the `sites-available` nginx directory, or copy it into `nginx.conf`.
86+
```
87+
$ panamax serve my-mirror
88+
Running HTTP on [::]:8080
89+
```
90+
91+
The server's index page provides all the instructions needed on how to set up a Rust client that uses this mirror.
92+
93+
If you would prefer having these instructions elsewhere, the rest of this README will describe the setup process in more detail.
94+
95+
Additionally, if you would prefer hosting a server with nginx, there is a sample nginx configuration in the repository, at `nginx.sample.conf`.
8596

8697
## Configuring `rustup` and `cargo`
8798

src/crates.rs

+29-26
Original file line numberDiff line numberDiff line change
@@ -57,32 +57,8 @@ pub fn sync_one_crate_entry(
5757
)
5858
};
5959

60-
let crate_name = format!("{}-{}.crate", &crate_entry.name, &crate_entry.vers);
61-
62-
let crate_path = match crate_entry.name.len() {
63-
1 => PathBuf::from("1"),
64-
2 => PathBuf::from("2"),
65-
3 => PathBuf::from("3"),
66-
n if n >= 4 => {
67-
let first_two = &crate_entry
68-
.name
69-
.get(0..2)
70-
.expect("crate name len >= 4 but couldn't get first 2 chars");
71-
let second_two = &crate_entry
72-
.name
73-
.get(2..4)
74-
.expect("crate name len >= 4 but couldn't get second 2 chars");
75-
[first_two, second_two].iter().collect()
76-
}
77-
_ => return Err(DownloadError::BadCrate("Empty crate name".into())),
78-
};
79-
80-
let file_path = path
81-
.join("crates")
82-
.join(crate_path)
83-
.join(&crate_entry.name)
84-
.join(&crate_entry.vers)
85-
.join(crate_name);
60+
let file_path = get_crate_path(path, &crate_entry.name, &crate_entry.vers)
61+
.ok_or_else(|| DownloadError::BadCrate(crate_entry.name.clone()))?;
8662

8763
download(
8864
&url[..],
@@ -306,3 +282,30 @@ pub fn is_new_crates_format(path: &Path) -> Result<bool, io::Error> {
306282

307283
Ok(true)
308284
}
285+
286+
pub fn get_crate_path(
287+
mirror_path: &Path,
288+
crate_name: &str,
289+
crate_version: &str,
290+
) -> Option<PathBuf> {
291+
let crate_path = match crate_name.len() {
292+
1 => PathBuf::from("1"),
293+
2 => PathBuf::from("2"),
294+
3 => PathBuf::from("3"),
295+
n if n >= 4 => {
296+
let first_two = crate_name.get(0..2)?;
297+
let second_two = crate_name.get(2..4)?;
298+
[first_two, second_two].iter().collect()
299+
}
300+
_ => return None,
301+
};
302+
303+
Some(
304+
mirror_path
305+
.join("crates")
306+
.join(crate_path)
307+
.join(crate_name)
308+
.join(crate_version)
309+
.join(format!("{}-{}.crate", crate_name, crate_version)),
310+
)
311+
}

src/main.rs

+36-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::path::PathBuf;
1+
use std::{net::IpAddr, path::PathBuf};
22
use structopt::StructOpt;
33

44
mod crates;
@@ -7,6 +7,7 @@ mod download;
77
mod mirror;
88
mod progress_bar;
99
mod rustup;
10+
mod serve;
1011

1112
/// Mirror rustup and crates.io repositories, for offline Rust and cargo usage.
1213
#[derive(Debug, StructOpt)]
@@ -41,6 +42,33 @@ enum Panamax {
4142
#[structopt(short, long)]
4243
base_url: Option<String>,
4344
},
45+
46+
/// Serve a mirror directory.
47+
#[structopt(name = "serve")]
48+
Serve {
49+
/// Mirror directory.
50+
#[structopt(parse(from_os_str))]
51+
path: PathBuf,
52+
53+
/// IP address to listen on. Defaults to listening on everything.
54+
#[structopt(short, long)]
55+
listen: Option<IpAddr>,
56+
57+
/// Port to listen on.
58+
/// Defaults to 8080, or 8443 if TLS certificate provided.
59+
#[structopt(short, long)]
60+
port: Option<u16>,
61+
62+
/// Path to a TLS certificate file. This enables TLS.
63+
/// Also requires key_path.
64+
#[structopt(long)]
65+
cert_path: Option<PathBuf>,
66+
67+
/// Path to a TLS key file.
68+
/// Also requires cert_path.
69+
#[structopt(long)]
70+
key_path: Option<PathBuf>,
71+
},
4472
}
4573

4674
fn main() {
@@ -50,6 +78,13 @@ fn main() {
5078
Panamax::Init { path } => mirror::init(&path),
5179
Panamax::Sync { path } => mirror::sync(&path),
5280
Panamax::Rewrite { path, base_url } => mirror::rewrite(&path, base_url),
81+
Panamax::Serve {
82+
path,
83+
listen,
84+
port,
85+
cert_path,
86+
key_path,
87+
} => mirror::serve(path, listen, port, cert_path, key_path),
5388
}
5489
.unwrap_or_else(|e| eprintln!("Panamax command failed! {}", e));
5590
}

src/mirror.rs

+46-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
use std::path::Path;
1+
use std::net::{IpAddr, SocketAddr};
2+
use std::path::{Path, PathBuf};
23
use std::{fs, io};
34

45
use console::style;
@@ -8,6 +9,7 @@ use thiserror::Error;
89

910
use crate::crates::is_new_crates_format;
1011
use crate::crates_index::rewrite_config_json;
12+
use crate::serve::TlsConfig;
1113

1214
#[derive(Error, Debug)]
1315
pub enum MirrorError {
@@ -17,6 +19,8 @@ pub enum MirrorError {
1719
Parse(#[from] toml::de::Error),
1820
#[error("Config file error: {0}")]
1921
Config(String),
22+
#[error("Command line error: {0}")]
23+
CmdLine(String),
2024
}
2125

2226
#[derive(Serialize, Deserialize, Debug)]
@@ -244,3 +248,44 @@ pub fn sync_crates(
244248

245249
eprintln!("{}", style("Syncing Crates repositories complete!").bold());
246250
}
251+
252+
pub fn serve(
253+
path: PathBuf,
254+
listen: Option<IpAddr>,
255+
port: Option<u16>,
256+
cert_path: Option<PathBuf>,
257+
key_path: Option<PathBuf>,
258+
) -> Result<(), MirrorError> {
259+
let listen = listen.unwrap_or_else(|| {
260+
"::".parse()
261+
.expect(":: IPv6 address should never fail to parse")
262+
});
263+
let port = port.unwrap_or_else(|| if cert_path.is_some() { 8443 } else { 8080 });
264+
let socket_addr = SocketAddr::new(listen, port);
265+
266+
let rt = tokio::runtime::Runtime::new()?;
267+
268+
match (cert_path, key_path) {
269+
(Some(cert_path), Some(key_path)) => rt.block_on(crate::serve::serve(
270+
path,
271+
socket_addr,
272+
Some(TlsConfig {
273+
cert_path,
274+
key_path,
275+
}),
276+
)),
277+
(None, None) => rt.block_on(crate::serve::serve(path, socket_addr, None)),
278+
(Some(_), None) => {
279+
return Err(MirrorError::CmdLine(
280+
"cert_path set but key_path not set.".to_string(),
281+
))
282+
}
283+
(None, Some(_)) => {
284+
return Err(MirrorError::CmdLine(
285+
"key_path set but cert_path not set.".to_string(),
286+
))
287+
}
288+
};
289+
290+
Ok(())
291+
}

0 commit comments

Comments
 (0)