3 Commits

Author SHA1 Message Date
fc27159c46 WIP: add pipeline for publishing debs 2025-05-12 13:21:27 +02:00
c2d22ee7f8 flake.lock: bump, Cargo.lock: bump, Cargo.toml: update inputs, format, etc
Some checks failed
Build / build (push) Has been cancelled
Build / check (push) Has been cancelled
Build / docs (push) Has been cancelled
2025-05-07 10:39:10 +02:00
8ba946976d README: add better description
Some checks failed
Build / build (push) Has been cancelled
Build / check (push) Has been cancelled
Build / docs (push) Has been cancelled
2025-03-18 12:38:28 +01:00
25 changed files with 512 additions and 373 deletions

View File

@@ -0,0 +1,32 @@
name: "publish-deb"
on:
workflow_dispatch:
inputs:
version:
description: "Version to publish"
required: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- name: Install cargo-deb
run: cargo install cargo-deb
- name: Build deb package
run: ./create-deb.sh
- name: Publish deb package
run: |
curl \
--user your_username:your_password_or_token \
--upload-file target/debian/*.deb \
https://git.pvv.ntnu.no/api/packages/testuser/debian/pool/bionic/main/upload

3
.gitignore vendored
View File

@@ -6,6 +6,3 @@ config.toml
/.direnv/
result
result-*
# Packaging
/assets/completions/

657
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,36 +2,26 @@
name = "mysqladm-rs"
version = "0.1.0"
edition = "2021"
license = "BSD3"
authors = [
"oysteikt@pvv.ntnu.no",
"felixalb@pvv.ntnu.no",
]
repository = "https://git.pvv.ntnu.no/Projects/mysqladm-rs"
description = "A command-line utility for MySQL administration for non-admin users"
categories = ["command-line-interface", "command-line-utilities"]
keywords = ["mysql", "cli", "administration"]
readme = "README.md"
[dependencies]
anyhow = "1.0.95"
async-bincode = "0.7.3"
bincode = "1.3.3"
async-bincode = "0.8.0"
bincode = "2.0.1"
clap = { version = "4.5.26", features = ["derive"] }
clap-verbosity-flag = "2.2.3"
clap-verbosity-flag = "3.0.2"
clap_complete = "4.5.42"
derive_more = { version = "1.0.0", features = ["display", "error"] }
derive_more = { version = "2.0.1", features = ["display", "error"] }
dialoguer = "0.11.0"
env_logger = "0.11.6"
futures = "0.3.31"
futures-util = "0.3.31"
indoc = "2.0.5"
itertools = "0.13.0"
itertools = "0.14.0"
log = "0.4.25"
nix = { version = "0.29.0", features = ["fs", "process", "socket", "user"] }
nix = { version = "0.30.1", features = ["fs", "process", "socket", "user"] }
prettytable = "0.10.0"
rand = "0.8.5"
ratatui = { version = "0.28.1", optional = true }
rand = "0.9.1"
ratatui = { version = "0.29.0", optional = true }
sd-notify = "0.4.5"
serde = "1.0.217"
serde_json = { version = "1.0.135", features = ["preserve_order"] }
@@ -64,37 +54,3 @@ anyhow = "1.0.95"
[dev-dependencies]
regex = "1.11.1"
# TODO: package shell completions
[package.metadata.deb]
maintainer = "Programvareverkstedet <projects@pvv.ntnu.no>"
section = "admin"
assets = [
[
"target/release/mysqladm",
"usr/bin/",
"755",
],
[
"example-config.toml",
"etc/mysqladm/config.toml",
"644",
],
[
"assets/completions/_*",
"usr/share/zsh/site-functions/completions/",
"644",
],
[
"assets/completions/*.bash",
"usr/share/bash-completion/completions/",
"644",
],
[
"assets/completions/*.fish",
"usr/share/fish/vendor_completions.d/",
"644",
],
]
conf-files = ["etc/mysqladm/config.toml"]
depends = []

View File

@@ -2,7 +2,26 @@
# mysqladm-rs
Work in progress rewrite of https://git.pvv.ntnu.no/Projects/mysql-admutils
Healing mysql spasms since 2024
## What is this?
This is a CLI tool that let's normal users perform administrative operations on a MySQL DBMS, with some restrictions.
The default restriction is to only let the user perform these actions on databases and database users that are prefixed with their username,
or with the name of any unix group that the user is a part of. i.e. `<user>_mydb`, `<user>_mydbuser`, or `<group>_myotherdb`.
The administrative actions available to the user includes:
- creating/listing/modifying/deleting databases and database users
- modifying database user privileges
- changing the passwords of the database users
- locking and unlocking database user accounts
- ... more to come
The software is split into a client and a server. The server has administrative access to the mysql server,
and is responsible for checking client authorization for the different types of actions the client might request.
This is designed for (and is only really useful for) multi-user servers, like tilde servers, university unix servers, etc.
## Installation

View File

@@ -1,19 +0,0 @@
#!/usr/bin/env bash
cargo build --release
mkdir -p assets/completions
./target/release/mysqladm generate-completions --shell bash > assets/completions/mysqladm.bash
./target/release/mysqladm generate-completions --shell zsh > assets/completions/_mysqladm
./target/release/mysqladm generate-completions --shell fish > assets/completions/mysqladm.fish
./target/release/mysqladm generate-completions --shell bash --command mysql-dbadm > assets/completions/mysql-dbadm.bash
./target/release/mysqladm generate-completions --shell zsh --command mysql-dbadm > assets/completions/_mysql-dbadm
./target/release/mysqladm generate-completions --shell fish --command mysql-dbadm > assets/completions/mysql-dbadm.fish
./target/release/mysqladm generate-completions --shell bash --command mysql-useradm > assets/completions/mysql-useradm.bash
./target/release/mysqladm generate-completions --shell zsh --command mysql-useradm > assets/completions/_mysql-useradm
./target/release/mysqladm generate-completions --shell fish --command mysql-useradm > assets/completions/mysql-useradm.fish
cargo deb

View File

@@ -19,4 +19,4 @@ port = 3306
username = "root"
password = "secret"
timeout = 2 # seconds
timeout = 2 # seconds

12
flake.lock generated
View File

@@ -2,11 +2,11 @@
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1737062831,
"narHash": "sha256-Tbk1MZbtV2s5aG+iM99U8FqwxU/YNArMcWAv6clcsBc=",
"lastModified": 1746461020,
"narHash": "sha256-7+pG1I9jvxNlmln4YgnlW4o+w0TZX24k688mibiFDUE=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "5df43628fdf08d642be8ba5b3625a6c70731c19c",
"rev": "3730d8a308f94996a9ba7c7138ede69c1b9ac4ae",
"type": "github"
},
"original": {
@@ -29,11 +29,11 @@
]
},
"locked": {
"lastModified": 1737166965,
"narHash": "sha256-vlDROBAgq+7PEVM0vaS2zboY6DXs3oKK0qW/1dVuFs4=",
"lastModified": 1746585402,
"narHash": "sha256-Pf+ufu6bYNA1+KQKHnGMNEfTwpD9ZIcAeLoE2yPWIP0=",
"owner": "oxalica",
"repo": "rust-overlay",
"rev": "fc839c9d5d1ebc789b4657c43c4d54838c7c01de",
"rev": "72dd969389583664f87aa348b3458f2813693617",
"type": "github"
},
"original": {

View File

@@ -48,7 +48,6 @@
mysql-client
cargo-nextest
cargo-deny
cargo-deb
];
RUST_SRC_PATH = "${toolchain}/lib/rustlib/src/rust/library";

1
rustfmt.toml Normal file
View File

@@ -0,0 +1 @@
style_edition = "2024"

View File

@@ -2,7 +2,7 @@ use anyhow::Context;
use clap::Parser;
use dialoguer::{Confirm, Editor};
use futures_util::{SinkExt, StreamExt};
use nix::unistd::{getuid, User};
use nix::unistd::{User, getuid};
use prettytable::{Cell, Row, Table};
use crate::{
@@ -15,13 +15,13 @@ use crate::{
parse_privilege_table_cli_arg,
},
protocol::{
ClientToServerMessageStream, MySQLDatabase, Request, Response,
print_create_databases_output_status, print_create_databases_output_status_json,
print_drop_databases_output_status, print_drop_databases_output_status_json,
print_modify_database_privileges_output_status, ClientToServerMessageStream,
MySQLDatabase, Request, Response,
print_modify_database_privileges_output_status,
},
},
server::sql::database_privilege_operations::{DatabasePrivilegeRow, DATABASE_PRIVILEGE_FIELDS},
server::sql::database_privilege_operations::{DATABASE_PRIVILEGE_FIELDS, DatabasePrivilegeRow},
};
#[derive(Parser, Debug, Clone)]

View File

@@ -19,8 +19,8 @@ use crate::{
core::{
bootstrap::bootstrap_server_connection_and_drop_privileges,
protocol::{
create_client_to_server_message_stream, ClientToServerMessageStream,
GetDatabasesPrivilegeDataError, MySQLDatabase, Request, Response,
ClientToServerMessageStream, GetDatabasesPrivilegeDataError, MySQLDatabase, Request,
Response, create_client_to_server_message_stream,
},
},
server::sql::database_privilege_operations::DatabasePrivilegeRow,
@@ -307,11 +307,7 @@ async fn show_databases(
#[inline]
fn yn(value: bool) -> &'static str {
if value {
"Y"
} else {
"N"
}
if value { "Y" } else { "N" }
}
fn print_db_privs(name: &str, rows: Vec<DatabasePrivilegeRow>) -> anyhow::Result<()> {

View File

@@ -19,8 +19,8 @@ use crate::{
core::{
bootstrap::bootstrap_server_connection_and_drop_privileges,
protocol::{
create_client_to_server_message_stream, ClientToServerMessageStream, MySQLUser,
Request, Response,
ClientToServerMessageStream, MySQLUser, Request, Response,
create_client_to_server_message_stream,
},
},
server::sql::user_operations::DatabaseUser,

View File

@@ -4,12 +4,12 @@ use dialoguer::{Confirm, Password};
use futures_util::{SinkExt, StreamExt};
use crate::core::protocol::{
ClientToServerMessageStream, ListUsersError, MySQLUser, Request, Response,
print_create_users_output_status, print_create_users_output_status_json,
print_drop_users_output_status, print_drop_users_output_status_json,
print_lock_users_output_status, print_lock_users_output_status_json,
print_set_password_output_status, print_unlock_users_output_status,
print_unlock_users_output_status_json, ClientToServerMessageStream, ListUsersError, MySQLUser,
Request, Response,
print_unlock_users_output_status_json,
};
use super::common::erroneous_server_response;

View File

@@ -1,12 +1,12 @@
use std::{fs, path::PathBuf};
use anyhow::Context;
use nix::libc::{exit, EXIT_SUCCESS};
use nix::libc::{EXIT_SUCCESS, exit};
use std::os::unix::net::UnixStream as StdUnixStream;
use tokio::net::UnixStream as TokioUnixStream;
use crate::{
core::common::{UnixUser, DEFAULT_CONFIG_PATH, DEFAULT_SOCKET_PATH},
core::common::{DEFAULT_CONFIG_PATH, DEFAULT_SOCKET_PATH, UnixUser},
server::{config::read_config_from_path, server_loop::handle_requests_for_single_session},
};

View File

@@ -66,11 +66,7 @@ impl UnixUser {
#[inline]
pub(crate) fn yn(b: bool) -> &'static str {
if b {
"Y"
} else {
"N"
}
if b { "Y" } else { "N" }
}
#[inline]

View File

@@ -1,4 +1,4 @@
use anyhow::{anyhow, Context};
use anyhow::{Context, anyhow};
use itertools::Itertools;
use prettytable::Table;
use serde::{Deserialize, Serialize};
@@ -12,7 +12,7 @@ use super::{
protocol::{MySQLDatabase, MySQLUser},
};
use crate::server::sql::database_privilege_operations::{
DatabasePrivilegeRow, DATABASE_PRIVILEGE_FIELDS,
DATABASE_PRIVILEGE_FIELDS, DatabasePrivilegeRow,
};
pub fn db_priv_field_human_readable_name(name: &str) -> String {
@@ -288,10 +288,10 @@ fn parse_privilege_row_from_editor(row: &str) -> PrivilegeRowParseResult {
match parts.len() {
n if (n < DATABASE_PRIVILEGE_FIELDS.len()) => {
return PrivilegeRowParseResult::TooFewFields(n)
return PrivilegeRowParseResult::TooFewFields(n);
}
n if (n > DATABASE_PRIVILEGE_FIELDS.len()) => {
return PrivilegeRowParseResult::TooManyFields(n)
return PrivilegeRowParseResult::TooManyFields(n);
}
_ => {}
}

View File

@@ -7,7 +7,7 @@ use std::{
use serde::{Deserialize, Serialize};
use tokio::net::UnixStream;
use tokio_serde::{formats::Bincode, Framed as SerdeFramed};
use tokio_serde::{Framed as SerdeFramed, formats::Bincode};
use tokio_util::codec::{Framed, LengthDelimitedCodec};
use crate::core::{database_privileges::DatabasePrivilegesDiff, protocol::*};

View File

@@ -2,7 +2,7 @@
extern crate prettytable;
use clap::{CommandFactory, Parser, ValueEnum};
use clap_complete::{generate, Shell};
use clap_complete::{Shell, generate};
use clap_verbosity_flag::Verbosity;
use std::path::PathBuf;
@@ -15,7 +15,7 @@ use futures::StreamExt;
use crate::{
core::{
bootstrap::bootstrap_server_connection_and_drop_privileges,
protocol::{create_client_to_server_message_stream, Response},
protocol::{Response, create_client_to_server_message_stream},
},
server::command::ServerArgs,
};

View File

@@ -12,7 +12,7 @@ use std::os::unix::net::UnixStream as StdUnixStream;
use tokio::net::UnixStream as TokioUnixStream;
use crate::core::common::UnixUser;
use crate::core::protocol::{create_server_to_client_message_stream, Response};
use crate::core::protocol::{Response, create_server_to_client_message_stream};
use crate::server::config::read_config_from_path_with_arg_overrides;
use crate::server::server_loop::listen_for_incoming_connections;
use crate::server::{

View File

@@ -1,9 +1,9 @@
use std::{fs, path::PathBuf, time::Duration};
use anyhow::{anyhow, Context};
use anyhow::{Context, anyhow};
use clap::Parser;
use serde::{Deserialize, Serialize};
use sqlx::{mysql::MySqlConnectOptions, ConnectOptions, MySqlConnection};
use sqlx::{ConnectOptions, MySqlConnection, mysql::MySqlConnectOptions};
use crate::core::common::DEFAULT_CONFIG_PATH;

View File

@@ -4,20 +4,20 @@ use futures_util::{SinkExt, StreamExt};
use indoc::concatdoc;
use tokio::net::{UnixListener, UnixStream};
use sqlx::prelude::*;
use sqlx::MySqlConnection;
use sqlx::prelude::*;
use crate::core::protocol::SetPasswordError;
use crate::server::sql::database_operations::list_databases;
use crate::{
core::{
common::{UnixUser, DEFAULT_SOCKET_PATH},
common::{DEFAULT_SOCKET_PATH, UnixUser},
protocol::request_response::{
create_server_to_client_message_stream, Request, Response, ServerToClientMessageStream,
Request, Response, ServerToClientMessageStream, create_server_to_client_message_stream,
},
},
server::{
config::{create_mysql_connection_from_config, ServerConfig},
config::{ServerConfig, create_mysql_connection_from_config},
sql::{
database_operations::{create_databases, drop_databases, list_all_databases_for_user},
database_privilege_operations::{

View File

@@ -1,7 +1,7 @@
use std::collections::BTreeMap;
use sqlx::prelude::*;
use sqlx::MySqlConnection;
use sqlx::prelude::*;
use serde::{Deserialize, Serialize};

View File

@@ -19,11 +19,11 @@ use std::collections::{BTreeMap, BTreeSet};
use indoc::indoc;
use itertools::Itertools;
use serde::{Deserialize, Serialize};
use sqlx::{mysql::MySqlRow, prelude::*, MySqlConnection};
use sqlx::{MySqlConnection, mysql::MySqlRow, prelude::*};
use crate::{
core::{
common::{rev_yn, yn, UnixUser},
common::{UnixUser, rev_yn, yn},
database_privileges::{DatabasePrivilegeChange, DatabasePrivilegesDiff},
protocol::{
DiffDoesNotApplyError, GetAllDatabasesPrivilegeData, GetAllDatabasesPrivilegeDataError,
@@ -280,9 +280,8 @@ async fn unsafe_apply_privilege_diff(
.map(|field| quote_identifier(field))
.join(",");
let question_marks = std::iter::repeat("?")
.take(DATABASE_PRIVILEGE_FIELDS.len())
.join(",");
let question_marks =
std::iter::repeat_n("?", DATABASE_PRIVILEGE_FIELDS.len()).join(",");
sqlx::query(
format!("INSERT INTO `db` ({}) VALUES ({})", tables, question_marks).as_str(),

View File

@@ -4,8 +4,8 @@ use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use sqlx::prelude::*;
use sqlx::MySqlConnection;
use sqlx::prelude::*;
use crate::{
core::{