Compare commits
1 Commits
main
...
debian-vm-
| Author | SHA1 | Date | |
|---|---|---|---|
|
3b25fe54e4
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -9,6 +9,7 @@ result-*
|
||||
|
||||
# Nix VM
|
||||
*.qcow2
|
||||
.nixos-test-history
|
||||
|
||||
# Packaging
|
||||
!/assets/debian/config.toml
|
||||
|
||||
757
Cargo.lock
generated
757
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
37
Cargo.toml
37
Cargo.toml
@@ -1,11 +1,12 @@
|
||||
[package]
|
||||
name = "muscl"
|
||||
version = "1.0.0"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
resolver = "2"
|
||||
license = "BSD-3-Clause"
|
||||
authors = [
|
||||
"Programvareverkstedet <projects@pvv.ntnu.no>",
|
||||
"oysteikt@pvv.ntnu.no",
|
||||
"felixalb@pvv.ntnu.no",
|
||||
]
|
||||
homepage = "https://git.pvv.ntnu.no/Projects/muscl"
|
||||
repository = "https://git.pvv.ntnu.no/Projects/muscl"
|
||||
@@ -18,50 +19,50 @@ autobins = false
|
||||
autolib = false
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.102"
|
||||
anyhow = "1.0.100"
|
||||
async-bincode = "0.8.0"
|
||||
bincode = "2.0.1"
|
||||
clap = { version = "4.6.0", features = ["cargo", "derive"] }
|
||||
clap = { version = "4.5.54", features = ["cargo", "derive"] }
|
||||
clap-verbosity-flag = { version = "3.0.4", features = [ "tracing" ] }
|
||||
clap_complete = { version = "4.6.0", features = ["unstable-dynamic"] }
|
||||
clap_complete = { version = "4.5.65", features = ["unstable-dynamic"] }
|
||||
color-print = "0.3.7"
|
||||
const_format = "0.2.35"
|
||||
derive_more = { version = "2.1.1", features = ["display", "error"] }
|
||||
dialoguer = "0.12.0"
|
||||
futures-util = "0.3.32"
|
||||
futures-util = "0.3.31"
|
||||
humansize = "2.1.3"
|
||||
indoc = "2.0.7"
|
||||
itertools = "0.14.0"
|
||||
nix = { version = "0.31.2", features = ["fs", "process", "socket", "user"] }
|
||||
nix = { version = "0.30.1", features = ["fs", "process", "socket", "user"] }
|
||||
num_cpus = "1.17.0"
|
||||
prettytable = "0.10.0"
|
||||
rand = "0.10.0"
|
||||
rand = "0.9.2"
|
||||
serde = "1.0.228"
|
||||
serde_json = { version = "1.0.149", features = ["preserve_order"] }
|
||||
sqlx = { version = "0.8.6", features = ["runtime-tokio", "mysql", "tls-rustls"] }
|
||||
thiserror = "2.0.18"
|
||||
tokio = { version = "1.50.0", features = ["rt-multi-thread", "macros", "signal"] }
|
||||
thiserror = "2.0.17"
|
||||
tokio = { version = "1.49.0", features = ["rt-multi-thread", "macros", "signal"] }
|
||||
tokio-serde = { version = "0.9.0", features = ["bincode"] }
|
||||
tokio-stream = "0.1.18"
|
||||
tokio-util = { version = "0.7.18", features = ["codec", "rt"] }
|
||||
toml = "1.1.2"
|
||||
toml = "0.9.11"
|
||||
tracing = { version = "0.1.44", features = ["log"] }
|
||||
tracing-subscriber = "0.3.23"
|
||||
uuid = { version = "1.23.0", features = ["v4"] }
|
||||
tracing-subscriber = "0.3.22"
|
||||
uuid = { version = "1.19.0", features = ["v4"] }
|
||||
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
landlock = "0.4.4"
|
||||
sd-notify = "0.5.0"
|
||||
sd-notify = "0.4.5"
|
||||
tracing-journald = "0.3.2"
|
||||
|
||||
[build-dependencies]
|
||||
anyhow = "1.0.102"
|
||||
build-info-build = "0.0.43"
|
||||
git2 = { version = "0.20.4", default-features = false }
|
||||
anyhow = "1.0.100"
|
||||
build-info-build = "0.0.42"
|
||||
git2 = { version = "0.20.3", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "1.4.1"
|
||||
regex = "1.12.3"
|
||||
regex = "1.12.2"
|
||||
|
||||
[features]
|
||||
default = ["mysql-admutils-compatibility"]
|
||||
|
||||
@@ -3,7 +3,6 @@ Description=Muscl MySQL admin tool
|
||||
|
||||
[Socket]
|
||||
ListenStream=/run/muscl/muscl.sock
|
||||
RemoveOnStop=true
|
||||
Accept=no
|
||||
PassCredentials=true
|
||||
|
||||
|
||||
39
flake.lock
generated
39
flake.lock
generated
@@ -2,11 +2,11 @@
|
||||
"nodes": {
|
||||
"crane": {
|
||||
"locked": {
|
||||
"lastModified": 1774313767,
|
||||
"narHash": "sha256-hy0XTQND6avzGEUFrJtYBBpFa/POiiaGBr2vpU6Y9tY=",
|
||||
"lastModified": 1767744144,
|
||||
"narHash": "sha256-9/9ntI0D+HbN4G0TrK3KmHbTvwgswz7p8IEJsWyef8Q=",
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"rev": "3d9df76e29656c679c744968b17fbaf28f0e923d",
|
||||
"rev": "2fb033290bf6b23f226d4c8b32f7f7a16b043d7e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -15,13 +15,33 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix-vm-test": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1763976673,
|
||||
"narHash": "sha256-QPeI8WR+brwodiy4YNfOnLI7rOHJfFPrGm+xT/HmtT4=",
|
||||
"owner": "numtide",
|
||||
"repo": "nix-vm-test",
|
||||
"rev": "8611bdd7a49750a880be9ee2ea9f68c53f8c9299",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "nix-vm-test",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1775036866,
|
||||
"narHash": "sha256-ZojAnPuCdy657PbTq5V0Y+AHKhZAIwSIT2cb8UgAz/U=",
|
||||
"lastModified": 1768127708,
|
||||
"narHash": "sha256-1Sm77VfZh3mU0F5OqKABNLWxOuDeHIlcFjsXeeiPazs=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "6201e203d09599479a3b3450ed24fa81537ebc4e",
|
||||
"rev": "ffbc9f8cbaacfb331b6017d5a5abb21a492c9a38",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@@ -34,6 +54,7 @@
|
||||
"root": {
|
||||
"inputs": {
|
||||
"crane": "crane",
|
||||
"nix-vm-test": "nix-vm-test",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"rust-overlay": "rust-overlay"
|
||||
}
|
||||
@@ -45,11 +66,11 @@
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1775099554,
|
||||
"narHash": "sha256-3xBsGnGDLOFtnPZ1D3j2LU19wpAlYefRKTlkv648rU0=",
|
||||
"lastModified": 1768186348,
|
||||
"narHash": "sha256-nkpIe3zkpeoFuOl8xBpexulECsHLQ9Ljg1gW3bPCjSI=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "8d6387ed6d8e6e6672fd3ed4b61b59d44b124d99",
|
||||
"rev": "af69e497567a5945a64057717bc9b17c8478097e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
||||
@@ -6,9 +6,12 @@
|
||||
rust-overlay.inputs.nixpkgs.follows = "nixpkgs";
|
||||
|
||||
crane.url = "github:ipetkov/crane";
|
||||
|
||||
nix-vm-test.url = "github:numtide/nix-vm-test";
|
||||
nix-vm-test.inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, rust-overlay, crane }:
|
||||
outputs = { self, nixpkgs, rust-overlay, crane, nix-vm-test }:
|
||||
let
|
||||
inherit (nixpkgs) lib;
|
||||
|
||||
@@ -95,6 +98,8 @@
|
||||
muscl = import ./nix/module.nix;
|
||||
};
|
||||
|
||||
# vmlib = forAllSystems(system: _: _: nix-vm-test.lib.${system});
|
||||
|
||||
packages = forAllSystems (system: pkgs: _:
|
||||
let
|
||||
cargoToml = builtins.fromTOML (builtins.readFile ./Cargo.toml);
|
||||
@@ -130,6 +135,8 @@
|
||||
filteredSource = pkgs.runCommandLocal "filtered-source" { } ''
|
||||
ln -s ${src} $out
|
||||
'';
|
||||
|
||||
debianVm = import ./nix/debian-vm-configuration.nix { inherit nix-vm-test nixpkgs system pkgs; };
|
||||
});
|
||||
|
||||
checks = forAllSystems (system: pkgs: _: {
|
||||
|
||||
49
nix/debian-vm-configuration.nix
Normal file
49
nix/debian-vm-configuration.nix
Normal file
@@ -0,0 +1,49 @@
|
||||
{ nix-vm-test, nixpkgs, system, pkgs, ... }:
|
||||
let
|
||||
image = nix-vm-test.lib.${system}.debian.images."13";
|
||||
|
||||
generic = import "${nix-vm-test}/generic" { inherit pkgs nixpkgs; inherit (pkgs) lib; };
|
||||
|
||||
makeVmTestForImage =
|
||||
image:
|
||||
{
|
||||
testScript,
|
||||
sharedDirs ? {},
|
||||
diskSize ? null,
|
||||
config ? { }
|
||||
}:
|
||||
generic.makeVmTest {
|
||||
inherit
|
||||
system
|
||||
testScript
|
||||
sharedDirs;
|
||||
image = nix-vm-test.lib.${system}.debian.prepareDebianImage {
|
||||
inherit diskSize;
|
||||
hostPkgs = pkgs;
|
||||
originalImage = image;
|
||||
};
|
||||
machineConfigModule = config;
|
||||
};
|
||||
|
||||
vmTest = makeVmTestForImage image {
|
||||
diskSize = "10G";
|
||||
sharedDirs = {
|
||||
debDir = {
|
||||
source = "${./.}";
|
||||
target = "/mnt";
|
||||
};
|
||||
};
|
||||
testScript = ''
|
||||
vm.wait_for_unit("multi-user.target")
|
||||
vm.succeed("apt-get update && apt-get -y install mariadb-server build-essential curl")
|
||||
vm.succeed("curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y")
|
||||
vm.succeed("source /root/.cargo/env && cargo install cargo-deb")
|
||||
vm.succeed("cp -r /mnt /root/src && chmod -R +w /root/src")
|
||||
vm.succeed("source /root/.cargo/env && cd /root/src && ./create-deb.sh")
|
||||
'';
|
||||
config.nodes.vm = {
|
||||
virtualisation.memorySize = 8192;
|
||||
virtualisation.cpus = 4;
|
||||
};
|
||||
};
|
||||
in vmTest.driverInteractive
|
||||
@@ -88,7 +88,7 @@ buildFunction ({
|
||||
'';
|
||||
|
||||
meta = with lib; {
|
||||
license = licenses.bsd3;
|
||||
license = licenses.mit;
|
||||
platforms = platforms.linux ++ platforms.darwin;
|
||||
inherit mainProgram;
|
||||
};
|
||||
|
||||
@@ -54,13 +54,11 @@ for variant in debian-bookworm debian-trixie ubuntu-jammy ubuntu-noble; do
|
||||
DEB_VERSION=$(find "$TMPDIR/muscl-deb-$variant-$GIT_SHA"/*.deb -print0 | xargs -0 -n1 basename | cut -d'_' -f2 | head -n1)
|
||||
DEB_ARCH=$(find "$TMPDIR/muscl-deb-$variant-$GIT_SHA"/*.deb -print0 | xargs -0 -n1 basename | cut -d'_' -f3 | cut -d'.' -f1 | head -n1)
|
||||
|
||||
# echo "[DELETE] https://git.pvv.ntnu.no/api/packages/Projects/debian/pool/$DISTRO_VERSION_NAME/main/$DEB_NAME/$DEB_VERSION/$DEB_ARCH"
|
||||
# curl \
|
||||
# -X DELETE \
|
||||
# --user "$GITEA_USER:$GITEA_TOKEN" \
|
||||
# "https://git.pvv.ntnu.no/api/packages/Projects/debian/pool/$DISTRO_VERSION_NAME/main/$DEB_NAME/$DEB_VERSION/$DEB_ARCH"
|
||||
curl \
|
||||
-X DELETE \
|
||||
--user "$GITEA_USER:$GITEA_TOKEN" \
|
||||
"https://git.pvv.ntnu.no/api/packages/Projects/debian/pool/$DISTRO_VERSION_NAME/main/$DEB_NAME/$DEB_VERSION/$DEB_ARCH"
|
||||
|
||||
echo "[PUT] https://git.pvv.ntnu.no/api/packages/Projects/debian/pool/$DISTRO_VERSION_NAME/main/upload"
|
||||
curl \
|
||||
-X PUT \
|
||||
--user "$GITEA_USER:$GITEA_TOKEN" \
|
||||
|
||||
@@ -319,8 +319,7 @@ fn main() -> anyhow::Result<()> {
|
||||
#[cfg(not(feature = "suid-sgid-mode"))]
|
||||
None,
|
||||
args.verbose,
|
||||
)
|
||||
.context("Failed to connect to the server")?;
|
||||
)?;
|
||||
|
||||
tokio_run_command(args.command, connection)?;
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
use std::{
|
||||
fs,
|
||||
os::unix::fs::FileTypeExt,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
@@ -8,10 +7,7 @@ use std::{
|
||||
|
||||
use anyhow::{Context, anyhow};
|
||||
use clap_verbosity_flag::{InfoLevel, Verbosity};
|
||||
use nix::{
|
||||
libc::{EXIT_SUCCESS, exit},
|
||||
unistd::{AccessFlags, access},
|
||||
};
|
||||
use nix::libc::{EXIT_SUCCESS, exit};
|
||||
use sqlx::mysql::MySqlPoolOptions;
|
||||
use std::os::unix::net::UnixStream as StdUnixStream;
|
||||
use tokio::{net::UnixStream as TokioUnixStream, sync::RwLock};
|
||||
@@ -134,28 +130,11 @@ pub fn bootstrap_server_connection_and_drop_privileges(
|
||||
}
|
||||
}
|
||||
|
||||
fn socket_path_is_ok(path: &Path) -> anyhow::Result<()> {
|
||||
fs::metadata(path)
|
||||
.context(format!("Failed to get metadata for {:?}", path))
|
||||
.and_then(|meta| {
|
||||
if !meta.file_type().is_socket() {
|
||||
anyhow::bail!("{:?} is not a unix socket", path);
|
||||
}
|
||||
|
||||
access(path, AccessFlags::R_OK | AccessFlags::W_OK)
|
||||
.with_context(|| format!("Socket at {:?} is not readable/writable", path))?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn connect_to_external_server(
|
||||
server_socket_path: Option<PathBuf>,
|
||||
) -> anyhow::Result<StdUnixStream> {
|
||||
// TODO: ensure this is both readable and writable
|
||||
if let Some(socket_path) = server_socket_path {
|
||||
tracing::trace!("Checking socket at {:?}", socket_path);
|
||||
socket_path_is_ok(&socket_path)?;
|
||||
|
||||
tracing::debug!("Connecting to socket at {:?}", socket_path);
|
||||
return match StdUnixStream::connect(socket_path) {
|
||||
Ok(socket) => Ok(socket),
|
||||
@@ -168,9 +147,6 @@ fn connect_to_external_server(
|
||||
}
|
||||
|
||||
if fs::metadata(DEFAULT_SOCKET_PATH).is_ok() {
|
||||
tracing::trace!("Checking socket at {:?}", DEFAULT_SOCKET_PATH);
|
||||
socket_path_is_ok(Path::new(DEFAULT_SOCKET_PATH))?;
|
||||
|
||||
tracing::debug!("Connecting to default socket at {:?}", DEFAULT_SOCKET_PATH);
|
||||
return match StdUnixStream::connect(DEFAULT_SOCKET_PATH) {
|
||||
Ok(socket) => Ok(socket),
|
||||
@@ -182,9 +158,7 @@ fn connect_to_external_server(
|
||||
};
|
||||
}
|
||||
|
||||
anyhow::bail!(
|
||||
"No socket path provided, and no socket found found at default location {DEFAULT_SOCKET_PATH}"
|
||||
);
|
||||
anyhow::bail!("No socket path provided, and no default socket found");
|
||||
}
|
||||
|
||||
// TODO: this function is security critical, it should be integration tested
|
||||
|
||||
@@ -34,7 +34,7 @@ impl DerefMut for MySQLUser {
|
||||
|
||||
impl fmt::Display for MySQLUser {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
write!(f, "{:<width$}", self.0, width = f.width().unwrap_or(0))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ impl DerefMut for MySQLDatabase {
|
||||
|
||||
impl fmt::Display for MySQLDatabase {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
write!(f, "{:<width$}", self.0, width = f.width().unwrap_or(0))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -59,10 +59,6 @@ fn parse_group_denylist(denylist_path: &Path, lines: Lines) -> GroupDenylist {
|
||||
}
|
||||
.trim();
|
||||
|
||||
if trimmed_line.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let parts: Vec<&str> = trimmed_line.splitn(2, ':').collect();
|
||||
if parts.len() != 2 {
|
||||
tracing::warn!(
|
||||
|
||||
@@ -90,12 +90,14 @@ impl Supervisor {
|
||||
};
|
||||
|
||||
let mut watchdog_duration = None;
|
||||
let mut watchdog_micro_seconds = 0;
|
||||
#[cfg(target_os = "linux")]
|
||||
let watchdog_task =
|
||||
if systemd_mode && let Some(watchdog_duration_) = sd_notify::watchdog_enabled() {
|
||||
if systemd_mode && sd_notify::watchdog_enabled(true, &mut watchdog_micro_seconds) {
|
||||
let watchdog_duration_ = Duration::from_micros(watchdog_micro_seconds);
|
||||
tracing::debug!(
|
||||
"Systemd watchdog enabled with {} millisecond interval",
|
||||
watchdog_duration_.as_millis()
|
||||
watchdog_micro_seconds.div_ceil(1000),
|
||||
);
|
||||
watchdog_duration = Some(watchdog_duration_);
|
||||
Some(spawn_watchdog_task(watchdog_duration_))
|
||||
@@ -293,12 +295,15 @@ impl Supervisor {
|
||||
|
||||
pub async fn reload(&self) -> anyhow::Result<()> {
|
||||
#[cfg(target_os = "linux")]
|
||||
sd_notify::notify(&[
|
||||
sd_notify::NotifyState::Reloading,
|
||||
sd_notify::NotifyState::monotonic_usec_now()
|
||||
.expect("Failed to get monotonic time to send to systemd while reloading"),
|
||||
sd_notify::NotifyState::Status("Reloading configuration"),
|
||||
])?;
|
||||
sd_notify::notify(
|
||||
false,
|
||||
&[
|
||||
sd_notify::NotifyState::Reloading,
|
||||
sd_notify::NotifyState::monotonic_usec_now()
|
||||
.expect("Failed to get monotonic time to send to systemd while reloading"),
|
||||
sd_notify::NotifyState::Status("Reloading configuration"),
|
||||
],
|
||||
)?;
|
||||
|
||||
let previous_config = self.config.lock().await.clone();
|
||||
self.reload_config().await?;
|
||||
@@ -335,14 +340,14 @@ impl Supervisor {
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
sd_notify::notify(&[sd_notify::NotifyState::Ready])?;
|
||||
sd_notify::notify(false, &[sd_notify::NotifyState::Ready])?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn shutdown(&self) -> anyhow::Result<()> {
|
||||
#[cfg(target_os = "linux")]
|
||||
sd_notify::notify(&[sd_notify::NotifyState::Stopping])?;
|
||||
sd_notify::notify(false, &[sd_notify::NotifyState::Stopping])?;
|
||||
|
||||
tracing::debug!("Stop accepting new connections");
|
||||
self.stop_receiving_new_connections()?;
|
||||
@@ -412,7 +417,7 @@ fn spawn_watchdog_task(duration: Duration) -> JoinHandle<()> {
|
||||
);
|
||||
loop {
|
||||
interval.tick().await;
|
||||
if let Err(err) = sd_notify::notify(&[sd_notify::NotifyState::Watchdog]) {
|
||||
if let Err(err) = sd_notify::notify(false, &[sd_notify::NotifyState::Watchdog]) {
|
||||
tracing::warn!("Failed to notify systemd watchdog: {}", err);
|
||||
}
|
||||
}
|
||||
@@ -435,7 +440,9 @@ fn spawn_status_notifier_task(task_tracker: TaskTracker) -> JoinHandle<()> {
|
||||
"Waiting for connections".to_string()
|
||||
};
|
||||
|
||||
if let Err(e) = sd_notify::notify(&[sd_notify::NotifyState::Status(message.as_str())]) {
|
||||
if let Err(e) =
|
||||
sd_notify::notify(false, &[sd_notify::NotifyState::Status(message.as_str())])
|
||||
{
|
||||
tracing::warn!("Failed to send systemd status notification: {}", e);
|
||||
}
|
||||
}
|
||||
@@ -550,7 +557,7 @@ async fn listener_task(
|
||||
group_denylist: Arc<RwLock<GroupDenylist>>,
|
||||
) -> anyhow::Result<()> {
|
||||
#[cfg(target_os = "linux")]
|
||||
sd_notify::notify(&[sd_notify::NotifyState::Ready])?;
|
||||
sd_notify::notify(false, &[sd_notify::NotifyState::Ready])?;
|
||||
|
||||
let connection_counter = AtomicU64::new(0);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user