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:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -52,6 +52,7 @@ src/lastRun.txt
|
|||||||
src/liblqos_python.so
|
src/liblqos_python.so
|
||||||
src/webusers.toml
|
src/webusers.toml
|
||||||
src/lqusers.toml
|
src/lqusers.toml
|
||||||
|
src/dist
|
||||||
|
|
||||||
# Ignore Rust build artifacts
|
# Ignore Rust build artifacts
|
||||||
src/rust/target
|
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",
|
"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]]
|
[[package]]
|
||||||
name = "cookie"
|
name = "cookie"
|
||||||
version = "0.16.2"
|
version = "0.16.2"
|
||||||
@@ -1402,6 +1413,14 @@ dependencies = [
|
|||||||
name = "lqos_rs"
|
name = "lqos_rs"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lqos_setup"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"colored",
|
||||||
|
"default-net",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lqos_sys"
|
name = "lqos_sys"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|||||||
@@ -22,4 +22,5 @@ members = [
|
|||||||
"lqos_python", # Python bindings for using the Rust bus directly
|
"lqos_python", # Python bindings for using the Rust bus directly
|
||||||
"lqusers", # CLI control for managing the web user list
|
"lqusers", # CLI control for managing the web user list
|
||||||
"lqos_utils", # A collection of macros and helpers we find useful
|
"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();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user