From d49dd672463aff72bd754d657abbd11cf8a0d8e0 Mon Sep 17 00:00:00 2001 From: Gregor Kleen Date: Sat, 2 Jun 2018 17:58:57 +0200 Subject: revamp uucp-mediaclient --- custom/notify-user.hs | 50 +++++++++++++++++++++++++ custom/notify-user.nix | 69 ----------------------------------- custom/notify-users.nix | 53 +++++++++++++++++++++++++++ custom/recv-media.sh | 56 ++++++++++++++++++++++++++++ custom/uucp-mediaclient.nix | 89 +++++++++++++++++++++++++++++++++++++++++++++ hel.nix | 22 +++++------ hel/recv-media.nix | 72 ------------------------------------ 7 files changed, 259 insertions(+), 152 deletions(-) create mode 100644 custom/notify-user.hs delete mode 100644 custom/notify-user.nix create mode 100644 custom/notify-users.nix create mode 100644 custom/recv-media.sh create mode 100644 custom/uucp-mediaclient.nix delete mode 100644 hel/recv-media.nix diff --git a/custom/notify-user.hs b/custom/notify-user.hs new file mode 100644 index 00000000..f9cc2369 --- /dev/null +++ b/custom/notify-user.hs @@ -0,0 +1,50 @@ +{-# LANGUAGE ViewPatterns, StandaloneDeriving #-} + +import System.FilePath.Glob (glob) +import System.Environment (setEnv, getArgs) +import System.Process (spawnProcess, waitForProcess) +import System.Exit (exitWith, ExitCode(..)) + +import Data.List (isPrefixOf, dropWhile, dropWhileEnd, init) +import Data.Char (isSpace, toLower, toUpper) + +import Control.Monad (forM_, void) + +import qualified Libnotify as Notify +import Data.Monoid + +import System.Console.GetOpt.Simple + +import qualified Data.Map as Map + +import Data.Maybe +import Text.Read (readMaybe) + +deriving instance Read Notify.Urgency + +main :: IO () +main = do + envFiles <- glob "@userHome@/.dbus/session-bus/*" + forM_ envFiles $ \envFile -> do + sessionAddr <- unQuote . tail . snd . break (== '=') . head . filter ("DBUS_SESSION_BUS_ADDRESS=" `isPrefixOf`) . lines <$> readFile envFile + setEnv "DBUS_SESSION_BUS_ADDRESS" sessionAddr + lines <- lines <$> getContents + case lines of + ((trim -> summary):(trim . unlines -> contents)) -> do + (opts, _) <- flip getUsingConf [] [ (arg, "urgency", Optional, "") + , (arg, "app-name", Optional, "") + , (arg, "category", Optional, "") + ] + let + urgency = fromMaybe Notify.Normal $ readMaybe . caseForRead =<< Map.lookup "urgency" opts + appName = fromMaybe "notify-@userName@" $ Map.lookup "app-name" opts + category = fromMaybe "" $ Map.lookup "category" opts + Notify.display_ $ Notify.summary summary <> Notify.body contents <> Notify.appName appName <> Notify.urgency urgency <> Notify.category category + _ -> exitWith $ ExitFailure 2 + where + trim = dropWhileEnd isSpace . dropWhile isSpace + unQuote ('\'':xs) = init xs + unQuote ('"':xs) = init xs + unQuote xs = xs + caseForRead [] = [] + caseForRead (x:xs) = toUpper x : map toLower xs diff --git a/custom/notify-user.nix b/custom/notify-user.nix deleted file mode 100644 index e9e98ddc..00000000 --- a/custom/notify-user.nix +++ /dev/null @@ -1,69 +0,0 @@ -{ stdenv, writeTextFile -, ghcWithPackages -, user ? "gkleen" -# , libnotify -}: - -stdenv.mkDerivation { - name = ''notify-${user}''; - src = writeTextFile { name = ''notify-${user}.hs''; text = '' - {-# LANGUAGE ViewPatterns, StandaloneDeriving #-} - - import System.FilePath.Glob (glob) - import System.Environment (setEnv, getArgs) - import System.Process (spawnProcess, waitForProcess) - import System.Exit (exitWith, ExitCode(..)) - - import Data.List (isPrefixOf, dropWhile, dropWhileEnd, init) - import Data.Char (isSpace, toLower, toUpper) - - import Control.Monad (forM_, void) - - import qualified Libnotify as Notify - import Data.Monoid - - import System.Console.GetOpt.Simple - - import qualified Data.Map as Map - - import Data.Maybe - import Text.Read (readMaybe) - - deriving instance Read Notify.Urgency - - main = do - envFiles <- glob "/home/${user}/.dbus/session-bus/*" - forM_ envFiles $ \envFile -> do - sessionAddr <- unQuote . tail . snd . break (== '=') . head . filter ("DBUS_SESSION_BUS_ADDRESS=" `isPrefixOf`) . lines <$> readFile envFile - setEnv "DBUS_SESSION_BUS_ADDRESS" sessionAddr - lines <- lines <$> getContents - case lines of - ((trim -> summary):(trim . unlines -> contents)) -> do - (opts, _) <- flip getUsingConf [] [ (arg, "urgency", Optional, "") - , (arg, "app-name", Optional, "") - , (arg, "category", Optional, "") - ] - let - urgency = fromMaybe Notify.Normal $ readMaybe . caseForRead =<< Map.lookup "urgency" opts - appName = fromMaybe "notify-${user}" $ Map.lookup "app-name" opts - category = fromMaybe "" $ Map.lookup "category" opts - Notify.display_ $ Notify.summary summary <> Notify.body contents <> Notify.appName appName <> Notify.urgency urgency <> Notify.category category - _ -> exitWith $ ExitFailure 2 - where - trim = dropWhileEnd isSpace . dropWhile isSpace - unQuote ('\''':xs) = init xs - unQuote ('"':xs) = init xs - unQuote xs = xs - caseForRead [] = [] - caseForRead (x:xs) = toUpper x : map toLower xs - ''; }; - phases = [ "buildPhase" "installPhase" ]; - buildPhase = '' - ${ghcWithPackages (p: with p; [ Glob process libnotify getopt-simple containers ])}/bin/ghc -odir . -hidir . $src -o notify-${user} - ''; - installPhase = '' - mkdir -p $out/bin - cp notify-${user} $out/bin - chmod +x $out/bin/notify-${user} - ''; -} diff --git a/custom/notify-users.nix b/custom/notify-users.nix new file mode 100644 index 00000000..e68b0be2 --- /dev/null +++ b/custom/notify-users.nix @@ -0,0 +1,53 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.notify-users; + + notify-user = userName: with pkgs; stdenv.mkDerivation { + name = "notify-${userName}"; + src = ./notify-user.hs; + + phases = [ "unpackPhase" "buildPhase" "installPhase" ]; + + unpackPhase = '' + cp $src notify-user.hs + ''; + + inherit userName; + userHome = config.users.users."${userName}".home; + + buildPhase = '' + substituteAllInPlace notify-user.hs + ${ghcWithPackages (p: with p; [ Glob process libnotify getopt-simple containers ])}/bin/ghc -odir . -hidir . $src -o notify-${userName} + ''; + + installPhase = '' + mkdir -p $out/bin + + install -m 755 -t $out/bin \ + notify-${userName} + ''; + }; +in { + options = { + services.notify-users = mkOption { + type = with types; listOf str; + default = []; + description = '' + Users to install a notify-user script for + ''; + }; + }; + + config = mkIf (cfg != []) { + security.wrappers = listToAttrs (map (user: nameValuePair "notify-${user}" { + owner = user; + setuid = true; + setgid = false; + permissions = "u+rx,g+x,o+x"; + source = "${notify-user user}/bin/notify-${user}"; + }) cfg); + }; +} diff --git a/custom/recv-media.sh b/custom/recv-media.sh new file mode 100644 index 00000000..29e3d158 --- /dev/null +++ b/custom/recv-media.sh @@ -0,0 +1,56 @@ +#!@zsh@/bin/zsh + +pid=$? + +exec 1> >(@utillinux@/bin/logger --id=$pid -t recv-media -p user.notice) +exec 2> >(@utillinux@/bin/logger --id=$pid -t recv-media -p user.error) + +[[ -z "$1" || -z "$2" ]] && exit 2 + +dir=@mediaDir@ +group=$(@coreutils@/bin/stat -c '%G' $dir) +tmpFile="${dir}/.tmp/${1:t}" +target="${dir}/${1:t}" + +if [[ -n "${3}" ]]; then + target="${dir}"/$(@coreutils@/bin/base64 -d <<<${3}) +fi + +@utillinux@/bin/logger --id=$pid -t recv-media -p user.debug </dev/null | sed -r '/\[CHAPTER\]/q; /^title=/!d; s/^title=//') + else + summary=${target:t} + fi + printf "%s\n%s\n" "${summary}" "" | @notify@ -a recv-media || true + ) || true +fi diff --git a/custom/uucp-mediaclient.nix b/custom/uucp-mediaclient.nix new file mode 100644 index 00000000..07afeda4 --- /dev/null +++ b/custom/uucp-mediaclient.nix @@ -0,0 +1,89 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.uucp.media-client; + + recv-media = with pkgs; stdenv.mkDerivation rec { + name = "recv-media"; + src = ./recv-media.sh; + + phases = [ "unpackPhase" "buildPhase" "installPhase" ]; + + unpackPhase = '' + cp $src recv-media + ''; + + inherit zsh coreutils; + inherit (config.security) wrapperDir; + inherit (cfg) mediaDir; + utillinux = util-linux; + doNotify = cfg.notify.users != []; + ffmpeg = if doNotify then ffmpeg-full else "/no-such-path"; + notify = writeScript "notify.sh" '' + #!${zsh}/bin/zsh + + ${concatMapStringsSep "\n" ({ name, ... }: "${notify-user name}/bin/notify-${name} $@") notify.users} + ''; + + buildPhase = '' + substituteAllInPlace recv-media + ''; + + installPhase = '' + mkdir -p $out/bin + + install -m 755 -t $out/bin \ + recv-media + ''; + }; + + +in { + options = { + services.uucp.media-client = { + remoteNodes = mkOption { + type = with types; listOf str; + default = []; + description = '' + Servers to receive media from + ''; + }; + + notify = { + users = mkOption { + type = with types; listOf str; + default = []; + description = '' + Users to notify + ''; + }; + + extractTitle = mkOption { + type = types.bool; + default = true; + description = '' + Use ffmpeg to include the media title in notifications + ''; + }; + }; + + mediaDir = mkOption { + type = types.path; + default = "/var/media"; + description = "Media directory"; + }; + }; + }; + + config = mkIf (cfg.remoteNodes != []) { + imports = [ ./custom/notify-users.nix ]; + + services.uucp.commandPath = [ "${recv-media}/bin" ]; + services.uucp.remoteNodes = genAttrs cfg.remoteNodes (name: { commands = ["recv-media"]; } ); + + services.notify-users = notify.users; + }; +} + diff --git a/hel.nix b/hel.nix index 423f90bd..efc58b8f 100644 --- a/hel.nix +++ b/hel.nix @@ -15,6 +15,8 @@ ./custom/tinc/yggdrasil.nix ./custom/uucp.nix ./custom/borgbackup.nix + ./custom/uucp-mediaclient.nix + ./custom/notify-users.nix ./utils/nix/module.nix ]; @@ -214,7 +216,7 @@ "odin" = { publicKeys = ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKcDj49TqmflGTmtGBqDawxmCBWW1txj61CZ7KT0hTHK uucp@odin"]; hostnames = ["odin.asgard.yggdrasil"]; - commands = lib.mkForce ["recv-media" "notify-gkleen"]; + commands = ["notify-gkleen"]; }; "ymir" = { publicKeys = ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFH1QWdgoC03nzW5GBuCl2pqASHeIXIYtE9IInHdaKcO uucp@ymir"]; @@ -222,9 +224,16 @@ }; }; - commandPath = [ "${pkgs.callPackage ./hel/recv-media.nix {}}/bin" config.security.wrapperDir ]; + commandPath = [ "${config.security.wrapperDir}" ]; defaultCommands = lib.mkForce []; + + media-client = { + remoteNodes = [ "odin" ]; + notify.users = [ "gkleen" ]; + }; }; + + notify-users = [ "gkleen" ]; postfix = { enable = true; @@ -357,15 +366,6 @@ "mount.cifs".source = "${pkgs.cifs-utils}/bin/mount.cifs"; "thinklight".source = "${(pkgs.callPackage ./custom/thinklight.nix { thinklight = "kbd_backlight"; })}/bin/thinklight"; - "notify-gkleen" = { - group = "users"; - owner = "gkleen"; - setgid = true; - setuid = true; - permissions = "u+rx,g+x,o+x"; - source = let notify-user = pkgs.callPackage ./custom/notify-user.nix { inherit (pkgs.haskellPackages) ghcWithPackages; }; - in "${notify-user}/bin/notify-gkleen"; - }; }; polkit = { diff --git a/hel/recv-media.nix b/hel/recv-media.nix deleted file mode 100644 index 91801593..00000000 --- a/hel/recv-media.nix +++ /dev/null @@ -1,72 +0,0 @@ -{ stdenv -, coreutils -, writeScriptBin -, eject -, notifyUser ? "gkleen" -, showTitle ? true -, ffmpeg ? null -, gnused ? null -, wrapperDir ? "/run/wrappers/bin" -}: - -assert showTitle -> ffmpeg != null && gnused != null && notifyUser != null; - -writeScriptBin "recv-media" '' - #!${stdenv.shell} - - pid=$? - notify=${if notifyUser == null then "false" else "true"} - notifyUser=${if notifyUser == null then "" else notifyUser} - - PATH=${wrapperDir}:${eject}/bin:${coreutils}/bin${if showTitle then '':${ffmpeg}/bin:${gnused}/bin'' else ""} - - exec 1> >(logger --id=$pid -t recv-media -p user.notice) - exec 2> >(logger --id=$pid -t recv-media -p user.error) - - [[ -z "$1" || -z "$2" ]] && exit 2 - - dir=/var/media - group=$(stat -c '%G' $dir) - tmpFile="''${dir}/.tmp/''${1:t}" - target="''${dir}/''${1:t}" - - if [[ -n "''${3}" ]]; then - target="''${dir}"/$(base64 -d <<<''${3}) - fi - - logger --id=$pid -t recv-media -p user.debug </dev/null | sed -r '/^\[CHAPTER\]$/q; /^title=/!d; s/^title=//')'' else ''""''} - [[ -z "''${summary}" ]] && summary=$(basename "$1") - printf "%s\n%s\n" "''${summary}" "" | notify-''${notifyUser} -a recv-media || true - ) || true - fi -'' -- cgit v1.2.3