7 Commits

Author SHA1 Message Date
48e307842e WIP 2025-12-15 18:02:02 +09:00
912f0e8971 server: hide systemd stuff behind compiletime cond
All checks were successful
Build and test / check (push) Successful in 1m49s
Build and test / check-license (push) Successful in 1m49s
Build and test / build (push) Successful in 2m36s
Build and test / test (push) Successful in 3m25s
Build and test / docs (push) Successful in 6m35s
2025-12-15 17:02:53 +09:00
73f5cd9fd4 .gitea/workflows: actions-rs/toolchain -> dtolnay/rust-toolchain
All checks were successful
Build and test / check-license (push) Successful in 59s
Build and test / check (push) Successful in 1m42s
Build and test / build (push) Successful in 3m2s
Build and test / test (push) Successful in 3m27s
Build and test / docs (push) Successful in 5m7s
2025-12-15 16:22:15 +09:00
caf16c7a21 .gitea/workflows: use cargo-binstall everywhere, disable telemetry
Some checks failed
Build and test / check (push) Successful in 1m44s
Build and test / check-license (push) Successful in 1m48s
Build and test / build (push) Successful in 2m52s
Build and test / test (push) Successful in 3m1s
Build and test / docs (push) Has been cancelled
2025-12-15 16:15:06 +09:00
aac7315fd9 .gitea/workflows: name artifact zips with commit hash
All checks were successful
Build and test / check (push) Successful in 1m40s
Build and test / build (push) Successful in 2m52s
Build and test / test (push) Successful in 3m4s
Build and test / check-license (push) Successful in 6m18s
Build and test / docs (push) Successful in 5m59s
2025-12-15 16:06:22 +09:00
aa96587a35 assets/debian/config.toml: leave link to installation instructions
Some checks failed
Build and test / build (push) Successful in 2m37s
Build and test / check (push) Successful in 2m51s
Build and test / docs (push) Has been cancelled
Build and test / test (push) Has been cancelled
Build and test / check-license (push) Has been cancelled
2025-12-15 16:00:52 +09:00
15ebc5df5b Cargo.toml: (deb) install documentation 2025-12-15 16:00:27 +09:00
10 changed files with 190 additions and 104 deletions

View File

@@ -6,6 +6,9 @@ on:
- main
pull_request:
env:
BINSTALL_DISABLE_TELEMETRY: 'true'
jobs:
build:
runs-on: debian-latest
@@ -13,10 +16,7 @@ jobs:
- uses: actions/checkout@v6
- name: Install rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
uses: dtolnay/rust-toolchain@stable
- name: Build
run: cargo build --all-features --verbose --release
@@ -27,10 +27,8 @@ jobs:
- uses: actions/checkout@v6
- name: Install rust toolchain
uses: actions-rs/toolchain@v1
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
override: true
components: rustfmt, clippy
- name: Check code format
@@ -43,15 +41,13 @@ jobs:
runs-on: debian-latest
steps:
- uses: actions/checkout@v6
- uses: cargo-bins/cargo-binstall@main
- name: Install rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
uses: dtolnay/rust-toolchain@stable
- name: Install cargo-deny
run: cargo install cargo-deny
run: cargo binstall -y cargo-deny
- name: Check licenses
run: |
@@ -72,8 +68,7 @@ jobs:
run: cargo binstall -y cargo-nextest --secure
- name: Run tests
run: |
cargo nextest run --release --no-fail-fast
run: cargo nextest run --release --no-fail-fast
env:
RUST_LOG: "trace"
RUSTFLAGS: "-Cinstrument-coverage"
@@ -116,10 +111,7 @@ jobs:
- uses: actions/checkout@v6
- name: Install rust toolchain
uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
uses: dtolnay/rust-toolchain@stable
- name: Build docs
run: cargo doc --all-features --document-private-items --release

View File

@@ -22,6 +22,9 @@ on:
- beta
default: stable
env:
BINSTALL_DISABLE_TELEMETRY: 'true'
# TODO: dynamic matrix builds when...
# https://github.com/go-gitea/gitea/issues/25179
jobs:
@@ -33,15 +36,15 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v6
- uses: cargo-bins/cargo-binstall@main
- name: Install rust toolchain
uses: actions-rs/toolchain@v1
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ inputs.rust_toolchain }}
override: true
- name: Install cargo-deb
run: cargo install cargo-deb
run: cargo binstall -y cargo-deb
- name: Build deb package
env:
@@ -60,7 +63,7 @@ jobs:
- name: Upload deb package artifact
uses: actions/upload-artifact@v3
with:
name: muscl-deb-${{ matrix.os }}.zip
name: muscl-deb-${{ matrix.os }}-${{ gitea.sha }}.zip
path: target/debian/*.deb
if-no-files-found: error
retention-days: 30

View File

@@ -34,7 +34,6 @@ nix = { version = "0.30.1", features = ["fs", "process", "socket", "user"] }
num_cpus = "1.17.0"
prettytable = "0.10.0"
rand = "0.9.2"
sd-notify = "0.4.5"
serde = "1.0.228"
serde_json = { version = "1.0.145", features = ["preserve_order"] }
sqlx = { version = "0.8.6", features = ["runtime-tokio", "mysql", "tls-rustls"] }
@@ -45,12 +44,13 @@ tokio-stream = "0.1.17"
tokio-util = { version = "0.7.17", features = ["codec", "rt"] }
toml = "0.9.8"
tracing = { version = "0.1.43", features = ["log"] }
tracing-journald = "0.3.2"
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.4.5"
tracing-journald = "0.3.2"
[build-dependencies]
anyhow = "1.0.100"
@@ -147,6 +147,16 @@ assets = [
"usr/share/fish/vendor_completions.d/",
"644",
],
[
"README.md",
"usr/share/doc/muscl/",
"644",
],
[
"docs/*.md",
"usr/share/doc/muscl/docs/",
"644",
],
]
preserve-symlinks = true
maintainer-scripts = "debian/"

View File

@@ -16,7 +16,8 @@ port = 3306
# systemd unit.
username = "muscl"
# This file gets created by systemd automatically, given you have set
# the password with `systemd-creds`.
# the password with `systemd-creds`. See /usr/share/doc/muscl/docs/installation.md
# for more information.
password_file = "/run/credentials/muscl.service/muscl_mysql_password"
# Database connection timeout in seconds

View File

@@ -50,6 +50,7 @@ pub enum ClientCommand {
/// If no database names are provided, all databases you have access to will be shown.
ShowPrivs(ShowPrivsArgs),
// TODO: rewrite doc comment to match new CLI
/// Change user privileges for one or more databases. See `edit-privs --help` for details.
///
/// This command has two modes of operation:
@@ -108,7 +109,10 @@ pub enum ClientCommand {
///
/// `muscl edit-privs my_db -p my_user:+d
///
#[command(verbatim_doc_comment)]
#[command(
verbatim_doc_comment,
override_usage = "muscl edit-privs [OPTIONS] [ -p DATABASE:USER:[+-]PRIVILEGES... | <DB_NAME> <USER_NAME> <[+-]PRIVILEGES> ]"
)]
EditPrivs(EditPrivsArgs),
/// Create one or more users
@@ -142,7 +146,9 @@ pub async fn handle_command(
ClientCommand::DropDb(args) => drop_databases(args, server_connection).await,
ClientCommand::ShowDb(args) => show_databases(args, server_connection).await,
ClientCommand::ShowPrivs(args) => show_database_privileges(args, server_connection).await,
ClientCommand::EditPrivs(args) => edit_database_privileges(args, server_connection).await,
ClientCommand::EditPrivs(args) => {
edit_database_privileges(args, None, server_connection).await
}
ClientCommand::CreateUser(args) => create_users(args, server_connection).await,
ClientCommand::DropUser(args) => drop_users(args, server_connection).await,
ClientCommand::PasswdUser(args) => passwd_user(args, server_connection).await,

View File

@@ -1,7 +1,7 @@
use std::collections::{BTreeMap, BTreeSet};
use anyhow::Context;
use clap::Parser;
use clap::{Args, Parser};
use clap_complete::ArgValueCompleter;
use dialoguer::{Confirm, Editor};
use futures_util::SinkExt;
@@ -11,7 +11,7 @@ use tokio_stream::StreamExt;
use crate::{
client::commands::erroneous_server_response,
core::{
completion::mysql_database_completer,
completion::{mysql_database_completer, mysql_user_completer},
database_privileges::{
DatabasePrivilegeEditEntry, DatabasePrivilegeRow, DatabasePrivilegeRowDiff,
DatabasePrivilegesDiff, create_or_modify_privilege_rows, diff_privileges,
@@ -28,20 +28,24 @@ use crate::{
#[derive(Parser, Debug, Clone)]
pub struct EditPrivsArgs {
/// The MySQL database to edit privileges for
#[cfg_attr(not(feature = "suid-sgid-mode"), arg(add = ArgValueCompleter::new(mysql_database_completer)))]
#[arg(value_name = "DB_NAME")]
pub name: Option<MySQLDatabase>,
/// The privileges to set, grant or revoke, in the format `DATABASE:USER:[+-]PRIVILEGES`
///
/// This option allows for changing privileges for multiple databases and users in batch.
///
/// This can not be used together with the `DB_NAME`, `USER_NAME` and `PRIVILEGES` arguments.
#[arg(
short,
long,
value_name = "[DATABASE:]USER:[+-]PRIVILEGES",
value_name = "DATABASE:USER:[+-]PRIVILEGES",
num_args = 0..,
value_parser = DatabasePrivilegeEditEntry::parse_from_str,
conflicts_with("single_priv"),
)]
pub privs: Vec<DatabasePrivilegeEditEntry>,
#[command(flatten)]
pub single_priv: Option<SinglePrivilegeEditEntry>,
/// Print the information as JSON
#[arg(short, long)]
pub json: bool,
@@ -60,6 +64,30 @@ pub struct EditPrivsArgs {
pub yes: bool,
}
#[derive(Args, Debug, Clone)]
pub struct SinglePrivilegeEditEntry {
/// The MySQL database to edit privileges for
#[cfg_attr(not(feature = "suid-sgid-mode"), arg(add = ArgValueCompleter::new(mysql_database_completer)))]
#[arg(
value_name = "DB_NAME",
requires = "user_name",
requires = "single_priv"
)]
pub db_name: Option<MySQLDatabase>,
/// The MySQL database to edit privileges for
#[cfg_attr(not(feature = "suid-sgid-mode"), arg(add = ArgValueCompleter::new(mysql_user_completer)))]
#[arg(value_name = "USER_NAME")]
pub user_name: Option<MySQLUser>,
// TODO add a proper parser for this
/// The privileges to set, grant or revoke
#[arg(
value_name = "[+-]PRIVILEGES",
)]
pub single_priv: Option<String>,
}
async fn users_exist(
server_connection: &mut ClientToServerMessageStream,
privilege_diff: &BTreeSet<DatabasePrivilegesDiff>,
@@ -120,12 +148,43 @@ async fn databases_exist(
pub async fn edit_database_privileges(
args: EditPrivsArgs,
// NOTE: this is only used for backwards compat with mysql-admutils
use_database: Option<MySQLDatabase>,
mut server_connection: ClientToServerMessageStream,
) -> anyhow::Result<()> {
let message = Request::ListPrivileges(args.name.to_owned().map(|name| vec![name]));
// TODO: handle args properly
let message = Request::ListPrivileges(use_database.clone().map(|db| vec![db]));
server_connection.send(message).await?;
debug_assert!(args.privs.is_empty() ^ args.single_priv.is_none());
let privs = if let Some(single_priv_entry) = &args.single_priv {
let db_name = single_priv_entry.db_name.clone().or(use_database.clone());
let user_name = single_priv_entry.user_name.clone().ok_or_else(|| {
anyhow::anyhow!(
"USER_NAME must be specified when DB_NAME is specified in single privilege mode"
)
})?;
let priv_entry = single_priv_entry.single_priv.clone().ok_or_else(|| {
anyhow::anyhow!(
"PRIVILEGES must be specified when DB_NAME is specified in single privilege mode"
)
})?;
vec![DatabasePrivilegeEditEntry {
database: db_name.ok_or_else(|| {
anyhow::anyhow!(
"DB_NAME must be specified when editing privileges in single privilege mode"
)
})?,
user: user_name,
privileges: priv_entry.privileges,
}]
} else {
args.privs.clone()
};
let existing_privilege_rows = match server_connection.next().await {
Some(Ok(Response::ListPrivileges(databases))) => databases
.into_iter()
@@ -156,7 +215,7 @@ pub async fn edit_database_privileges(
create_or_modify_privilege_rows(&existing_privilege_rows, &privileges_to_change)?
} else {
let privileges_to_change =
edit_privileges_with_editor(&existing_privilege_rows, args.name.as_ref())?;
edit_privileges_with_editor(&existing_privilege_rows, use_database.as_ref())?;
diff_privileges(&existing_privilege_rows, &privileges_to_change)
};
@@ -228,7 +287,7 @@ fn parse_privilege_tables_from_args(
.iter()
.map(|priv_edit_entry| {
priv_edit_entry
.as_database_privileges_diff(args.name.as_ref())
.as_database_privileges_diff(None)
.context(format!(
"Failed parsing database privileges: `{}`",
priv_edit_entry

View File

@@ -208,14 +208,19 @@ fn tokio_run_command(command: Command, server_connection: StdUnixStream) -> anyh
Command::Show(args) => show_databases(args, message_stream).await,
Command::Editperm(args) => {
let edit_privileges_args = EditPrivsArgs {
name: Some(args.database),
single_priv: None,
privs: vec![],
json: false,
editor: None,
yes: false,
};
edit_database_privileges(edit_privileges_args, message_stream).await
edit_database_privileges(
edit_privileges_args,
Some(args.database),
message_stream,
)
.await
}
}
})

View File

@@ -17,10 +17,10 @@ pub enum DatabasePrivilegeEditEntryType {
///
/// This is typically parsed from a string looking like:
///
/// `[database_name:]username:[+|-]privileges`
/// `database_name:username:[+|-]privileges`
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DatabasePrivilegeEditEntry {
pub database: Option<MySQLDatabase>,
pub database: MySQLDatabase,
pub user: MySQLUser,
pub type_: DatabasePrivilegeEditEntryType,
pub privileges: Vec<String>,
@@ -31,25 +31,21 @@ impl DatabasePrivilegeEditEntry {
///
/// The expected format is:
///
/// `[database_name:]username:[+|-]privileges`
/// `database_name:username:[+|-]privileges`
///
/// where:
/// - database_name is optional, if omitted the entry applies to all databases
/// - database_name is the name of the database to edit privileges for
/// - username is the name of the user to edit privileges for
/// - privileges is a string of characters representing the privileges to add, set or remove
/// - the `+` or `-` prefix indicates whether to add or remove the privileges, if omitted the privileges are set directly
/// - privileges characters are: siudcDaAItlrA
pub fn parse_from_str(arg: &str) -> anyhow::Result<DatabasePrivilegeEditEntry> {
let parts: Vec<&str> = arg.split(':').collect();
if parts.len() < 2 || parts.len() > 3 {
if parts.len() != 3 {
anyhow::bail!("Invalid privilege edit entry format: {}", arg);
}
let (database, user, user_privs) = if parts.len() == 3 {
(Some(parts[0].to_string()), parts[1].to_string(), parts[2])
} else {
(None, parts[0].to_string(), parts[1])
};
let (database, user, user_privs) = (parts[0].to_string(), parts[1].to_string(), parts[2]);
if user.is_empty() {
anyhow::bail!("Username cannot be empty in privilege edit entry: {}", arg);
@@ -77,34 +73,19 @@ impl DatabasePrivilegeEditEntry {
}
Ok(DatabasePrivilegeEditEntry {
database: database.map(MySQLDatabase::from),
database: MySQLDatabase::from(database),
user: MySQLUser::from(user),
type_: edit_type,
privileges,
})
}
pub fn as_database_privileges_diff(
&self,
external_database_name: Option<&MySQLDatabase>,
) -> anyhow::Result<DatabasePrivilegeRowDiff> {
let database = match self.database.as_ref() {
Some(db) => db.clone(),
None => {
if let Some(external_db) = external_database_name {
external_db.clone()
} else {
anyhow::bail!(
"Database name must be specified either in the privilege edit entry or as an external argument."
);
}
}
};
pub fn as_database_privileges_diff(&self) -> anyhow::Result<DatabasePrivilegeRowDiff> {
let mut diff;
match self.type_ {
DatabasePrivilegeEditEntryType::Set => {
diff = DatabasePrivilegeRowDiff {
db: database,
db: self.database.clone(),
user: self.user.clone(),
select_priv: Some(DatabasePrivilegeChange::YesToNo),
insert_priv: Some(DatabasePrivilegeChange::YesToNo),
@@ -150,7 +131,7 @@ impl DatabasePrivilegeEditEntry {
}
DatabasePrivilegeEditEntryType::Add | DatabasePrivilegeEditEntryType::Remove => {
diff = DatabasePrivilegeRowDiff {
db: database,
db: self.database.clone(),
user: self.user.clone(),
select_priv: None,
insert_priv: None,
@@ -207,9 +188,7 @@ impl DatabasePrivilegeEditEntry {
impl std::fmt::Display for DatabasePrivilegeEditEntry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(db) = &self.database {
write!(f, "{}:, ", db)?;
}
write!(f, "{}:, ", self.database)?;
write!(f, "{}: ", self.user)?;
match self.type_ {
DatabasePrivilegeEditEntryType::Add => write!(f, "+")?,
@@ -234,7 +213,7 @@ mod tests {
assert_eq!(
result.ok(),
Some(DatabasePrivilegeEditEntry {
database: Some("db".into()),
database: "db".into(),
user: "user".into(),
type_: DatabasePrivilegeEditEntryType::Set,
privileges: vec!["A".into()],
@@ -248,7 +227,7 @@ mod tests {
assert_eq!(
result.ok(),
Some(DatabasePrivilegeEditEntry {
database: Some("db".into()),
database: "db".into(),
user: "user".into(),
type_: DatabasePrivilegeEditEntryType::Set,
privileges: vec![],
@@ -262,7 +241,7 @@ mod tests {
assert_eq!(
result.ok(),
Some(DatabasePrivilegeEditEntry {
database: Some("db".into()),
database: "db".into(),
user: "user".into(),
type_: DatabasePrivilegeEditEntryType::Set,
privileges: vec!["s".into(), "i".into(), "u".into(), "d".into()],
@@ -270,20 +249,6 @@ mod tests {
);
}
#[test]
fn test_cli_arg_parse_set_user_nonexistent_misc() {
let result = DatabasePrivilegeEditEntry::parse_from_str("user:siud");
assert_eq!(
result.ok(),
Some(DatabasePrivilegeEditEntry {
database: None,
user: "user".into(),
type_: DatabasePrivilegeEditEntryType::Set,
privileges: vec!["s".into(), "i".into(), "u".into(), "d".into()],
}),
);
}
#[test]
fn test_cli_arg_parse_set_db_user_nonexistent_privilege() {
let result = DatabasePrivilegeEditEntry::parse_from_str("db:user:F");
@@ -308,7 +273,7 @@ mod tests {
assert_eq!(
result.ok(),
Some(DatabasePrivilegeEditEntry {
database: Some("db".into()),
database: "db".into(),
user: "user".into(),
type_: DatabasePrivilegeEditEntryType::Add,
privileges: vec!["s".into(), "i".into(), "u".into(), "d".into()],
@@ -322,7 +287,7 @@ mod tests {
assert_eq!(
result.ok(),
Some(DatabasePrivilegeEditEntry {
database: Some("db".into()),
database: "db".into(),
user: "user".into(),
type_: DatabasePrivilegeEditEntryType::Remove,
privileges: vec!["s".into(), "i".into(), "u".into(), "d".into()],

View File

@@ -16,6 +16,7 @@ pub struct ServerArgs {
pub subcmd: ServerCommand,
/// Enable systemd mode
#[cfg(target_os = "linux")]
#[arg(long)]
pub systemd: bool,
@@ -58,6 +59,8 @@ pub async fn handle_command(
args: ServerArgs,
) -> anyhow::Result<()> {
let mut auto_detected_systemd_mode = false;
#[cfg(target_os = "linux")]
let systemd_mode = args.systemd || {
if let Ok(true) = sd_notify::booted() {
auto_detected_systemd_mode = true;
@@ -67,24 +70,30 @@ pub async fn handle_command(
}
};
#[cfg(not(target_os = "linux"))]
let systemd_mode = false;
if systemd_mode {
let subscriber = tracing_subscriber::Registry::default()
.with(verbosity.tracing_level_filter())
.with(tracing_journald::layer()?);
#[cfg(target_os = "linux")]
{
let subscriber = tracing_subscriber::Registry::default()
.with(verbosity.tracing_level_filter())
.with(tracing_journald::layer()?);
tracing::subscriber::set_global_default(subscriber)
.context("Failed to set global default tracing subscriber")?;
tracing::subscriber::set_global_default(subscriber)
.context("Failed to set global default tracing subscriber")?;
trace_server_prelude();
trace_server_prelude();
if verbosity.tracing_level_filter() >= tracing::Level::TRACE {
tracing::warn!("{}", LOG_LEVEL_WARNING.trim());
}
if verbosity.tracing_level_filter() >= tracing::Level::TRACE {
tracing::warn!("{}", LOG_LEVEL_WARNING.trim());
}
if auto_detected_systemd_mode {
tracing::debug!("Running in systemd mode, auto-detected");
} else {
tracing::debug!("Running in systemd mode");
if auto_detected_systemd_mode {
tracing::debug!("Running in systemd mode, auto-detected");
} else {
tracing::debug!("Running in systemd mode");
}
}
} else {
let subscriber = tracing_subscriber::Registry::default()

View File

@@ -68,6 +68,7 @@ impl Supervisor {
let mut watchdog_duration = None;
let mut watchdog_micro_seconds = 0;
#[cfg(target_os = "linux")]
let watchdog_task =
if systemd_mode && sd_notify::watchdog_enabled(true, &mut watchdog_micro_seconds) {
watchdog_duration = Some(Duration::from_micros(watchdog_micro_seconds));
@@ -80,6 +81,8 @@ impl Supervisor {
tracing::debug!("Systemd watchdog not enabled, skipping watchdog thread");
None
};
#[cfg(not(target_os = "linux"))]
let watchdog_task = None;
let db_connection_pool =
Arc::new(RwLock::new(create_db_connection_pool(&config.mysql).await?));
@@ -102,19 +105,34 @@ impl Supervisor {
let task_tracker = TaskTracker::new();
#[cfg(target_os = "linux")]
let status_notifier_task = if systemd_mode {
Some(spawn_status_notifier_task(task_tracker.clone()))
} else {
None
};
#[cfg(not(target_os = "linux"))]
let status_notifier_task = None;
let (tx, rx) = broadcast::channel(1);
// TODO: try to detech systemd socket before using the provided socket path
#[cfg(target_os = "linux")]
let listener = Arc::new(RwLock::new(match config.socket_path {
Some(ref path) => create_unix_listener_with_socket_path(path.clone()).await?,
None => create_unix_listener_with_systemd_socket().await?,
}));
#[cfg(not(target_os = "linux"))]
let listener = Arc::new(RwLock::new(
create_unix_listener_with_socket_path(
config
.socket_path
.as_ref()
.ok_or(anyhow!("Socket path must be set"))?
.clone(),
)
.await?,
));
let (reload_tx, reload_rx) = broadcast::channel(1);
let shutdown_cancel_token = CancellationToken::new();
@@ -211,16 +229,28 @@ impl Supervisor {
// first. Make sure to handle that appropriately to avoid a deadlock.
async fn reload_listener(&self) -> anyhow::Result<()> {
let config = self.config.lock().await;
#[cfg(target_os = "linux")]
let new_listener = match config.socket_path {
Some(ref path) => create_unix_listener_with_socket_path(path.clone()).await?,
None => create_unix_listener_with_systemd_socket().await?,
};
#[cfg(not(target_os = "linux"))]
let new_listener = create_unix_listener_with_socket_path(
config
.socket_path
.as_ref()
.ok_or(anyhow!("Socket path must be set"))?
.clone(),
)
.await?;
let mut listener = self.listener.write().await;
*listener = new_listener;
Ok(())
}
pub async fn reload(&self) -> anyhow::Result<()> {
#[cfg(target_os = "linux")]
sd_notify::notify(false, &[sd_notify::NotifyState::Reloading])?;
let previous_config = self.config.lock().await.clone();
@@ -257,12 +287,14 @@ impl Supervisor {
self.resume_receiving_new_connections()?;
}
#[cfg(target_os = "linux")]
sd_notify::notify(false, &[sd_notify::NotifyState::Ready])?;
Ok(())
}
pub async fn shutdown(&self) -> anyhow::Result<()> {
#[cfg(target_os = "linux")]
sd_notify::notify(false, &[sd_notify::NotifyState::Stopping])?;
tracing::debug!("Stop accepting new connections");
@@ -323,6 +355,7 @@ impl Supervisor {
}
}
#[cfg(target_os = "linux")]
fn spawn_watchdog_task(duration: Duration) -> JoinHandle<()> {
tokio::spawn(async move {
let mut interval = interval(duration.div_f32(2.0));
@@ -339,6 +372,7 @@ fn spawn_watchdog_task(duration: Duration) -> JoinHandle<()> {
})
}
#[cfg(target_os = "linux")]
fn spawn_status_notifier_task(task_tracker: TaskTracker) -> JoinHandle<()> {
const STATUS_UPDATE_INTERVAL_SECS: Duration = Duration::from_secs(1);
@@ -385,6 +419,7 @@ async fn create_unix_listener_with_socket_path(
Ok(listener)
}
#[cfg(target_os = "linux")]
async fn create_unix_listener_with_systemd_socket() -> anyhow::Result<TokioUnixListener> {
let fd = sd_notify::listen_fds()
.context("Failed to get file descriptors from systemd")?
@@ -468,6 +503,7 @@ async fn listener_task(
mut supervisor_message_receiver: broadcast::Receiver<SupervisorMessage>,
db_is_mariadb: Arc<RwLock<bool>>,
) -> anyhow::Result<()> {
#[cfg(target_os = "linux")]
sd_notify::notify(false, &[sd_notify::NotifyState::Ready])?;
loop {