mirror of
https://github.com/LibreQoE/LibreQoS.git
synced 2025-02-25 18:55:32 -06:00
(Very alpha!) Add dpkg builder and initial configurator
* Adds a new Rust program, `lqos_setup`. * If no /etc/lqos.conf is found, prompts for interfaces and creates a dual-interface XDP bridge setup. * If no /opt/libreqos/src/ispConfig.py is found, prompts for bandwidth and creates one (using the interfaces also) * Same for ShapedDevices.csv and network.json * If no webusers are found, prompts to make one. * Adds build_dbpkg.sh * Creates a new directory named `dist` * Builds the Rust components in a portable mode. * Creates a list of dependencies and DEBIAN directory with control and postinst files. * Handles PIP dependencies in postinst * Calls the new `lqos_setup` program for final configuration. * Sets up the daemons in systemd and enables them. In very brief testing, I had a working XDP bridge with 1 fake user and a total bandwidth limit configured and working after running: dpkg -i 1.4-1.dpkg apt -f install Could still use some tweaking.
This commit is contained in:
parent
e74662631c
commit
3e9ff0c0f5
1
.gitignore
vendored
1
.gitignore
vendored
@ -52,6 +52,7 @@ src/lastRun.txt
|
||||
src/liblqos_python.so
|
||||
src/webusers.toml
|
||||
src/lqusers.toml
|
||||
src/dist
|
||||
|
||||
# Ignore Rust build artifacts
|
||||
src/rust/target
|
||||
|
113
src/build_dpkg.sh
Executable file
113
src/build_dpkg.sh
Executable file
@ -0,0 +1,113 @@
|
||||
#!/bin/bash
|
||||
|
||||
####################################################
|
||||
# Copyright (c) 2022, Herbert Wolverson and LibreQoE
|
||||
# This is all GPL2.
|
||||
|
||||
PACKAGE=libreqos
|
||||
VERSION=1.4
|
||||
DPKG_DIR=dist/$PACKAGE_$VERSION-1_amd64
|
||||
APT_DEPENDENCIES="python3-pip, clang, gcc, gcc-multilib, llvm, libelf-dev, git, nano, graphviz, curl, screen, llvm, pkg-config, linux-tools-common, libbpf-dev"
|
||||
DEBIAN_DIR=$DPKG_DIR/DEBIAN
|
||||
LQOS_DIR=$DPKG_DIR/opt/libreqos/src
|
||||
ETC_DIR=$DPKG_DIR/etc
|
||||
LQOS_FILES="graphInfluxDB.py influxDBdashboardTemplate.json integrationCommon.py integrationRestHttp.py integrationSplynx.py integrationUISP.py ispConfig.example.py LibreQoS.py lqos.example lqTools.py mikrotikFindIPv6.py network.example.json pythonCheck.py README.md scheduler.py ShapedDevices.example.csv"
|
||||
LQOS_BIN_FILES="lqos_scheduler.service.example lqosd.service.example lqos_node_manager.service.example"
|
||||
RUSTPROGS="lqosd lqtop xdp_iphash_to_cpu_cmdline xdp_pping lqos_node_manager lqusers lqos_setup"
|
||||
|
||||
####################################################
|
||||
# Clean any previous dist build
|
||||
rm -rf dist
|
||||
|
||||
####################################################
|
||||
# The Debian Packaging Bit
|
||||
|
||||
# Create the basic directory structure
|
||||
mkdir -p $DEBIAN_DIR
|
||||
|
||||
# Build the chroot directory structure
|
||||
mkdir -p $LQOS_DIR
|
||||
mkdir -p $LQOS_DIR/bin/static
|
||||
mkdir -p $ETC_DIR
|
||||
|
||||
# Create the Debian control file
|
||||
pushd $DEBIAN_DIR > /dev/null
|
||||
touch control
|
||||
echo "Package: $PACKAGE" >> control
|
||||
echo "Version: $VERSION" >> control
|
||||
echo "Architecture: amd64" >> control
|
||||
echo "Maintainer: Herbert Wolverson <herberticus@gmail.com>" >> control
|
||||
echo "Description: CAKE-based traffic shaping for ISPs" >> control
|
||||
echo "Depends: $APT_DEPENDENCIES" >> control
|
||||
popd > /dev/null
|
||||
|
||||
# Create the post-installation file
|
||||
pushd $DEBIAN_DIR > /dev/null
|
||||
touch postinst
|
||||
echo "#!/bin/bash" >> postinst
|
||||
echo "# Install Python Dependencies" >> postinst
|
||||
echo "pushd /opt/libreqos" >> postinst
|
||||
# - Setup Python dependencies as a post-install task
|
||||
while requirement= read -r line
|
||||
do
|
||||
echo "python3 -m pip install $line" >> postinst
|
||||
echo "sudo python3 -m pip install $line" >> postinst
|
||||
done < ../../../../requirements.txt
|
||||
# - Run lqsetup
|
||||
echo "/opt/libreqos/src/bin/lqos_setup" >> postinst
|
||||
# - Setup the services
|
||||
echo "cp /opt/libreqos/src/bin/lqos_node_manager.service.example /etc/systemd/system/lqos_node_manager.service" >> postinst
|
||||
echo "cp /opt/libreqos/src/bin/lqosd.service.example /etc/systemd/system/lqosd.service" >> postinst
|
||||
echo "cp /opt/libreqos/src/bin/lqos_scheduler.service.example /etc/systemd/system/lqos_scheduler.service" >> postinst
|
||||
echo "/bin/systemctl daemon-reload" >> postinst
|
||||
echo "/bin/systemctl enable lqosd lqos_node_manager lqos_scheduler" >> postinst
|
||||
echo "/bin/systemctl start lqosd" >> postinst
|
||||
echo "/bin/systemctl start lqos_node_manager" >> postinst
|
||||
echo "/bin/systemctl start lqos_scheduler" >> postinst
|
||||
echo "popd" >> postinst
|
||||
chmod a+x postinst
|
||||
popd > /dev/null
|
||||
|
||||
# Create the cleanup file
|
||||
pushd $DEBIAN_DIR > /dev/null
|
||||
touch postrm
|
||||
echo "#!/bin/bash" >> postrm
|
||||
chmod a+x postrm
|
||||
popd > /dev/null
|
||||
|
||||
# Copy files into the LibreQoS directory
|
||||
for file in $LQOS_FILES
|
||||
do
|
||||
cp $file $LQOS_DIR
|
||||
done
|
||||
|
||||
# Copy files into the LibreQoS/bin directory
|
||||
for file in $LQOS_BIN_FILES
|
||||
do
|
||||
cp bin/$file $LQOS_DIR/bin
|
||||
done
|
||||
|
||||
####################################################
|
||||
# Build the Rust programs
|
||||
pushd rust > /dev/null
|
||||
cargo clean
|
||||
cargo build --all --release
|
||||
popd > /dev/null
|
||||
|
||||
# Copy newly built Rust files
|
||||
# - The Python integration Library
|
||||
cp rust/target/release/liblqos_python.so $LQOS_DIR
|
||||
# - The main executables
|
||||
for prog in $RUSTPROGS
|
||||
do
|
||||
cp rust/target/release/$prog $LQOS_DIR/bin
|
||||
done
|
||||
# - The webserver skeleton files
|
||||
cp rust/lqos_node_manager/Rocket.toml $LQOS_DIR/bin
|
||||
cp -R rust/lqos_node_manager/static/* $LQOS_DIR/bin/static
|
||||
|
||||
####################################################
|
||||
# Assemble the package
|
||||
pushd dist / dev/null
|
||||
dpkg-deb --root-owner-group --build $DPKG_DIR
|
||||
popd > /dev/null
|
19
src/rust/Cargo.lock
generated
19
src/rust/Cargo.lock
generated
@ -399,6 +399,17 @@ dependencies = [
|
||||
"os_str_bytes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colored"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"lazy_static",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cookie"
|
||||
version = "0.16.2"
|
||||
@ -1402,6 +1413,14 @@ dependencies = [
|
||||
name = "lqos_rs"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "lqos_setup"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"colored",
|
||||
"default-net",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lqos_sys"
|
||||
version = "0.1.0"
|
||||
|
@ -22,4 +22,5 @@ members = [
|
||||
"lqos_python", # Python bindings for using the Rust bus directly
|
||||
"lqusers", # CLI control for managing the web user list
|
||||
"lqos_utils", # A collection of macros and helpers we find useful
|
||||
"lqos_setup", # A quick CLI setup for first-time users
|
||||
]
|
||||
|
8
src/rust/lqos_setup/Cargo.toml
Normal file
8
src/rust/lqos_setup/Cargo.toml
Normal file
@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "lqos_setup"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
colored = "2"
|
||||
default-net = "0" # For obtaining an easy-to-use NIC list
|
225
src/rust/lqos_setup/src/main.rs
Normal file
225
src/rust/lqos_setup/src/main.rs
Normal file
@ -0,0 +1,225 @@
|
||||
use std::{path::Path, fs, process::Command};
|
||||
use colored::Colorize;
|
||||
use default_net::{get_interfaces, interface::InterfaceType, Interface};
|
||||
|
||||
fn get_available_interfaces() -> Vec<Interface> {
|
||||
get_interfaces()
|
||||
.iter()
|
||||
.filter(|eth| eth.if_type == InterfaceType::Ethernet && !eth.name.starts_with("br"))
|
||||
.cloned()
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn should_build(path: &str) -> bool {
|
||||
if Path::new(path).exists() {
|
||||
let string = format!("Skipping: {path}");
|
||||
println!("{}", string.red());
|
||||
println!("{}", "You already have one installed\n".cyan());
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn list_interfaces(interfaces: &Vec<Interface>) {
|
||||
println!("{}", "Available Interfaces".white());
|
||||
for i in interfaces {
|
||||
let iftype = format!("{:?}", i.if_type);
|
||||
println!("{} - {}", i.name.cyan(), iftype.yellow());
|
||||
}
|
||||
}
|
||||
|
||||
fn is_valid_interface(interfaces: &[Interface], iface: &str) -> bool {
|
||||
interfaces.iter().any(|i| i.name == iface)
|
||||
}
|
||||
|
||||
pub fn read_line() -> String {
|
||||
let mut guess = String::new();
|
||||
std::io::stdin()
|
||||
.read_line(&mut guess)
|
||||
.expect("failed to readline");
|
||||
guess.trim().to_string()
|
||||
}
|
||||
|
||||
pub fn read_line_as_number() -> u32 {
|
||||
loop {
|
||||
let str = read_line();
|
||||
if let Ok(n) = str::parse::<u32>(&str) {
|
||||
return n;
|
||||
}
|
||||
println!("Could not parse [{str}] as a number. Try again.");
|
||||
}
|
||||
}
|
||||
|
||||
const LQOS_CONF: &str = "/etc/lqos.conf";
|
||||
const ISP_CONF: &str = "/opt/libreqos/src/ispConfig.py";
|
||||
const NETWORK_JSON: &str = "/opt/libreqos/src/network.json";
|
||||
const SHAPED_DEVICES: &str = "/opt/libreqos/src/ShapedDevices.csv";
|
||||
const LQUSERS: &str = "/opt/libreqos/src/lqusers.toml";
|
||||
|
||||
fn get_internet_interface(interfaces: &Vec<Interface>, if_internet: &mut Option<String>) {
|
||||
if if_internet.is_none() {
|
||||
println!("{}", "Which Network Interface faces the INTERNET?".yellow());
|
||||
list_interfaces(interfaces);
|
||||
loop {
|
||||
let iface = read_line();
|
||||
if is_valid_interface(interfaces, &iface) {
|
||||
*if_internet = Some(iface);
|
||||
break;
|
||||
} else {
|
||||
println!("{}", "Not a valid interface".red());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_isp_interface(interfaces: &Vec<Interface>, if_isp: &mut Option<String>) {
|
||||
if if_isp.is_none() {
|
||||
println!("{}", "Which Network Interface faces the ISP CORE?".yellow());
|
||||
list_interfaces(interfaces);
|
||||
loop {
|
||||
let iface = read_line();
|
||||
if is_valid_interface(interfaces, &iface) {
|
||||
*if_isp = Some(iface);
|
||||
break;
|
||||
} else {
|
||||
println!("{}", "Not a valid interface".red());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_bandwidth(up: bool) -> u32 {
|
||||
loop {
|
||||
match up {
|
||||
true => println!("{}", "How much UPLOAD bandwidth do you have? (Mbps, e.g. 1000 = 1 gbit)".yellow()),
|
||||
false => println!("{}", "How much DOWNLOAD bandwidth do you have? (Mbps, e.g. 1000 = 1 gbit)".yellow()),
|
||||
}
|
||||
let bandwidth = read_line_as_number();
|
||||
if bandwidth > 0 {
|
||||
return bandwidth;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ETC_LQOS_CONF: &str = "lqos_directory = '/opt/libreqos/src'
|
||||
queue_check_period_ms = 1000
|
||||
|
||||
[tuning]
|
||||
stop_irq_balance = true
|
||||
netdev_budget_usecs = 8000
|
||||
netdev_budget_packets = 300
|
||||
rx_usecs = 8
|
||||
tx_usecs = 8
|
||||
disable_rxvlan = true
|
||||
disable_txvlan = true
|
||||
disable_offload = [ \"gso\", \"tso\", \"lro\", \"sg\", \"gro\" ]
|
||||
|
||||
[bridge]
|
||||
use_xdp_bridge = true
|
||||
interface_mapping = [
|
||||
{ name = \"{INTERNET}\", redirect_to = \"{ISP}\", scan_vlans = false },
|
||||
{ name = \"{ISP}\", redirect_to = \"{INTERNET}\", scan_vlans = false }
|
||||
]
|
||||
vlan_mapping = []
|
||||
";
|
||||
|
||||
fn write_etc_lqos_conf(internet: &str, isp: &str) {
|
||||
let output = ETC_LQOS_CONF.replace("{INTERNET}", internet).replace("{ISP}", isp);
|
||||
fs::write(LQOS_CONF, output).expect("Unable to write file");
|
||||
}
|
||||
|
||||
pub fn write_isp_config_py(
|
||||
dir: &str,
|
||||
download: u32,
|
||||
upload: u32,
|
||||
lan: &str,
|
||||
internet: &str,
|
||||
) {
|
||||
// Copy ispConfig.example.py to ispConfig.py
|
||||
let orig = format!("{dir}ispConfig.example.py");
|
||||
let dest = format!("{dir}ispConfig.py");
|
||||
std::fs::copy(orig, &dest).unwrap();
|
||||
|
||||
let config_file = std::fs::read_to_string(&dest).unwrap();
|
||||
let mut new_config_file = String::new();
|
||||
config_file.split('\n').for_each(|line| {
|
||||
if line.contains("upstreamBandwidthCapacityDownloadMbps") {
|
||||
new_config_file += &format!("upstreamBandwidthCapacityDownloadMbps = {download}\n");
|
||||
} else if line.contains("upstreamBandwidthCapacityUploadMbps") {
|
||||
new_config_file += &format!("upstreamBandwidthCapacityUploadMbps = {upload}\n");
|
||||
} else if line.contains("interfaceA") {
|
||||
new_config_file += &format!("interfaceA = \"{lan}\"\n");
|
||||
} else if line.contains("interfaceB") {
|
||||
new_config_file += &format!("interfaceB = \"{internet}\"\n");
|
||||
} else if line.contains("generatedPNDownloadMbps") {
|
||||
new_config_file += &format!("generatedPNDownloadMbps = \"{download}\"\n");
|
||||
} else if line.contains("generatedPNUploadMbps") {
|
||||
new_config_file += &format!("generatedPNUploadMbps = \"{upload}\"\n");
|
||||
} else {
|
||||
new_config_file += line;
|
||||
new_config_file += "\n";
|
||||
}
|
||||
});
|
||||
std::fs::write(&dest, new_config_file).unwrap();
|
||||
}
|
||||
|
||||
fn write_network_json() {
|
||||
let output = "{}\n";
|
||||
fs::write(NETWORK_JSON, output).expect("Unable to write file");
|
||||
}
|
||||
|
||||
const EMPTY_SHAPED_DEVICES: &str = "# This is a comment
|
||||
Circuit ID,Circuit Name,Device ID,Device Name,Parent Node,MAC,IPv4,IPv6,Download Min Mbps,Upload Min Mbps,Download Max Mbps,Upload Max Mbps,Comment
|
||||
# This is another comment
|
||||
\"9999\",\"968 Circle St., Gurnee, IL 60031\",1,Device 1,AP_7,,\"100.64.1.2, 100.64.0.14\",,25,5,500,500,";
|
||||
|
||||
fn write_shaped_devices() {
|
||||
fs::write(SHAPED_DEVICES, EMPTY_SHAPED_DEVICES).expect("Unable to write file");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("{:^80}", "LibreQoS 1.4 Setup Assistant".yellow().on_blue());
|
||||
println!();
|
||||
let interfaces = get_available_interfaces();
|
||||
let mut if_internet: Option<String> = None;
|
||||
let mut if_isp: Option<String> = None;
|
||||
if should_build(LQOS_CONF) {
|
||||
println!("{}{}", LQOS_CONF.cyan(), "does not exist, building one.".white());
|
||||
get_internet_interface(&interfaces, &mut if_internet);
|
||||
get_isp_interface(&interfaces, &mut if_isp);
|
||||
if let (Some(internet), Some(isp)) = (&if_internet, &if_isp) {
|
||||
write_etc_lqos_conf(internet, isp);
|
||||
}
|
||||
}
|
||||
|
||||
if should_build(ISP_CONF) {
|
||||
println!("{}{}", ISP_CONF.cyan(), "does not exist, building one.".white());
|
||||
get_internet_interface(&interfaces, &mut if_internet);
|
||||
get_isp_interface(&interfaces, &mut if_isp);
|
||||
let upload = get_bandwidth(true);
|
||||
let download = get_bandwidth(false);
|
||||
if let (Some(internet), Some(isp)) = (&if_internet, &if_isp) {
|
||||
write_isp_config_py("/opt/libreqos/src/", download, upload, isp, internet)
|
||||
}
|
||||
}
|
||||
|
||||
if should_build(NETWORK_JSON) {
|
||||
println!("{}{}", NETWORK_JSON.cyan(), "does not exist, making a simple flat network.".white());
|
||||
write_network_json();
|
||||
}
|
||||
if should_build(SHAPED_DEVICES) {
|
||||
println!("{}{}", SHAPED_DEVICES.cyan(), "does not exist, making an empty one.".white());
|
||||
println!("{}", "Don't forget to add some users!".magenta());
|
||||
write_shaped_devices();
|
||||
}
|
||||
if should_build(LQUSERS) {
|
||||
println!("Enter a username for the web manager:");
|
||||
let user = read_line();
|
||||
println!("Enter a password for the web manager:");
|
||||
let password = read_line();
|
||||
Command::new("/opt/libreqos/src/bin/lqusers")
|
||||
.args(["add", "--username", &user, "--role", "admin", "--password", &password])
|
||||
.output()
|
||||
.unwrap();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user