Compare commits
6 Commits
d554280741
...
7ec2c5de20
Author | SHA1 | Date |
---|---|---|
Oystein Kristoffer Tveit | 7ec2c5de20 | |
Oystein Kristoffer Tveit | 94e0e5d6c7 | |
Oystein Kristoffer Tveit | 807017ea70 | |
Oystein Kristoffer Tveit | 0e38fbb7e9 | |
Oystein Kristoffer Tveit | 5d049390b8 | |
Oystein Kristoffer Tveit | a6f00d4313 |
|
@ -265,6 +265,15 @@ dependencies = [
|
||||||
"strsim",
|
"strsim",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_complete"
|
||||||
|
version = "4.5.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1ee158892bd7ce77aa15c208abbdb73e155d191c287a659b57abd5adb92feb03"
|
||||||
|
dependencies = [
|
||||||
|
"clap",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap_derive"
|
name = "clap_derive"
|
||||||
version = "4.5.13"
|
version = "4.5.13"
|
||||||
|
@ -1055,6 +1064,7 @@ dependencies = [
|
||||||
"async-bincode",
|
"async-bincode",
|
||||||
"bincode",
|
"bincode",
|
||||||
"clap",
|
"clap",
|
||||||
|
"clap_complete",
|
||||||
"derive_more",
|
"derive_more",
|
||||||
"dialoguer",
|
"dialoguer",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
|
|
|
@ -8,6 +8,7 @@ anyhow = "1.0.86"
|
||||||
async-bincode = "0.7.2"
|
async-bincode = "0.7.2"
|
||||||
bincode = "1.3.3"
|
bincode = "1.3.3"
|
||||||
clap = { version = "4.5.16", features = ["derive"] }
|
clap = { version = "4.5.16", features = ["derive"] }
|
||||||
|
clap_complete = "4.5.18"
|
||||||
derive_more = { version = "1.0.0", features = ["display", "error"] }
|
derive_more = { version = "1.0.0", features = ["display", "error"] }
|
||||||
dialoguer = "0.11.0"
|
dialoguer = "0.11.0"
|
||||||
env_logger = "0.11.5"
|
env_logger = "0.11.5"
|
||||||
|
|
64
build.rs
64
build.rs
|
@ -3,40 +3,44 @@ use anyhow::anyhow;
|
||||||
#[cfg(feature = "mysql-admutils-compatibility")]
|
#[cfg(feature = "mysql-admutils-compatibility")]
|
||||||
use std::{env, os::unix::fs::symlink, path::PathBuf};
|
use std::{env, os::unix::fs::symlink, path::PathBuf};
|
||||||
|
|
||||||
fn main() -> anyhow::Result<()> {
|
fn generate_mysql_admutils_symlinks() -> anyhow::Result<()> {
|
||||||
#[cfg(feature = "mysql-admutils-compatibility")]
|
// NOTE: This is slightly illegal, and depends on implementation details.
|
||||||
{
|
// But it is only here for ease of testing the compatibility layer,
|
||||||
// NOTE: This is slightly illegal, and depends on implementation details.
|
// and not critical in any way. Considering the code is never going
|
||||||
// But it is only here for ease of testing the compatibility layer,
|
// to be used as a library, it should be fine.
|
||||||
// and not critical in any way. Considering the code is never going
|
let target_profile_dir: PathBuf = PathBuf::from(env::var("OUT_DIR")?)
|
||||||
// to be used as a library, it should be fine.
|
.parent()
|
||||||
let target_profile_dir: PathBuf = PathBuf::from(env::var("OUT_DIR")?)
|
.and_then(|p| p.parent())
|
||||||
.parent()
|
.and_then(|p| p.parent())
|
||||||
.and_then(|p| p.parent())
|
.ok_or(anyhow!("Could not resolve target profile directory"))?
|
||||||
.and_then(|p| p.parent())
|
.to_path_buf();
|
||||||
.ok_or(anyhow!("Could not resolve target profile directory"))?
|
|
||||||
.to_path_buf();
|
|
||||||
|
|
||||||
if !target_profile_dir.exists() {
|
if !target_profile_dir.exists() {
|
||||||
std::fs::create_dir_all(&target_profile_dir)?;
|
std::fs::create_dir_all(&target_profile_dir)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !target_profile_dir.join("mysql-useradm").exists() {
|
if !target_profile_dir.join("mysql-useradm").exists() {
|
||||||
symlink(
|
symlink(
|
||||||
target_profile_dir.join("mysqladm"),
|
target_profile_dir.join("mysqladm"),
|
||||||
target_profile_dir.join("mysql-useradm"),
|
target_profile_dir.join("mysql-useradm"),
|
||||||
)
|
)
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
if !target_profile_dir.join("mysql-dbadm").exists() {
|
if !target_profile_dir.join("mysql-dbadm").exists() {
|
||||||
symlink(
|
symlink(
|
||||||
target_profile_dir.join("mysqladm"),
|
target_profile_dir.join("mysqladm"),
|
||||||
target_profile_dir.join("mysql-dbadm"),
|
target_profile_dir.join("mysql-dbadm"),
|
||||||
)
|
)
|
||||||
.ok();
|
.ok();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn main() -> anyhow::Result<()> {
|
||||||
|
#[cfg(feature = "mysql-admutils-compatibility")]
|
||||||
|
generate_mysql_admutils_symlinks()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
# This should go to `/etc/mysqladm/config.toml`
|
# This should go to `/etc/mysqladm/config.toml`
|
||||||
|
|
||||||
|
[server]
|
||||||
|
socket_path = "/var/run/mysqladm/mysqladm.sock"
|
||||||
|
|
||||||
[mysql]
|
[mysql]
|
||||||
host = "localhost"
|
host = "localhost"
|
||||||
port = 3306
|
port = 3306
|
||||||
|
|
12
flake.lock
12
flake.lock
|
@ -2,11 +2,11 @@
|
||||||
"nodes": {
|
"nodes": {
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1713297878,
|
"lastModified": 1723637854,
|
||||||
"narHash": "sha256-hOkzkhLT59wR8VaMbh1ESjtZLbGi+XNaBN6h49SPqEc=",
|
"narHash": "sha256-med8+5DSWa2UnOqtdICndjDAEjxr5D7zaIiK4pn0Q7c=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "66adc1e47f8784803f2deb6cacd5e07264ec2d5c",
|
"rev": "c3aa7b8938b17aebd2deecf7be0636000d62a2b9",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
@ -29,11 +29,11 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1720577957,
|
"lastModified": 1723947704,
|
||||||
"narHash": "sha256-RZuzLdB/8FaXaSzEoWLg3au/mtbuH7MGn2LmXUKT62g=",
|
"narHash": "sha256-TcVf66N2NgGhxORFytzgqWcg0XJ+kk8uNLNsTRI5sYM=",
|
||||||
"owner": "oxalica",
|
"owner": "oxalica",
|
||||||
"repo": "rust-overlay",
|
"repo": "rust-overlay",
|
||||||
"rev": "a434177dfcc53bf8f1f348a3c39bfb336d760286",
|
"rev": "456e78a55feade2c3bc6d7bc0bf5e710c9d86120",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
|
@ -52,11 +52,16 @@
|
||||||
|
|
||||||
overlays = {
|
overlays = {
|
||||||
default = self.overlays.mysqladm-rs;
|
default = self.overlays.mysqladm-rs;
|
||||||
greg-ng = final: prev: {
|
mysqladm-rs = final: prev: {
|
||||||
inherit (self.packages.${prev.system}) mysqladm-rs;
|
inherit (self.packages.${prev.system}) mysqladm-rs;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
nixosModules = {
|
||||||
|
default = self.nixosModules.mysqladm-rs;
|
||||||
|
mysqladm-rs = import ./nix/module.nix;
|
||||||
|
};
|
||||||
|
|
||||||
packages = let
|
packages = let
|
||||||
cargoToml = builtins.fromTOML (builtins.readFile ./Cargo.toml);
|
cargoToml = builtins.fromTOML (builtins.readFile ./Cargo.toml);
|
||||||
cargoLock = ./Cargo.lock;
|
cargoLock = ./Cargo.lock;
|
||||||
|
|
|
@ -4,8 +4,10 @@
|
||||||
, cargoToml
|
, cargoToml
|
||||||
, cargoLock
|
, cargoLock
|
||||||
, src
|
, src
|
||||||
|
, installShellFiles
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
|
mainProgram = (lib.head cargoToml.bin).name;
|
||||||
in
|
in
|
||||||
rustPlatform.buildRustPackage {
|
rustPlatform.buildRustPackage {
|
||||||
pname = cargoToml.package.name;
|
pname = cargoToml.package.name;
|
||||||
|
@ -14,9 +16,20 @@ rustPlatform.buildRustPackage {
|
||||||
|
|
||||||
cargoLock.lockFile = cargoLock;
|
cargoLock.lockFile = cargoLock;
|
||||||
|
|
||||||
|
nativeBuildInputs = [ installShellFiles ];
|
||||||
|
postInstall = let
|
||||||
|
commands = lib.mapCartesianProduct ({ shell, command }: ''
|
||||||
|
"$out/bin/${mainProgram}" generate-completions --shell "${shell}" --command "${command}" > "$TMP/mysqladm.${shell}"
|
||||||
|
installShellCompletion "--${shell}" --cmd "${command}" "$TMP/mysqladm.${shell}"
|
||||||
|
'') {
|
||||||
|
shell = [ "bash" "zsh" "fish" ];
|
||||||
|
command = [ "mysqladm" "mysql-dbadm" "mysql-useradm" ];
|
||||||
|
};
|
||||||
|
in lib.concatStringsSep "\n" commands;
|
||||||
|
|
||||||
meta = with lib; {
|
meta = with lib; {
|
||||||
license = licenses.mit;
|
license = licenses.mit;
|
||||||
platforms = platforms.linux ++ platforms.darwin;
|
platforms = platforms.linux ++ platforms.darwin;
|
||||||
mainProgram = (lib.head cargoToml.bin).name;
|
inherit mainProgram;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,108 @@
|
||||||
|
{ config, pkgs, lib, ... }:
|
||||||
|
let
|
||||||
|
cfg = config.services.mysqladm-rs;
|
||||||
|
format = pkgs.formats.toml { };
|
||||||
|
in
|
||||||
|
{
|
||||||
|
options.services.mysqladm-rs = {
|
||||||
|
enable = lib.mkEnableOption "Enable mysqladm-rs";
|
||||||
|
|
||||||
|
package = lib.mkPackageOption pkgs "mysqladm-rs" { };
|
||||||
|
|
||||||
|
createLocalUser = lib.mkOption {
|
||||||
|
type = lib.types.bool;
|
||||||
|
default = false;
|
||||||
|
description = "Create a local database user for mysqladm-rs";
|
||||||
|
};
|
||||||
|
|
||||||
|
settings = lib.mkOption {
|
||||||
|
default = { };
|
||||||
|
type = lib.types.submodule {
|
||||||
|
freeformType = format.type;
|
||||||
|
options = {
|
||||||
|
server = {
|
||||||
|
socket_path = lib.mkOption {
|
||||||
|
type = lib.types.path;
|
||||||
|
default = "/var/run/mysqladm/mysqladm.sock";
|
||||||
|
description = "Path to the MySQL socket";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
mysql = {
|
||||||
|
host = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "localhost";
|
||||||
|
description = "MySQL host";
|
||||||
|
};
|
||||||
|
port = lib.mkOption {
|
||||||
|
type = lib.types.int;
|
||||||
|
default = 3306;
|
||||||
|
description = "MySQL port";
|
||||||
|
};
|
||||||
|
username = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "root";
|
||||||
|
description = "MySQL username";
|
||||||
|
};
|
||||||
|
# passwordFile = lib.mkOption {
|
||||||
|
# type = lib.types.path;
|
||||||
|
# default = "secret";
|
||||||
|
# description = "Path to a file containing the MySQL password";
|
||||||
|
# };
|
||||||
|
password = lib.mkOption {
|
||||||
|
type = lib.types.str;
|
||||||
|
default = "secret";
|
||||||
|
description = "MySQL password";
|
||||||
|
};
|
||||||
|
timeout = lib.mkOption {
|
||||||
|
type = lib.types.int;
|
||||||
|
default = 2;
|
||||||
|
description = "Number of seconds to wait for a response from the MySQL server";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
config = let
|
||||||
|
configFile = format.generate "mysqladm-rs.conf" cfg.settings;
|
||||||
|
in lib.mkIf config.services.mysqladm-rs.enable {
|
||||||
|
environment.systemPackages = [ cfg.package ];
|
||||||
|
|
||||||
|
services.mysql.ensureUsers = lib.mkIf cfg.createLocalUser [
|
||||||
|
{
|
||||||
|
name = "mysqladm";
|
||||||
|
ensurePermissions = {
|
||||||
|
"mysql.*" = "SELECT, INSERT, UPDATE, DELETE";
|
||||||
|
"information_schema.*" = "SELECT";
|
||||||
|
"*.*" = "CREATE USER, GRANT OPTION";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
systemd.services."mysqladm@" = {
|
||||||
|
description = "MySQL administration tool for non-admin users";
|
||||||
|
# after = [ "mysql.target" ];
|
||||||
|
environment.RUST_LOG = "debug";
|
||||||
|
serviceConfig = {
|
||||||
|
Type = "notify";
|
||||||
|
ExecStart = "${lib.getExe cfg.package} server socket-activate --config ${configFile}";
|
||||||
|
User = "mysqladm";
|
||||||
|
Group = "mysqladm";
|
||||||
|
DynamicUser = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
systemd.sockets."mysqladm" = {
|
||||||
|
description = "MySQL administration tool for non-admin users";
|
||||||
|
wantedBy = [ "sockets.target" ];
|
||||||
|
restartTriggers = [ configFile ];
|
||||||
|
socketConfig = {
|
||||||
|
ListenStream = cfg.settings.server.socket_path;
|
||||||
|
Accept = "yes";
|
||||||
|
PassCredentials = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
|
@ -49,7 +49,19 @@ The Y/N-values corresponds to the following mysql privileges:
|
||||||
References - Enables use of REFERENCES
|
References - Enables use of REFERENCES
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
|
/// Create, drop or edit permissions for the DATABASE(s),
|
||||||
|
/// as determined by the COMMAND.
|
||||||
|
///
|
||||||
|
/// This is a compatibility layer for the mysql-dbadm command.
|
||||||
|
/// Please consider using the newer mysqladm command instead.
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
|
#[command(
|
||||||
|
bin_name = "mysql-dbadm",
|
||||||
|
version,
|
||||||
|
about,
|
||||||
|
disable_help_subcommand = true,
|
||||||
|
verbatim_doc_comment,
|
||||||
|
)]
|
||||||
pub struct Args {
|
pub struct Args {
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
pub command: Option<Command>,
|
pub command: Option<Command>,
|
||||||
|
@ -82,14 +94,7 @@ pub struct Args {
|
||||||
// NOTE: mysql-dbadm explicitly calls privileges "permissions".
|
// NOTE: mysql-dbadm explicitly calls privileges "permissions".
|
||||||
// This is something we're trying to move away from.
|
// This is something we're trying to move away from.
|
||||||
// See https://git.pvv.ntnu.no/Projects/mysqladm-rs/issues/29
|
// See https://git.pvv.ntnu.no/Projects/mysqladm-rs/issues/29
|
||||||
|
|
||||||
/// Create, drop or edit permissions for the DATABASE(s),
|
|
||||||
/// as determined by the COMMAND.
|
|
||||||
///
|
|
||||||
/// This is a compatibility layer for the mysql-dbadm command.
|
|
||||||
/// Please consider using the newer mysqladm command instead.
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(version, about, disable_help_subcommand = true, verbatim_doc_comment)]
|
|
||||||
pub enum Command {
|
pub enum Command {
|
||||||
/// create the DATABASE(s).
|
/// create the DATABASE(s).
|
||||||
Create(CreateArgs),
|
Create(CreateArgs),
|
||||||
|
|
|
@ -25,7 +25,19 @@ use crate::{
|
||||||
server::sql::user_operations::DatabaseUser,
|
server::sql::user_operations::DatabaseUser,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Create, delete or change password for the USER(s),
|
||||||
|
/// as determined by the COMMAND.
|
||||||
|
///
|
||||||
|
/// This is a compatibility layer for the mysql-useradm command.
|
||||||
|
/// Please consider using the newer mysqladm command instead.
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
|
#[command(
|
||||||
|
bin_name = "mysql-useradm",
|
||||||
|
version,
|
||||||
|
about,
|
||||||
|
disable_help_subcommand = true,
|
||||||
|
verbatim_doc_comment,
|
||||||
|
)]
|
||||||
pub struct Args {
|
pub struct Args {
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
pub command: Option<Command>,
|
pub command: Option<Command>,
|
||||||
|
@ -51,13 +63,7 @@ pub struct Args {
|
||||||
config: Option<PathBuf>,
|
config: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create, delete or change password for the USER(s),
|
|
||||||
/// as determined by the COMMAND.
|
|
||||||
///
|
|
||||||
/// This is a compatibility layer for the mysql-useradm command.
|
|
||||||
/// Please consider using the newer mysqladm command instead.
|
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[command(version, about, disable_help_subcommand = true, verbatim_doc_comment)]
|
|
||||||
pub enum Command {
|
pub enum Command {
|
||||||
/// create the USER(s).
|
/// create the USER(s).
|
||||||
Create(CreateArgs),
|
Create(CreateArgs),
|
||||||
|
|
|
@ -6,7 +6,8 @@ use futures_util::{SinkExt, StreamExt};
|
||||||
use crate::core::protocol::{
|
use crate::core::protocol::{
|
||||||
print_create_users_output_status, print_drop_users_output_status,
|
print_create_users_output_status, print_drop_users_output_status,
|
||||||
print_lock_users_output_status, print_set_password_output_status,
|
print_lock_users_output_status, print_set_password_output_status,
|
||||||
print_unlock_users_output_status, ClientToServerMessageStream, Request, Response,
|
print_unlock_users_output_status, ClientToServerMessageStream, ListUsersError, Request,
|
||||||
|
Response,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::common::erroneous_server_response;
|
use super::common::erroneous_server_response;
|
||||||
|
@ -207,6 +208,28 @@ async fn passwd_user(
|
||||||
args: UserPasswdArgs,
|
args: UserPasswdArgs,
|
||||||
mut server_connection: ClientToServerMessageStream,
|
mut server_connection: ClientToServerMessageStream,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
|
// TODO: create a "user" exists check" command
|
||||||
|
let message = Request::ListUsers(Some(vec![args.username.clone()]));
|
||||||
|
if let Err(err) = server_connection.send(message).await {
|
||||||
|
server_connection.close().await.ok();
|
||||||
|
anyhow::bail!(err);
|
||||||
|
}
|
||||||
|
let response = match server_connection.next().await {
|
||||||
|
Some(Ok(Response::ListUsers(users))) => users,
|
||||||
|
response => return erroneous_server_response(response),
|
||||||
|
};
|
||||||
|
match response
|
||||||
|
.get(&args.username)
|
||||||
|
.unwrap_or(&Err(ListUsersError::UserDoesNotExist))
|
||||||
|
{
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(err) => {
|
||||||
|
server_connection.send(Request::Exit).await?;
|
||||||
|
server_connection.close().await.ok();
|
||||||
|
anyhow::bail!("{}", err.to_error_message(&args.username));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let password = if let Some(password_file) = args.password_file {
|
let password = if let Some(password_file) = args.password_file {
|
||||||
std::fs::read_to_string(password_file)
|
std::fs::read_to_string(password_file)
|
||||||
.context("Failed to read password file")?
|
.context("Failed to read password file")?
|
||||||
|
|
|
@ -76,6 +76,7 @@ impl OwnerValidationError {
|
||||||
indoc! {r#"
|
indoc! {r#"
|
||||||
Invalid {} name prefix: '{}' does not match your username or any of your groups.
|
Invalid {} name prefix: '{}' does not match your username or any of your groups.
|
||||||
Are you sure you are allowed to create {} names with this prefix?
|
Are you sure you are allowed to create {} names with this prefix?
|
||||||
|
The format should be: <prefix>_<{} name>
|
||||||
|
|
||||||
Allowed prefixes:
|
Allowed prefixes:
|
||||||
- {}
|
- {}
|
||||||
|
@ -84,6 +85,7 @@ impl OwnerValidationError {
|
||||||
db_or_user.lowercased(),
|
db_or_user.lowercased(),
|
||||||
name,
|
name,
|
||||||
db_or_user.lowercased(),
|
db_or_user.lowercased(),
|
||||||
|
db_or_user.lowercased(),
|
||||||
user.as_ref()
|
user.as_ref()
|
||||||
.map(|u| u.username.clone())
|
.map(|u| u.username.clone())
|
||||||
.unwrap_or("???".to_string()),
|
.unwrap_or("???".to_string()),
|
||||||
|
|
121
src/main.rs
121
src/main.rs
|
@ -1,7 +1,8 @@
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate prettytable;
|
extern crate prettytable;
|
||||||
|
|
||||||
use clap::Parser;
|
use clap::{CommandFactory, Parser, ValueEnum};
|
||||||
|
use clap_complete::{generate, Shell};
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
@ -27,7 +28,14 @@ mod core;
|
||||||
#[cfg(feature = "tui")]
|
#[cfg(feature = "tui")]
|
||||||
mod tui;
|
mod tui;
|
||||||
|
|
||||||
|
/// Database administration tool for non-admin users to manage their own MySQL databases and users.
|
||||||
|
///
|
||||||
|
/// This tool allows you to manage users and databases in MySQL.
|
||||||
|
///
|
||||||
|
/// You are only allowed to manage databases and users that are prefixed with
|
||||||
|
/// either your username, or a group that you are a member of.
|
||||||
#[derive(Parser, Debug)]
|
#[derive(Parser, Debug)]
|
||||||
|
#[command(bin_name = "mysqladm", version, about, disable_help_subcommand = true)]
|
||||||
struct Args {
|
struct Args {
|
||||||
#[command(subcommand)]
|
#[command(subcommand)]
|
||||||
command: Command,
|
command: Command,
|
||||||
|
@ -57,14 +65,7 @@ struct Args {
|
||||||
interactive: bool,
|
interactive: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Database administration tool for non-admin users to manage their own MySQL databases and users.
|
|
||||||
//
|
|
||||||
// This tool allows you to manage users and databases in MySQL.
|
|
||||||
//
|
|
||||||
// You are only allowed to manage databases and users that are prefixed with
|
|
||||||
// either your username, or a group that you are a member of.
|
|
||||||
#[derive(Parser, Debug, Clone)]
|
#[derive(Parser, Debug, Clone)]
|
||||||
#[command(version, about, disable_help_subcommand = true)]
|
|
||||||
enum Command {
|
enum Command {
|
||||||
#[command(flatten)]
|
#[command(flatten)]
|
||||||
Db(cli::database_command::DatabaseCommand),
|
Db(cli::database_command::DatabaseCommand),
|
||||||
|
@ -74,6 +75,26 @@ enum Command {
|
||||||
|
|
||||||
#[command(hide = true)]
|
#[command(hide = true)]
|
||||||
Server(server::command::ServerArgs),
|
Server(server::command::ServerArgs),
|
||||||
|
|
||||||
|
#[command(hide = true)]
|
||||||
|
GenerateCompletions(GenerateCompletionArgs),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Parser, Debug, Clone)]
|
||||||
|
struct GenerateCompletionArgs {
|
||||||
|
#[arg(long, default_value = "bash")]
|
||||||
|
shell: Shell,
|
||||||
|
|
||||||
|
#[arg(long, default_value = "mysqladm")]
|
||||||
|
command: ToplevelCommands,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "mysql-admutils-compatibility")]
|
||||||
|
#[derive(ValueEnum, Debug, Clone)]
|
||||||
|
enum ToplevelCommands {
|
||||||
|
Mysqladm,
|
||||||
|
MysqlDbadm,
|
||||||
|
MysqlUseradm,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: tag all functions that are run with elevated privileges with
|
// TODO: tag all functions that are run with elevated privileges with
|
||||||
|
@ -86,28 +107,18 @@ fn main() -> anyhow::Result<()> {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
|
|
||||||
#[cfg(feature = "mysql-admutils-compatibility")]
|
#[cfg(feature = "mysql-admutils-compatibility")]
|
||||||
{
|
if let Some(_) = handle_mysql_admutils_command()? {
|
||||||
let argv0 = std::env::args().next().and_then(|s| {
|
return Ok(());
|
||||||
PathBuf::from(s)
|
|
||||||
.file_name()
|
|
||||||
.map(|s| s.to_string_lossy().to_string())
|
|
||||||
});
|
|
||||||
|
|
||||||
match argv0.as_deref() {
|
|
||||||
Some("mysql-dbadm") => return mysql_dbadm::main(),
|
|
||||||
Some("mysql-useradm") => return mysql_useradm::main(),
|
|
||||||
_ => { /* fall through */ }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let args: Args = Args::parse();
|
let args: Args = Args::parse();
|
||||||
match args.command {
|
|
||||||
Command::Server(ref command) => {
|
if let Some(_) = handle_server_command(&args)? {
|
||||||
drop_privs()?;
|
return Ok(());
|
||||||
tokio_start_server(args.server_socket_path, args.config, command.clone())?;
|
}
|
||||||
return Ok(());
|
|
||||||
}
|
if let Some(_) = handle_generate_completions_command(&args)? {
|
||||||
_ => { /* fall through */ }
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let server_connection =
|
let server_connection =
|
||||||
|
@ -118,6 +129,61 @@ fn main() -> anyhow::Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_mysql_admutils_command() -> anyhow::Result<Option<()>> {
|
||||||
|
let argv0 = std::env::args().next().and_then(|s| {
|
||||||
|
PathBuf::from(s)
|
||||||
|
.file_name()
|
||||||
|
.map(|s| s.to_string_lossy().to_string())
|
||||||
|
});
|
||||||
|
|
||||||
|
match argv0.as_deref() {
|
||||||
|
Some("mysql-dbadm") => mysql_dbadm::main().map(|result| Some(result)),
|
||||||
|
Some("mysql-useradm") => mysql_useradm::main().map(|result| Some(result)),
|
||||||
|
_ => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_server_command(args: &Args) -> anyhow::Result<Option<()>> {
|
||||||
|
match args.command {
|
||||||
|
Command::Server(ref command) => {
|
||||||
|
drop_privs()?;
|
||||||
|
tokio_start_server(
|
||||||
|
args.server_socket_path.clone(),
|
||||||
|
args.config.clone(),
|
||||||
|
command.clone(),
|
||||||
|
)?;
|
||||||
|
Ok(Some(()))
|
||||||
|
}
|
||||||
|
_ => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_generate_completions_command(args: &Args) -> anyhow::Result<Option<()>> {
|
||||||
|
match args.command {
|
||||||
|
Command::GenerateCompletions(ref completion_args) => {
|
||||||
|
let mut cmd = match completion_args.command {
|
||||||
|
ToplevelCommands::Mysqladm => Args::command(),
|
||||||
|
#[cfg(feature = "mysql-admutils-compatibility")]
|
||||||
|
ToplevelCommands::MysqlDbadm => mysql_dbadm::Args::command(),
|
||||||
|
#[cfg(feature = "mysql-admutils-compatibility")]
|
||||||
|
ToplevelCommands::MysqlUseradm => mysql_useradm::Args::command(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let binary_name = cmd.get_bin_name().unwrap().to_owned();
|
||||||
|
|
||||||
|
generate(
|
||||||
|
completion_args.shell,
|
||||||
|
&mut cmd,
|
||||||
|
binary_name,
|
||||||
|
&mut std::io::stdout(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(Some(()))
|
||||||
|
}
|
||||||
|
_ => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn tokio_start_server(
|
fn tokio_start_server(
|
||||||
server_socket_path: Option<PathBuf>,
|
server_socket_path: Option<PathBuf>,
|
||||||
config_path: Option<PathBuf>,
|
config_path: Option<PathBuf>,
|
||||||
|
@ -148,6 +214,7 @@ fn tokio_run_command(command: Command, server_connection: StdUnixStream) -> anyh
|
||||||
cli::database_command::handle_command(db_args, message_stream).await
|
cli::database_command::handle_command(db_args, message_stream).await
|
||||||
}
|
}
|
||||||
Command::Server(_) => unreachable!(),
|
Command::Server(_) => unreachable!(),
|
||||||
|
Command::GenerateCompletions(_) => unreachable!(),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,7 @@ pub fn validate_ownership_by_prefixes(
|
||||||
|
|
||||||
if prefixes
|
if prefixes
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|p| name.starts_with(*p))
|
.filter(|p| name.starts_with(&(p.to_string() + "_")))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.is_empty()
|
.is_empty()
|
||||||
{
|
{
|
||||||
|
|
Loading…
Reference in New Issue