summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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
-rw-r--r--hel.nix22
-rw-r--r--hel/recv-media.nix72
7 files changed, 259 insertions, 152 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
diff --git a/hel.nix b/hel.nix
index 423f90bd..efc58b8f 100644
--- a/hel.nix
+++ b/hel.nix
@@ -15,6 +15,8 @@
15 ./custom/tinc/yggdrasil.nix 15 ./custom/tinc/yggdrasil.nix
16 ./custom/uucp.nix 16 ./custom/uucp.nix
17 ./custom/borgbackup.nix 17 ./custom/borgbackup.nix
18 ./custom/uucp-mediaclient.nix
19 ./custom/notify-users.nix
18 ./utils/nix/module.nix 20 ./utils/nix/module.nix
19 ]; 21 ];
20 22
@@ -214,7 +216,7 @@
214 "odin" = { 216 "odin" = {
215 publicKeys = ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKcDj49TqmflGTmtGBqDawxmCBWW1txj61CZ7KT0hTHK uucp@odin"]; 217 publicKeys = ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKcDj49TqmflGTmtGBqDawxmCBWW1txj61CZ7KT0hTHK uucp@odin"];
216 hostnames = ["odin.asgard.yggdrasil"]; 218 hostnames = ["odin.asgard.yggdrasil"];
217 commands = lib.mkForce ["recv-media" "notify-gkleen"]; 219 commands = ["notify-gkleen"];
218 }; 220 };
219 "ymir" = { 221 "ymir" = {
220 publicKeys = ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFH1QWdgoC03nzW5GBuCl2pqASHeIXIYtE9IInHdaKcO uucp@ymir"]; 222 publicKeys = ["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFH1QWdgoC03nzW5GBuCl2pqASHeIXIYtE9IInHdaKcO uucp@ymir"];
@@ -222,9 +224,16 @@
222 }; 224 };
223 }; 225 };
224 226
225 commandPath = [ "${pkgs.callPackage ./hel/recv-media.nix {}}/bin" config.security.wrapperDir ]; 227 commandPath = [ "${config.security.wrapperDir}" ];
226 defaultCommands = lib.mkForce []; 228 defaultCommands = lib.mkForce [];
229
230 media-client = {
231 remoteNodes = [ "odin" ];
232 notify.users = [ "gkleen" ];
233 };
227 }; 234 };
235
236 notify-users = [ "gkleen" ];
228 237
229 postfix = { 238 postfix = {
230 enable = true; 239 enable = true;
@@ -357,15 +366,6 @@
357 "mount.cifs".source = "${pkgs.cifs-utils}/bin/mount.cifs"; 366 "mount.cifs".source = "${pkgs.cifs-utils}/bin/mount.cifs";
358 "thinklight".source = 367 "thinklight".source =
359 "${(pkgs.callPackage ./custom/thinklight.nix { thinklight = "kbd_backlight"; })}/bin/thinklight"; 368 "${(pkgs.callPackage ./custom/thinklight.nix { thinklight = "kbd_backlight"; })}/bin/thinklight";
360 "notify-gkleen" = {
361 group = "users";
362 owner = "gkleen";
363 setgid = true;
364 setuid = true;
365 permissions = "u+rx,g+x,o+x";
366 source = let notify-user = pkgs.callPackage ./custom/notify-user.nix { inherit (pkgs.haskellPackages) ghcWithPackages; };
367 in "${notify-user}/bin/notify-gkleen";
368 };
369 }; 369 };
370 370
371 polkit = { 371 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 @@
1{ stdenv
2, coreutils
3, writeScriptBin
4, eject
5, notifyUser ? "gkleen"
6, showTitle ? true
7, ffmpeg ? null
8, gnused ? null
9, wrapperDir ? "/run/wrappers/bin"
10}:
11
12assert showTitle -> ffmpeg != null && gnused != null && notifyUser != null;
13
14writeScriptBin "recv-media" ''
15 #!${stdenv.shell}
16
17 pid=$?
18 notify=${if notifyUser == null then "false" else "true"}
19 notifyUser=${if notifyUser == null then "" else notifyUser}
20
21 PATH=${wrapperDir}:${eject}/bin:${coreutils}/bin${if showTitle then '':${ffmpeg}/bin:${gnused}/bin'' else ""}
22
23 exec 1> >(logger --id=$pid -t recv-media -p user.notice)
24 exec 2> >(logger --id=$pid -t recv-media -p user.error)
25
26 [[ -z "$1" || -z "$2" ]] && exit 2
27
28 dir=/var/media
29 group=$(stat -c '%G' $dir)
30 tmpFile="''${dir}/.tmp/''${1:t}"
31 target="''${dir}/''${1:t}"
32
33 if [[ -n "''${3}" ]]; then
34 target="''${dir}"/$(base64 -d <<<''${3})
35 fi
36
37 logger --id=$pid -t recv-media -p user.debug <<EOF
38 $(id)
39 $(stat ''${dir})
40 $(stat ''${1})
41 $(echo ''${2})
42 EOF
43
44 if [[ $(id -Gn) != *"$group"* ]]; then
45 printf "Groups are ‘%s’. Trying to switch primary group to ‘%s’..." $(id -Gn) $group
46 exec -- sg $group "$0 $*"
47 fi
48
49 typeset -a failures
50 failures=()
51
52 (
53 if ! cp -lnv --preserve=all "$1" "''${target}"; then
54 mkdir -pv "''${tmpFile:h}" || failures+="mkdir"
55
56 ionice -c 3 -t cp -vn --preserve=all "$1" "''${tmpFile}" && mv -v "''${tmpFile}" "''${target}" || failures+="cp"
57 fi
58 touch -c -m -t "$2" "''${target}" || failures+="touch"
59 )
60
61 if $notify && [[ $#failures -gt 0 ]]; then
62 printf "%s\n%s\n%s" $(basename "$1") "Failed to download" ''${(j:, :)failures} | notify-''${notifyUser} -a recv-media -u Critical
63 fi
64
65 if $notify; then
66 (
67 summary=${if showTitle then ''$(ffmpeg -i "''${target}" -f ffmetadata pipe:1 2>/dev/null | sed -r '/^\[CHAPTER\]$/q; /^title=/!d; s/^title=//')'' else ''""''}
68 [[ -z "''${summary}" ]] && summary=$(basename "$1")
69 printf "%s\n%s\n" "''${summary}" "" | notify-''${notifyUser} -a recv-media || true
70 ) || true
71 fi
72''