Compare commits

..

No commits in common. "9934b1176678758c74691facb82e5521e129a4be" and "64bcef4307398769a18a0904a8e278aa4d48d526" have entirely different histories.

6 changed files with 31 additions and 192 deletions

29
Cargo.lock generated
View File

@ -284,16 +284,6 @@ dependencies = [
"clap_derive", "clap_derive",
] ]
[[package]]
name = "clap-verbosity-flag"
version = "2.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e099138e1807662ff75e2cebe4ae2287add879245574489f9b1588eb5e5564ed"
dependencies = [
"clap",
"log",
]
[[package]] [[package]]
name = "clap_builder" name = "clap_builder"
version = "4.5.20" version = "4.5.20"
@ -506,14 +496,11 @@ dependencies = [
"anyhow", "anyhow",
"axum", "axum",
"clap", "clap",
"clap-verbosity-flag",
"env_logger", "env_logger",
"log", "log",
"mpvipc-async", "mpvipc-async",
"sd-notify",
"serde", "serde",
"serde_json", "serde_json",
"systemd-journal-logger",
"tempfile", "tempfile",
"tokio", "tokio",
"tower", "tower",
@ -996,12 +983,6 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "sd-notify"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1be20c5f7f393ee700f8b2f28ea35812e4e212f40774b550cd2a93ea91684451"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.210" version = "1.0.210"
@ -1119,16 +1100,6 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
[[package]]
name = "systemd-journal-logger"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c18918ae65f3d828ec9ab7f4714b8c564149045f47407e319dd25cadfaf9d0cf"
dependencies = [
"log",
"rustix",
]
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.13.0" version = "3.13.0"

View File

@ -12,14 +12,11 @@ readme = "README.md"
anyhow = "1.0.82" anyhow = "1.0.82"
axum = { version = "0.6.20", features = ["macros"] } axum = { version = "0.6.20", features = ["macros"] }
clap = { version = "4.4.1", features = ["derive"] } clap = { version = "4.4.1", features = ["derive"] }
clap-verbosity-flag = "2.2.2"
env_logger = "0.10.0" env_logger = "0.10.0"
log = "0.4.20" log = "0.4.20"
mpvipc-async = { git = "https://git.pvv.ntnu.no/oysteikt/mpvipc-async.git", rev = "v0.1.0" } mpvipc-async = { git = "https://git.pvv.ntnu.no/oysteikt/mpvipc-async.git", rev = "v0.1.0" }
sd-notify = "0.4.3"
serde = { version = "1.0.188", features = ["derive"] } serde = { version = "1.0.188", features = ["derive"] }
serde_json = "1.0.105" serde_json = "1.0.105"
systemd-journal-logger = "2.2.0"
tempfile = "3.11.0" tempfile = "3.11.0"
tokio = { version = "1.32.0", features = ["full"] } tokio = { version = "1.32.0", features = ["full"] }
tower = { version = "0.4.13", features = ["full"] } tower = { version = "0.4.13", features = ["full"] }

View File

@ -4,7 +4,6 @@
, rustPlatform , rustPlatform
, makeWrapper , makeWrapper
, mpv , mpv
, wrapped ? false
}: }:
rustPlatform.buildRustPackage rec { rustPlatform.buildRustPackage rec {
@ -14,20 +13,13 @@ rustPlatform.buildRustPackage rec {
baseName = baseNameOf (toString path); baseName = baseNameOf (toString path);
in !(lib.any (b: b) [ in !(lib.any (b: b) [
(!(lib.cleanSourceFilter path type)) (!(lib.cleanSourceFilter path type))
(type == "directory" && lib.elem baseName [ (baseName == "target" && type == "directory")
".direnv" (baseName == "nix" && type == "directory")
".git" (baseName == "flake.nix" && type == "regular")
"target" (baseName == "flake.lock" && type == "regular")
"result"
])
(type == "regular" && lib.elem baseName [
"flake.nix"
"default.nix"
"module.nix"
".envrc"
])
])) ./.; ])) ./.;
nativeBuildInputs = [ makeWrapper ]; nativeBuildInputs = [ makeWrapper ];
cargoLock = { cargoLock = {
@ -37,7 +29,7 @@ rustPlatform.buildRustPackage rec {
}; };
}; };
postInstall = lib.optionalString wrapped '' postInstall = ''
wrapProgram $out/bin/greg-ng \ wrapProgram $out/bin/greg-ng \
--prefix PATH : '${lib.makeBinPath [ mpv ]}' --prefix PATH : '${lib.makeBinPath [ mpv ]}'
''; '';

View File

@ -35,7 +35,7 @@
apps = forAllSystems (system: pkgs: _: { apps = forAllSystems (system: pkgs: _: {
default = self.apps.${system}.greg-ng; default = self.apps.${system}.greg-ng;
greg-ng = let greg-ng = let
package = self.packages.${system}.greg-ng-wrapped; package = self.packages.${system}.greg-ng;
in { in {
type = "app"; type = "app";
program = lib.getExe package; program = lib.getExe package;
@ -63,9 +63,6 @@
packages = forAllSystems (system: pkgs: _: { packages = forAllSystems (system: pkgs: _: {
default = self.packages.${system}.greg-ng; default = self.packages.${system}.greg-ng;
greg-ng = pkgs.callPackage ./default.nix { }; greg-ng = pkgs.callPackage ./default.nix { };
greg-ng-wrapped = pkgs.callPackage ./default.nix {
wrapped = true;
};
}); });
} // { } // {
nixosModules.default = ./module.nix; nixosModules.default = ./module.nix;

View File

@ -14,19 +14,7 @@ in
enablePipewire = lib.mkEnableOption "pipewire" // { default = true; }; enablePipewire = lib.mkEnableOption "pipewire" // { default = true; };
logLevel = lib.mkOption { enableDebug = lib.mkEnableOption "debug logs";
type = lib.types.enum [ "quiet" "error" "warn" "info" "debug" "trace" ];
default = "debug";
description = "Log level.";
apply = level: {
"quiet" = "-q";
"error" = "";
"warn" = "-v";
"info" = "-vv";
"debug" = "-vvv";
"trace" = "-vvvv";
}.${level};
};
# TODO: create some better descriptions # TODO: create some better descriptions
settings = { settings = {
@ -99,18 +87,12 @@ in
description = "greg-ng, an mpv based media player"; description = "greg-ng, an mpv based media player";
wantedBy = [ "graphical-session.target" ]; wantedBy = [ "graphical-session.target" ];
partOf = [ "graphical-session.target" ]; partOf = [ "graphical-session.target" ];
environment.RUST_LOG = lib.mkIf cfg.enableDebug "greg_ng=trace,mpvipc=trace";
serviceConfig = { serviceConfig = {
Type = "notify"; Type = "simple";
ExecStart = let ExecStart = "${lib.getExe cfg.package} ${lib.cli.toGNUCommandLineShell { } cfg.settings}";
args = lib.cli.toGNUCommandLineShell { } (cfg.settings // {
systemd = true;
});
in "${lib.getExe cfg.package} ${cfg.logLevel} ${args}";
Restart = "always"; Restart = "always";
RestartSec = 3; RestartSec = 3;
WatchdogSec = lib.mkDefault 15;
TimeoutStartSec = lib.mkDefault 30;
RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ]; RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
AmbientCapabilities = [ "" ]; AmbientCapabilities = [ "" ];

View File

@ -1,11 +1,8 @@
use anyhow::Context; use anyhow::Context;
use axum::{Router, Server}; use axum::{Router, Server};
use clap::Parser; use clap::Parser;
use clap_verbosity_flag::Verbosity;
use mpv_setup::{connect_to_mpv, create_mpv_config_file, show_grzegorz_image}; use mpv_setup::{connect_to_mpv, create_mpv_config_file, show_grzegorz_image};
use mpvipc_async::Mpv;
use std::net::{IpAddr, SocketAddr}; use std::net::{IpAddr, SocketAddr};
use systemd_journal_logger::JournalLog;
use tempfile::NamedTempFile; use tempfile::NamedTempFile;
mod api; mod api;
@ -19,12 +16,6 @@ struct Args {
#[clap(short, long, default_value = "8008")] #[clap(short, long, default_value = "8008")]
port: u16, port: u16,
#[command(flatten)]
verbose: Verbosity,
#[clap(long)]
systemd: bool,
#[clap(long, value_name = "PATH", default_value = "/run/mpv/mpv.sock")] #[clap(long, value_name = "PATH", default_value = "/run/mpv/mpv.sock")]
mpv_socket_path: String, mpv_socket_path: String,
@ -49,8 +40,6 @@ struct MpvConnectionArgs<'a> {
force_auto_start: bool, force_auto_start: bool,
} }
/// Helper function to resolve a hostname to an IP address.
/// Why is this not in the standard library? >:(
async fn resolve(host: &str) -> anyhow::Result<IpAddr> { async fn resolve(host: &str) -> anyhow::Result<IpAddr> {
let addr = format!("{}:0", host); let addr = format!("{}:0", host);
let addresses = tokio::net::lookup_host(addr).await?; let addresses = tokio::net::lookup_host(addr).await?;
@ -61,71 +50,11 @@ async fn resolve(host: &str) -> anyhow::Result<IpAddr> {
.ok_or_else(|| anyhow::anyhow!("Failed to resolve address")) .ok_or_else(|| anyhow::anyhow!("Failed to resolve address"))
} }
/// Helper function that spawns a tokio thread that
/// continuously sends a ping to systemd watchdog, if enabled.
async fn setup_systemd_watchdog_thread() -> anyhow::Result<()> {
let mut watchdog_microsecs: u64 = 0;
if sd_notify::watchdog_enabled(true, &mut watchdog_microsecs) {
watchdog_microsecs = watchdog_microsecs.div_ceil(2);
tokio::spawn(async move {
log::debug!(
"Starting systemd watchdog thread with {} millisecond interval",
watchdog_microsecs.div_ceil(1000)
);
loop {
tokio::time::sleep(tokio::time::Duration::from_micros(watchdog_microsecs)).await;
if let Err(err) = sd_notify::notify(false, &[sd_notify::NotifyState::Watchdog]) {
log::warn!("Failed to notify systemd watchdog: {}", err);
} else {
log::trace!("Ping sent to systemd watchdog");
}
}
});
} else {
log::info!("Watchdog not enabled, skipping");
}
Ok(())
}
async fn shutdown(mpv: Mpv, proc: Option<tokio::process::Child>) {
log::info!("Shutting down");
sd_notify::notify(false, &[sd_notify::NotifyState::Stopping])
.unwrap_or_else(|e| log::warn!("Failed to notify systemd that the service is stopping: {}", e));
mpv.disconnect()
.await
.unwrap_or_else(|e| log::warn!("Failed to disconnect from mpv: {}", e));
if let Some(mut proc) = proc {
proc.kill()
.await
.unwrap_or_else(|e| log::warn!("Failed to kill mpv process: {}", e));
}
}
#[tokio::main] #[tokio::main]
async fn main() -> anyhow::Result<()> { async fn main() -> anyhow::Result<()> {
env_logger::init();
let args = Args::parse(); let args = Args::parse();
let systemd_mode = args.systemd && sd_notify::booted().unwrap_or(false);
if systemd_mode {
JournalLog::new()
.context("Failed to initialize journald logging")?
.install()
.context("Failed to install journald logger")?;
log::set_max_level(args.verbose.log_level_filter());
log::debug!("Running with systemd integration");
setup_systemd_watchdog_thread().await?;
} else {
env_logger::Builder::new()
.filter_level(args.verbose.log_level_filter())
.init();
log::info!("Running without systemd integration");
}
let mpv_config_file = create_mpv_config_file(args.mpv_config_file)?; let mpv_config_file = create_mpv_config_file(args.mpv_config_file)?;
let (mpv, proc) = connect_to_mpv(&MpvConnectionArgs { let (mpv, proc) = connect_to_mpv(&MpvConnectionArgs {
@ -135,65 +64,37 @@ async fn main() -> anyhow::Result<()> {
auto_start: args.auto_start_mpv, auto_start: args.auto_start_mpv,
force_auto_start: args.force_auto_start, force_auto_start: args.force_auto_start,
}) })
.await .await?;
.context("Failed to connect to mpv")?;
if let Err(e) = show_grzegorz_image(mpv.clone()).await { if let Err(e) = show_grzegorz_image(mpv.clone()).await {
log::warn!("Could not show Grzegorz image: {}", e); log::warn!("Could not show Grzegorz image: {}", e);
} }
let addr = match resolve(&args.host) let addr = SocketAddr::new(resolve(&args.host).await?, args.port);
.await log::info!("Starting API on {}", addr);
.context(format!("Failed to resolve address: {}", &args.host))
{
Ok(addr) => addr,
Err(e) => {
log::error!("{}", e);
shutdown(mpv, proc).await;
return Err(e);
}
};
let socket_addr = SocketAddr::new(addr, args.port);
log::info!("Starting API on {}", socket_addr);
let app = Router::new().nest("/api", api::rest_api_routes(mpv.clone())); let app = Router::new().nest("/api", api::rest_api_routes(mpv.clone()));
let server = match Server::try_bind(&socket_addr.clone())
.context(format!("Failed to bind API server to '{}'", &socket_addr))
{
Ok(server) => server,
Err(e) => {
log::error!("{}", e);
shutdown(mpv, proc).await;
return Err(e);
}
};
if systemd_mode {
match sd_notify::notify(false, &[sd_notify::NotifyState::Ready])
.context("Failed to notify systemd that the service is ready")
{
Ok(_) => log::trace!("Notified systemd that the service is ready"),
Err(e) => {
log::error!("{}", e);
shutdown(mpv, proc).await;
return Err(e);
}
}
}
if let Some(mut proc) = proc { if let Some(mut proc) = proc {
tokio::select! { tokio::select! {
exit_status = proc.wait() => { exit_status = proc.wait() => {
log::warn!("mpv process exited with status: {}", exit_status?); log::warn!("mpv process exited with status: {}", exit_status?);
shutdown(mpv, Some(proc)).await; mpv.disconnect().await?;
} }
_ = tokio::signal::ctrl_c() => { _ = tokio::signal::ctrl_c() => {
log::info!("Received Ctrl-C, exiting"); log::info!("Received Ctrl-C, exiting");
shutdown(mpv, Some(proc)).await; mpv.disconnect().await?;
proc.kill().await?;
} }
result = server.serve(app.into_make_service()) => { result = async {
match Server::try_bind(&addr.clone()).context("Failed to bind server") {
Ok(server) => server.serve(app.into_make_service()).await.context("Failed to serve app"),
Err(err) => Err(err),
}
} => {
log::info!("API server exited"); log::info!("API server exited");
shutdown(mpv, Some(proc)).await; mpv.disconnect().await?;
proc.kill().await?;
result?; result?;
} }
} }
@ -201,12 +102,11 @@ async fn main() -> anyhow::Result<()> {
tokio::select! { tokio::select! {
_ = tokio::signal::ctrl_c() => { _ = tokio::signal::ctrl_c() => {
log::info!("Received Ctrl-C, exiting"); log::info!("Received Ctrl-C, exiting");
shutdown(mpv.clone(), None).await; mpv.disconnect().await?;
} }
result = server.serve(app.into_make_service()) => { _ = Server::bind(&addr.clone()).serve(app.into_make_service()) => {
log::info!("API server exited"); log::info!("API server exited");
shutdown(mpv.clone(), None).await; mpv.disconnect().await?;
result?;
} }
} }
} }