summaryrefslogtreecommitdiff
path: root/custom
diff options
context:
space:
mode:
Diffstat (limited to 'custom')
-rw-r--r--custom/notify-user.hs50
-rw-r--r--custom/notify-user.nix69
-rw-r--r--custom/notify-users.nix53
-rw-r--r--custom/recv-media.sh56
-rw-r--r--custom/uucp-mediaclient.nix89
5 files changed, 248 insertions, 69 deletions
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 @@
1{-# LANGUAGE ViewPatterns, StandaloneDeriving #-}
2
3import System.FilePath.Glob (glob)
4import System.Environment (setEnv, getArgs)
5import System.Process (spawnProcess, waitForProcess)
6import System.Exit (exitWith, ExitCode(..))
7
8import Data.List (isPrefixOf, dropWhile, dropWhileEnd, init)
9import Data.Char (isSpace, toLower, toUpper)
10
11import Control.Monad (forM_, void)
12
13import qualified Libnotify as Notify
14import Data.Monoid
15
16import System.Console.GetOpt.Simple
17
18import qualified Data.Map as Map
19
20import Data.Maybe
21import Text.Read (readMaybe)
22
23deriving instance Read Notify.Urgency
24
25main :: IO ()
26main = do
27 envFiles <- glob "@userHome@/.dbus/session-bus/*"
28 forM_ envFiles $ \envFile -> do
29 sessionAddr <- unQuote . tail . snd . break (== '=') . head . filter ("DBUS_SESSION_BUS_ADDRESS=" `isPrefixOf`) . lines <$> readFile envFile
30 setEnv "DBUS_SESSION_BUS_ADDRESS" sessionAddr
31 lines <- lines <$> getContents
32 case lines of
33 ((trim -> summary):(trim . unlines -> contents)) -> do
34 (opts, _) <- flip getUsingConf [] [ (arg, "urgency", Optional, "")
35 , (arg, "app-name", Optional, "")
36 , (arg, "category", Optional, "")
37 ]
38 let
39 urgency = fromMaybe Notify.Normal $ readMaybe . caseForRead =<< Map.lookup "urgency" opts
40 appName = fromMaybe "notify-@userName@" $ Map.lookup "app-name" opts
41 category = fromMaybe "" $ Map.lookup "category" opts
42 Notify.display_ $ Notify.summary summary <> Notify.body contents <> Notify.appName appName <> Notify.urgency urgency <> Notify.category category
43 _ -> exitWith $ ExitFailure 2
44 where
45 trim = dropWhileEnd isSpace . dropWhile isSpace
46 unQuote ('\'':xs) = init xs
47 unQuote ('"':xs) = init xs
48 unQuote xs = xs
49 caseForRead [] = []
50 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 @@
1{ stdenv, writeTextFile
2, ghcWithPackages
3, user ? "gkleen"
4# , libnotify
5}:
6
7stdenv.mkDerivation {
8 name = ''notify-${user}'';
9 src = writeTextFile { name = ''notify-${user}.hs''; text = ''
10 {-# LANGUAGE ViewPatterns, StandaloneDeriving #-}
11
12 import System.FilePath.Glob (glob)
13 import System.Environment (setEnv, getArgs)
14 import System.Process (spawnProcess, waitForProcess)
15 import System.Exit (exitWith, ExitCode(..))
16
17 import Data.List (isPrefixOf, dropWhile, dropWhileEnd, init)
18 import Data.Char (isSpace, toLower, toUpper)
19
20 import Control.Monad (forM_, void)
21
22 import qualified Libnotify as Notify
23 import Data.Monoid
24
25 import System.Console.GetOpt.Simple
26
27 import qualified Data.Map as Map
28
29 import Data.Maybe
30 import Text.Read (readMaybe)
31
32 deriving instance Read Notify.Urgency
33
34 main = do
35 envFiles <- glob "/home/${user}/.dbus/session-bus/*"
36 forM_ envFiles $ \envFile -> do
37 sessionAddr <- unQuote . tail . snd . break (== '=') . head . filter ("DBUS_SESSION_BUS_ADDRESS=" `isPrefixOf`) . lines <$> readFile envFile
38 setEnv "DBUS_SESSION_BUS_ADDRESS" sessionAddr
39 lines <- lines <$> getContents
40 case lines of
41 ((trim -> summary):(trim . unlines -> contents)) -> do
42 (opts, _) <- flip getUsingConf [] [ (arg, "urgency", Optional, "")
43 , (arg, "app-name", Optional, "")
44 , (arg, "category", Optional, "")
45 ]
46 let
47 urgency = fromMaybe Notify.Normal $ readMaybe . caseForRead =<< Map.lookup "urgency" opts
48 appName = fromMaybe "notify-${user}" $ Map.lookup "app-name" opts
49 category = fromMaybe "" $ Map.lookup "category" opts
50 Notify.display_ $ Notify.summary summary <> Notify.body contents <> Notify.appName appName <> Notify.urgency urgency <> Notify.category category
51 _ -> exitWith $ ExitFailure 2
52 where
53 trim = dropWhileEnd isSpace . dropWhile isSpace
54 unQuote ('\''':xs) = init xs
55 unQuote ('"':xs) = init xs
56 unQuote xs = xs
57 caseForRead [] = []
58 caseForRead (x:xs) = toUpper x : map toLower xs
59 ''; };
60 phases = [ "buildPhase" "installPhase" ];
61 buildPhase = ''
62 ${ghcWithPackages (p: with p; [ Glob process libnotify getopt-simple containers ])}/bin/ghc -odir . -hidir . $src -o notify-${user}
63 '';
64 installPhase = ''
65 mkdir -p $out/bin
66 cp notify-${user} $out/bin
67 chmod +x $out/bin/notify-${user}
68 '';
69}
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 @@
1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.notify-users;
7
8 notify-user = userName: with pkgs; stdenv.mkDerivation {
9 name = "notify-${userName}";
10 src = ./notify-user.hs;
11
12 phases = [ "unpackPhase" "buildPhase" "installPhase" ];
13
14 unpackPhase = ''
15 cp $src notify-user.hs
16 '';
17
18 inherit userName;
19 userHome = config.users.users."${userName}".home;
20
21 buildPhase = ''
22 substituteAllInPlace notify-user.hs
23 ${ghcWithPackages (p: with p; [ Glob process libnotify getopt-simple containers ])}/bin/ghc -odir . -hidir . $src -o notify-${userName}
24 '';
25
26 installPhase = ''
27 mkdir -p $out/bin
28
29 install -m 755 -t $out/bin \
30 notify-${userName}
31 '';
32 };
33in {
34 options = {
35 services.notify-users = mkOption {
36 type = with types; listOf str;
37 default = [];
38 description = ''
39 Users to install a notify-user script for
40 '';
41 };
42 };
43
44 config = mkIf (cfg != []) {
45 security.wrappers = listToAttrs (map (user: nameValuePair "notify-${user}" {
46 owner = user;
47 setuid = true;
48 setgid = false;
49 permissions = "u+rx,g+x,o+x";
50 source = "${notify-user user}/bin/notify-${user}";
51 }) cfg);
52 };
53}
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 @@
1#!@zsh@/bin/zsh
2
3pid=$?
4
5exec 1> >(@utillinux@/bin/logger --id=$pid -t recv-media -p user.notice)
6exec 2> >(@utillinux@/bin/logger --id=$pid -t recv-media -p user.error)
7
8[[ -z "$1" || -z "$2" ]] && exit 2
9
10dir=@mediaDir@
11group=$(@coreutils@/bin/stat -c '%G' $dir)
12tmpFile="${dir}/.tmp/${1:t}"
13target="${dir}/${1:t}"
14
15if [[ -n "${3}" ]]; then
16 target="${dir}"/$(@coreutils@/bin/base64 -d <<<${3})
17fi
18
19@utillinux@/bin/logger --id=$pid -t recv-media -p user.debug <<EOF
20$(id)
21$(stat ${dir})
22$(stat ${1})
23$(echo ${2})
24EOF
25
26if [[ $(id -Gn) != *"$group"* ]]; then
27 printf "Groups are ā€˜%sā€™. Trying to switch primary group to ā€˜%sā€™..." $(id -Gn) $group
28 exec -- @wrapperDir@/sg $group "$0 $*"
29fi
30
31typeset -a failures
32failures=()
33
34(
35 if ! cp -lnv --preserve=all "$1" "${target}"; then
36 @coreutils@/bin/mkdir -pv "${tmpFile:h}" || failures+="mkdir"
37
38 @utillinux@/bin/ionice -c 3 -t @coreutils@/bin/cp -vn --preserve=all "$1" "${tmpFile}" && mv -v "${tmpFile}" "${target}" || failures+="cp"
39 fi
40 @coreutils@/bin/touch -c -m -t "$2" "${target}" || failures+="touch"
41)
42
43if @doNotify@ && [[ $#failures -gt 0 ]]; then
44 printf "%s\n%s\n%s" "${target:t}" "Failed to download" ${(j:, :)failures} | @notify@ -a recv-media -u Critical
45fi
46
47if @doNotify@; then
48 (
49 if @showTitle@; then
50 summary=$(@ffmpeg@/bin/ffmpeg -i "${target}" -f ffmetadata pipe:1 2>/dev/null | sed -r '/\[CHAPTER\]/q; /^title=/!d; s/^title=//')
51 else
52 summary=${target:t}
53 fi
54 printf "%s\n%s\n" "${summary}" "" | @notify@ -a recv-media || true
55 ) || true
56fi
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 @@
1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.uucp.media-client;
7
8 recv-media = with pkgs; stdenv.mkDerivation rec {
9 name = "recv-media";
10 src = ./recv-media.sh;
11
12 phases = [ "unpackPhase" "buildPhase" "installPhase" ];
13
14 unpackPhase = ''
15 cp $src recv-media
16 '';
17
18 inherit zsh coreutils;
19 inherit (config.security) wrapperDir;
20 inherit (cfg) mediaDir;
21 utillinux = util-linux;
22 doNotify = cfg.notify.users != [];
23 ffmpeg = if doNotify then ffmpeg-full else "/no-such-path";
24 notify = writeScript "notify.sh" ''
25 #!${zsh}/bin/zsh
26
27 ${concatMapStringsSep "\n" ({ name, ... }: "${notify-user name}/bin/notify-${name} $@") notify.users}
28 '';
29
30 buildPhase = ''
31 substituteAllInPlace recv-media
32 '';
33
34 installPhase = ''
35 mkdir -p $out/bin
36
37 install -m 755 -t $out/bin \
38 recv-media
39 '';
40 };
41
42
43in {
44 options = {
45 services.uucp.media-client = {
46 remoteNodes = mkOption {
47 type = with types; listOf str;
48 default = [];
49 description = ''
50 Servers to receive media from
51 '';
52 };
53
54 notify = {
55 users = mkOption {
56 type = with types; listOf str;
57 default = [];
58 description = ''
59 Users to notify
60 '';
61 };
62
63 extractTitle = mkOption {
64 type = types.bool;
65 default = true;
66 description = ''
67 Use ffmpeg to include the media title in notifications
68 '';
69 };
70 };
71
72 mediaDir = mkOption {
73 type = types.path;
74 default = "/var/media";
75 description = "Media directory";
76 };
77 };
78 };
79
80 config = mkIf (cfg.remoteNodes != []) {
81 imports = [ ./custom/notify-users.nix ];
82
83 services.uucp.commandPath = [ "${recv-media}/bin" ];
84 services.uucp.remoteNodes = genAttrs cfg.remoteNodes (name: { commands = ["recv-media"]; } );
85
86 services.notify-users = notify.users;
87 };
88}
89