init
This commit is contained in:
commit
0d177729ab
15 changed files with 2263 additions and 0 deletions
1
.envrc
Normal file
1
.envrc
Normal file
|
|
@ -0,0 +1 @@
|
|||
use flake
|
||||
13
.github/workflows/build_nix.yml
vendored
Normal file
13
.github/workflows/build_nix.yml
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
name: "Build legacy Nix package on Ubuntu"
|
||||
|
||||
on:
|
||||
push:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: cachix/install-nix-action@v26
|
||||
- name: Building package
|
||||
run: nix build
|
||||
21
.gitignore
vendored
Normal file
21
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
debug
|
||||
target
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
# Generated by cargo mutants
|
||||
# Contains mutation testing data
|
||||
**/mutants.out*/
|
||||
|
||||
# RustRover
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
1703
Cargo.lock
generated
Normal file
1703
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
13
Cargo.toml
Normal file
13
Cargo.toml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "package_management"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
console = "0.16.1"
|
||||
indicatif = "0.18.0"
|
||||
md5 = "0.8.0"
|
||||
rand = "0.9.2"
|
||||
reqwest = { version = "0.12.23", features = ["blocking", "json"] }
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
|
||||
7
default.nix
Normal file
7
default.nix
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
(import (
|
||||
fetchTarball {
|
||||
url = "https://github.com/edolstra/flake-compat/archive/99f1c2157fba4bfe6211a321fd0ee43199025dbf.tar.gz";
|
||||
sha256 = "0x2jn3vrawwv9xp15674wjz9pixwjyj3j771izayl962zziivbx2"; }
|
||||
) {
|
||||
src = ./.;
|
||||
}).defaultNix
|
||||
137
flake.lock
generated
Normal file
137
flake.lock
generated
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
{
|
||||
"nodes": {
|
||||
"fenix": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"naersk",
|
||||
"nixpkgs"
|
||||
],
|
||||
"rust-analyzer-src": "rust-analyzer-src"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1752475459,
|
||||
"narHash": "sha256-z6QEu4ZFuHiqdOPbYss4/Q8B0BFhacR8ts6jO/F/aOU=",
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"rev": "bf0d6f70f4c9a9cf8845f992105652173f4b617f",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"naersk": {
|
||||
"inputs": {
|
||||
"fenix": "fenix",
|
||||
"nixpkgs": "nixpkgs"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1752689277,
|
||||
"narHash": "sha256-uldUBFkZe/E7qbvxa3mH1ItrWZyT6w1dBKJQF/3ZSsc=",
|
||||
"owner": "nix-community",
|
||||
"repo": "naersk",
|
||||
"rev": "0e72363d0938b0208d6c646d10649164c43f4d64",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"ref": "master",
|
||||
"repo": "naersk",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1752077645,
|
||||
"narHash": "sha256-HM791ZQtXV93xtCY+ZxG1REzhQenSQO020cu6rHtAPk=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "be9e214982e20b8310878ac2baa063a961c1bdf6",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs_2": {
|
||||
"locked": {
|
||||
"lastModified": 1759070547,
|
||||
"narHash": "sha256-JVZl8NaVRYb0+381nl7LvPE+A774/dRpif01FKLrYFQ=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "647e5c14cbd5067f44ac86b74f014962df460840",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"naersk": "naersk",
|
||||
"nixpkgs": "nixpkgs_2",
|
||||
"utils": "utils"
|
||||
}
|
||||
},
|
||||
"rust-analyzer-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1752428706,
|
||||
"narHash": "sha256-EJcdxw3aXfP8Ex1Nm3s0awyH9egQvB2Gu+QEnJn2Sfg=",
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "591e3b7624be97e4443ea7b5542c191311aa141d",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "rust-lang",
|
||||
"ref": "nightly",
|
||||
"repo": "rust-analyzer",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
40
flake.nix
Normal file
40
flake.nix
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
inputs = {
|
||||
naersk.url = "github:nix-community/naersk/master";
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
|
||||
utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
|
||||
outputs =
|
||||
{
|
||||
self,
|
||||
nixpkgs,
|
||||
utils,
|
||||
naersk,
|
||||
}:
|
||||
utils.lib.eachDefaultSystem (
|
||||
system:
|
||||
let
|
||||
pkgs = import nixpkgs { inherit system; };
|
||||
naersk-lib = pkgs.callPackage naersk { };
|
||||
in
|
||||
{
|
||||
defaultPackage = naersk-lib.buildPackage ./.;
|
||||
devShell =
|
||||
with pkgs;
|
||||
mkShell {
|
||||
buildInputs = [
|
||||
cargo
|
||||
rustc
|
||||
rustfmt
|
||||
pre-commit
|
||||
rustPackages.clippy
|
||||
pkg-config
|
||||
openssl
|
||||
gemini-cli-bin
|
||||
];
|
||||
RUST_SRC_PATH = rustPlatform.rustLibSrc;
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
7
shell.nix
Normal file
7
shell.nix
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
(import (
|
||||
fetchTarball {
|
||||
url = "https://github.com/edolstra/flake-compat/archive/99f1c2157fba4bfe6211a321fd0ee43199025dbf.tar.gz";
|
||||
sha256 = "0x2jn3vrawwv9xp15674wjz9pixwjyj3j771izayl962zziivbx2"; }
|
||||
) {
|
||||
src = ./.;
|
||||
}).shellNix
|
||||
137
src/downloader.rs
Normal file
137
src/downloader.rs
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
use std::collections::HashMap;
|
||||
use std::fs::{self, File};
|
||||
use std::io::{Read, Write};
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
|
||||
use console::style;
|
||||
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
|
||||
use md5;
|
||||
use reqwest::blocking::Client;
|
||||
|
||||
/// Prüft Datei gegen erwarteten MD5-Hash
|
||||
fn verify_md5(file_path: &Path, expected_hash: &str) -> bool {
|
||||
let mut f = match File::open(file_path) {
|
||||
Ok(f) => f,
|
||||
Err(_) => return false,
|
||||
};
|
||||
let mut buffer = Vec::new();
|
||||
if f.read_to_end(&mut buffer).is_err() {
|
||||
return false;
|
||||
}
|
||||
let digest = md5::compute(&buffer);
|
||||
let hex = format!("{:x}", digest);
|
||||
hex == expected_hash
|
||||
}
|
||||
|
||||
/// Download + Live-MD5-Prüfung
|
||||
pub fn download_files(
|
||||
wget_list: &str,
|
||||
target_dir: &Path,
|
||||
package_mirror: Option<String>,
|
||||
md5_map: Option<&HashMap<String, String>>,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
fs::create_dir_all(target_dir)?;
|
||||
|
||||
let urls: Vec<&str> = wget_list.lines().filter(|l| !l.trim().is_empty()).collect();
|
||||
let total = urls.len();
|
||||
let client = Arc::new(Client::new());
|
||||
let mp = Arc::new(MultiProgress::new());
|
||||
|
||||
// Clone md5_map before the loop so we can move it into threads
|
||||
let md5_map = md5_map.cloned();
|
||||
|
||||
let mut handles = vec![];
|
||||
|
||||
for (i, url) in urls.into_iter().enumerate() {
|
||||
let client = Arc::clone(&client);
|
||||
let mp = Arc::clone(&mp);
|
||||
let target_dir = target_dir.to_path_buf();
|
||||
let package_mirror = package_mirror.clone();
|
||||
let url = url.to_string();
|
||||
let md5_map = md5_map.clone();
|
||||
|
||||
let handle = thread::spawn(move || -> Result<(), Box<dyn std::error::Error + Send>> {
|
||||
let filename = url.split('/').last().unwrap_or("file.tar.xz");
|
||||
let filepath = target_dir.join(filename);
|
||||
|
||||
let download_url = if let Some(ref mirror) = package_mirror {
|
||||
if url.contains("ftp.gnu.org") {
|
||||
url.replacen("ftp.gnu.org", mirror, 1)
|
||||
} else {
|
||||
url.to_string()
|
||||
}
|
||||
} else {
|
||||
url.to_string()
|
||||
};
|
||||
|
||||
let pb = mp.add(ProgressBar::new(0));
|
||||
pb.set_style(
|
||||
ProgressStyle::with_template(
|
||||
"{bar:40.cyan/blue} {bytes}/{total_bytes} ({eta}) {msg}",
|
||||
)
|
||||
.unwrap()
|
||||
.progress_chars("=> "),
|
||||
);
|
||||
pb.set_message(format!(
|
||||
"[{}/{}] {}",
|
||||
i + 1,
|
||||
total,
|
||||
style(filename).yellow()
|
||||
));
|
||||
|
||||
let mut resp = client
|
||||
.get(&download_url)
|
||||
.send()
|
||||
.map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send>)?;
|
||||
let total_size = resp.content_length().unwrap_or(0);
|
||||
pb.set_length(total_size);
|
||||
|
||||
let mut file = File::create(&filepath)
|
||||
.map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send>)?;
|
||||
let mut downloaded: u64 = 0;
|
||||
let mut buffer = [0u8; 8192];
|
||||
|
||||
loop {
|
||||
let bytes_read = resp
|
||||
.read(&mut buffer)
|
||||
.map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send>)?;
|
||||
if bytes_read == 0 {
|
||||
break;
|
||||
}
|
||||
file.write_all(&buffer[..bytes_read])
|
||||
.map_err(|e| Box::new(e) as Box<dyn std::error::Error + Send>)?;
|
||||
downloaded += bytes_read as u64;
|
||||
pb.set_position(downloaded);
|
||||
}
|
||||
|
||||
// Live-MD5-Prüfung
|
||||
let status = if let Some(ref md5_map) = md5_map {
|
||||
if let Some(expected_hash) = md5_map.get(filename) {
|
||||
if verify_md5(&filepath, expected_hash) {
|
||||
style("✅").green()
|
||||
} else {
|
||||
style("❌").red()
|
||||
}
|
||||
} else {
|
||||
style("⚠️").yellow()
|
||||
}
|
||||
} else {
|
||||
style("⚠️").yellow()
|
||||
};
|
||||
|
||||
pb.finish_with_message(format!("{} {}", status, style(filename).yellow()));
|
||||
Ok(())
|
||||
});
|
||||
|
||||
handles.push(handle);
|
||||
}
|
||||
|
||||
for handle in handles {
|
||||
let result = handle.join().unwrap();
|
||||
result.map_err(|e| e as Box<dyn std::error::Error>)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
50
src/main.rs
Normal file
50
src/main.rs
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
mod downloader;
|
||||
mod md5_utils;
|
||||
mod mirrors;
|
||||
mod wget_list;
|
||||
|
||||
use console::style;
|
||||
use rand::Rng;
|
||||
use std::collections::HashMap;
|
||||
use std::env;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// LFS sources Pfad
|
||||
let lfs_sources = match env::var("LFS") {
|
||||
Ok(lfs) => PathBuf::from(lfs).join("sources"),
|
||||
Err(_) => {
|
||||
let mut rng = rand::rng();
|
||||
let random_number: u32 = rng.random_range(1000..=9999);
|
||||
let tmp_path = format!("/tmp/lfs_{}", random_number);
|
||||
println!(
|
||||
"{} Using temporary path {}",
|
||||
style("ℹ️").blue(),
|
||||
style(&tmp_path).yellow()
|
||||
);
|
||||
PathBuf::from(tmp_path).join("sources")
|
||||
}
|
||||
};
|
||||
|
||||
// Mirror für Pakete auswählen
|
||||
let package_mirror = mirrors::choose_package_mirror();
|
||||
|
||||
// Wget-Liste vom Original LFS-Mirror holen
|
||||
let wget_list = wget_list::get_wget_list()?;
|
||||
|
||||
// MD5 Map vorbereiten
|
||||
let mut md5_map: HashMap<String, String> = HashMap::new();
|
||||
let md5_content = md5_utils::get_md5sums()?;
|
||||
for line in md5_content.lines() {
|
||||
let mut parts = line.split_whitespace();
|
||||
if let (Some(hash), Some(filename)) = (parts.next(), parts.next()) {
|
||||
md5_map.insert(filename.to_string(), hash.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
// Pakete herunterladen + Live-MD5 prüfen
|
||||
downloader::download_files(&wget_list, &lfs_sources, package_mirror, Some(&md5_map))?;
|
||||
|
||||
println!("{} All done!", style("🎉").green().bold());
|
||||
Ok(())
|
||||
}
|
||||
59
src/md5_utils.rs
Normal file
59
src/md5_utils.rs
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
use console::style;
|
||||
use md5;
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader, Read};
|
||||
use std::path::Path;
|
||||
|
||||
pub fn get_md5sums() -> Result<String, Box<dyn std::error::Error>> {
|
||||
let client = reqwest::blocking::Client::builder()
|
||||
.redirect(reqwest::redirect::Policy::none())
|
||||
.build()?;
|
||||
let res = client
|
||||
.get("https://www.linuxfromscratch.org/~thomas/multilib-m32/md5sums")
|
||||
.send()?
|
||||
.text()?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
pub fn save_md5sums(content: &str, path: &Path) -> Result<(), Box<dyn std::error::Error>> {
|
||||
std::fs::write(path, content)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn verify_md5sums(
|
||||
md5_file: &Path,
|
||||
sources_dir: &Path,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let file = File::open(md5_file)?;
|
||||
for line in BufReader::new(file).lines() {
|
||||
let line = line?;
|
||||
if line.trim().is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let mut parts = line.split_whitespace();
|
||||
let expected_hash = parts.next().ok_or("Malformed md5sums line")?;
|
||||
let filename = parts.next().ok_or("Malformed md5sums line")?;
|
||||
let file_path = sources_dir.join(filename);
|
||||
|
||||
let mut f = File::open(&file_path)?;
|
||||
let mut buffer = Vec::new();
|
||||
f.read_to_end(&mut buffer)?;
|
||||
|
||||
let digest = md5::compute(&buffer);
|
||||
let hex = format!("{:x}", digest);
|
||||
|
||||
if hex == expected_hash {
|
||||
println!("{} {} OK", style("✅").green(), filename);
|
||||
} else {
|
||||
println!(
|
||||
"{} {} FAILED (expected {}, got {})",
|
||||
style("❌").red(),
|
||||
filename,
|
||||
expected_hash,
|
||||
hex
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
33
src/mirrors
Normal file
33
src/mirrors
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
use console::Style;
|
||||
use std::io::{self, Write};
|
||||
|
||||
pub fn choose_package_mirror() -> Option<String> {
|
||||
let mirrors = vec![
|
||||
"https://ftp.fau.de",
|
||||
"https://mirror.kernel.org/linux",
|
||||
"https://mirror.example.org/linux",
|
||||
];
|
||||
|
||||
println!("Optional: choose a mirror for source packages:");
|
||||
|
||||
for (i, mirror) in mirrors.iter().enumerate() {
|
||||
println!(" [{}] {}", i + 1, mirror);
|
||||
}
|
||||
|
||||
print!("Enter number or press Enter for default: ");
|
||||
io::stdout().flush().unwrap();
|
||||
|
||||
let mut input = String::new();
|
||||
io::stdin().read_line(&mut input).unwrap();
|
||||
let input = input.trim();
|
||||
|
||||
if input.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let choice = input.parse::<usize>().unwrap_or(1);
|
||||
let chosen = mirrors.get(choice.saturating_sub(1)).unwrap_or(&mirrors[0]);
|
||||
println!("Using package mirror: {}", Style::new().green().apply_to(chosen));
|
||||
Some(chosen.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
31
src/mirrors.rs
Normal file
31
src/mirrors.rs
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
use console::Style;
|
||||
use std::io::{self, Write};
|
||||
|
||||
pub fn choose_package_mirror() -> Option<String> {
|
||||
let mirrors = vec!["ftp.fau.de", "mirror.kernel.org", "mirror.example.org"];
|
||||
|
||||
println!("Optional: choose a mirror for GNU source packages (replace ftp.gnu.org):");
|
||||
|
||||
for (i, mirror) in mirrors.iter().enumerate() {
|
||||
println!(" [{}] {}", i + 1, mirror);
|
||||
}
|
||||
|
||||
print!("Enter number or press Enter for default: ");
|
||||
io::stdout().flush().unwrap();
|
||||
|
||||
let mut input = String::new();
|
||||
io::stdin().read_line(&mut input).unwrap();
|
||||
let input = input.trim();
|
||||
|
||||
if input.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let choice = input.parse::<usize>().unwrap_or(1);
|
||||
let chosen = mirrors.get(choice.saturating_sub(1)).unwrap_or(&mirrors[0]);
|
||||
println!(
|
||||
"Using package mirror: {}",
|
||||
Style::new().green().apply_to(chosen)
|
||||
);
|
||||
Some(chosen.to_string())
|
||||
}
|
||||
}
|
||||
11
src/wget_list.rs
Normal file
11
src/wget_list.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
use reqwest::blocking::Client;
|
||||
use reqwest::redirect::Policy;
|
||||
|
||||
pub fn get_wget_list() -> Result<String, Box<dyn std::error::Error>> {
|
||||
let client = Client::builder().redirect(Policy::none()).build()?;
|
||||
let res = client
|
||||
.get("https://www.linuxfromscratch.org/~thomas/multilib-m32/wget-list-sysv")
|
||||
.send()?
|
||||
.text()?;
|
||||
Ok(res)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue