summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorlassulus <lass@aidsballs.de>2015-10-25 21:27:26 +0100
committerlassulus <lass@aidsballs.de>2015-10-25 21:27:26 +0100
commit43b856994ff2501562081152260d22626cea10f7 (patch)
tree102f201bd27ae705c842b2cc786b20cf8e394903
parentcc5220a4477e290f1833e609fed1e0f9f56e4a41 (diff)
parent4b22988392f940c705e7f3fddc39481635777895 (diff)
Merge remote-tracking branch 'cd/master'
-rw-r--r--default.nix6
-rw-r--r--krebs/3modules/current.nix26
-rw-r--r--krebs/3modules/default.nix2
-rw-r--r--krebs/3modules/shared/default.nix42
-rw-r--r--krebs/5pkgs/default.nix4
-rw-r--r--krebs/Zhosts/wolf10
-rw-r--r--shared/1systems/wolf.nix106
-rw-r--r--shared/3modules/default.nix5
-rw-r--r--shared/5pkgs/default.nix5
-rw-r--r--tv/1systems/wu.nix1
-rw-r--r--tv/2configs/test.nix31
-rw-r--r--tv/2configs/xserver/default.nix50
-rw-r--r--tv/2configs/xserver/xmonad/.gitignore1
-rw-r--r--tv/2configs/xserver/xmonad/Main.hs80
-rw-r--r--tv/2configs/xserver/xmonad/Util/Shutdown.hs53
15 files changed, 330 insertions, 92 deletions
diff --git a/default.nix b/default.nix
index c7846419..472d7597 100644
--- a/default.nix
+++ b/default.nix
@@ -31,9 +31,13 @@ let stockholm = {
kpath = lib.nspath "krebs";
upath = lib.nspath current-user-name;
- base-module = {
+ base-module = { config, ... }: {
imports = map (f: f "3modules") [ kpath upath ];
+ krebs.current.enable = true;
+ krebs.current.host = config.krebs.hosts.${current-host-name};
+ krebs.current.user = config.krebs.users.${current-user-name};
+
nixpkgs.config.packageOverrides = pkgs:
let
kpkgs = import (kpath "5pkgs") { inherit lib pkgs; };
diff --git a/krebs/3modules/current.nix b/krebs/3modules/current.nix
new file mode 100644
index 00000000..41941e28
--- /dev/null
+++ b/krebs/3modules/current.nix
@@ -0,0 +1,26 @@
+{ config, pkgs, lib, ... }:
+
+with lib;
+
+let
+ cfg = config.krebs.current;
+
+ out = {
+ options.krebs.current = api;
+ config = mkIf cfg.enable imp;
+ };
+
+ api = {
+ enable = mkEnableOption "krebs.current";
+ host = mkOption {
+ type = types.host;
+ };
+ user = mkOption {
+ type = types.user;
+ };
+ };
+
+ imp = {
+ };
+
+in out
diff --git a/krebs/3modules/default.nix b/krebs/3modules/default.nix
index e0c3b00f..fd9d56ed 100644
--- a/krebs/3modules/default.nix
+++ b/krebs/3modules/default.nix
@@ -8,6 +8,7 @@ let
imports = [
./bepasty-server.nix
./build.nix
+ ./current.nix
./exim-retiolum.nix
./exim-smarthost.nix
./github-hosts-sync.nix
@@ -76,6 +77,7 @@ let
imp = mkMerge [
{ krebs = import ./lass { inherit lib; }; }
{ krebs = import ./makefu { inherit lib; }; }
+ { krebs = import ./shared { inherit lib; }; }
{ krebs = import ./tv { inherit lib; }; }
{
krebs.dns.providers = {
diff --git a/krebs/3modules/shared/default.nix b/krebs/3modules/shared/default.nix
new file mode 100644
index 00000000..24dd7b78
--- /dev/null
+++ b/krebs/3modules/shared/default.nix
@@ -0,0 +1,42 @@
+{ lib, ... }:
+
+with lib;
+
+{
+ hosts = addNames {
+ wolf = {
+ #dc = "shack";
+ nets = {
+ #shack = {
+ # addrs4 = [ TODO ];
+ # aliases = ["wolf.shack"];
+ #};
+ retiolum = {
+ addrs4 = ["10.243.77.1"];
+ addrs6 = ["42:0:0:0:0:0:77:1"];
+ aliases = [
+ "wolf.retiolum"
+ ];
+ tinc.pubkey = ''
+ -----BEGIN RSA PUBLIC KEY-----
+ MIIBCgKCAQEAzpXyEATt8+ElxPq650/fkboEC9RvTWqN6UIAl/R4Zu+uDhAZ2ekb
+ HBjoSbRxu/0w2I37nwWUhEOemxGm4PXCgWrtO0jeRF4nVNYu3ZBppA3vuVALUWq7
+ apxRUEL9FdsWQlXGo4PVd20dGaDTi8M/Ggo755MStVTY0rRLluxyPq6VAa015sNg
+ 4NOFuWm0NDn4e+qrahTCTiSjbCU8rWixm0GktV40kdg0QAiFbEcRhuXF1s9/yojk
+ 7JT/nFg6LELjWUSSNZnioj5oSfVbThDRelIld9VaAKBAZZ5/zy6T2XSeDfoepytH
+ 8aw6itEuTCy1M1DTiTG+12SPPw+ubG+NqQIDAQAB
+ -----END RSA PUBLIC KEY-----
+ '';
+ };
+ };
+ ssh.privkey.path = <secrets/ssh.id_ed25519>;
+ ssh.pubkey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKYMXMWZIK0jjnZDM9INiYAKcwjXs2241vew54K8veCR";
+ };
+ };
+ users = addNames {
+ shared = {
+ mail = "spam@krebsco.de";
+ pubkey = "lol"; # TODO krebs.users.shared.pubkey should be unnecessary
+ };
+ };
+}
diff --git a/krebs/5pkgs/default.nix b/krebs/5pkgs/default.nix
index 0ec4b3de..7df7b7d3 100644
--- a/krebs/5pkgs/default.nix
+++ b/krebs/5pkgs/default.nix
@@ -54,4 +54,8 @@ subdirs // rec {
gcc -O -Wall -o "$exe" $src
strip --strip-unneeded "$exe"
'';
+
+ writeNixFromCabal = name: path: pkgs.runCommand name {} ''
+ ${pkgs.cabal2nix}/bin/cabal2nix ${path} > $out
+ '';
}
diff --git a/krebs/Zhosts/wolf b/krebs/Zhosts/wolf
new file mode 100644
index 00000000..ded8275b
--- /dev/null
+++ b/krebs/Zhosts/wolf
@@ -0,0 +1,10 @@
+Subnet = 10.243.77.1/32
+Subnet = 42:0:0:0:0:0:77:1/128
+-----BEGIN RSA PUBLIC KEY-----
+MIIBCgKCAQEAzpXyEATt8+ElxPq650/fkboEC9RvTWqN6UIAl/R4Zu+uDhAZ2ekb
+HBjoSbRxu/0w2I37nwWUhEOemxGm4PXCgWrtO0jeRF4nVNYu3ZBppA3vuVALUWq7
+apxRUEL9FdsWQlXGo4PVd20dGaDTi8M/Ggo755MStVTY0rRLluxyPq6VAa015sNg
+4NOFuWm0NDn4e+qrahTCTiSjbCU8rWixm0GktV40kdg0QAiFbEcRhuXF1s9/yojk
+7JT/nFg6LELjWUSSNZnioj5oSfVbThDRelIld9VaAKBAZZ5/zy6T2XSeDfoepytH
+8aw6itEuTCy1M1DTiTG+12SPPw+ubG+NqQIDAQAB
+-----END RSA PUBLIC KEY-----
diff --git a/shared/1systems/wolf.nix b/shared/1systems/wolf.nix
new file mode 100644
index 00000000..aeaeee28
--- /dev/null
+++ b/shared/1systems/wolf.nix
@@ -0,0 +1,106 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+{
+ imports = [
+ <nixpkgs/nixos/modules/profiles/qemu-guest.nix>
+ ];
+
+ krebs.build.host = config.krebs.hosts.wolf;
+ # TODO rename shared user to "krebs"
+ krebs.build.user = config.krebs.users.shared;
+ krebs.build.target = "wolf";
+
+ krebs.enable = true;
+ krebs.retiolum = {
+ enable = true;
+ connectTo = [
+ # TODO remove connectTo cd, this was only used for bootstrapping
+ "cd"
+ "gum"
+ "pigstarter"
+ ];
+ };
+
+ krebs.build.source = {
+ git.nixpkgs = {
+ url = https://github.com/NixOS/nixpkgs;
+ rev = "e916273209560b302ab231606babf5ce1c481f08";
+ };
+ dir.secrets = {
+ host = config.krebs.current.host;
+ path = "${getEnv "HOME"}/secrets/krebs/wolf";
+ };
+ dir.stockholm = {
+ host = config.krebs.current.host;
+ path = "${getEnv "HOME"}/stockholm";
+ };
+ };
+
+ networking.hostName = config.krebs.build.host.name;
+
+ boot.kernel.sysctl = {
+ # Enable IPv6 Privacy Extensions
+ "net.ipv6.conf.all.use_tempaddr" = 2;
+ "net.ipv6.conf.default.use_tempaddr" = 2;
+ };
+
+ boot.initrd.availableKernelModules = [
+ "ata_piix" "uhci_hcd" "ehci_pci" "virtio_pci" "virtio_blk"
+ ];
+ boot.kernelModules = [ ];
+ boot.extraModulePackages = [ ];
+
+ boot.loader.grub.enable = true;
+ boot.loader.grub.version = 2;
+ boot.loader.grub.device = "/dev/vda";
+
+ fileSystems."/" = { device = "/dev/disk/by-label/nixos"; fsType = "ext4"; };
+
+ swapDevices = [
+ { device = "/dev/disk/by-label/swap"; }
+ ];
+
+ nix.maxJobs = 1;
+ nix.trustedBinaryCaches = [
+ "https://cache.nixos.org"
+ "http://cache.nixos.org"
+ "http://hydra.nixos.org"
+ ];
+ nix.useChroot = true;
+
+ nixpkgs.config.packageOverrides = pkgs: {
+ nano = pkgs.vim;
+ };
+
+ environment.systemPackages = with pkgs; [
+ git
+ rxvt_unicode.terminfo
+ ];
+
+ time.timeZone = "Europe/Berlin";
+
+ programs.ssh.startAgent = false;
+
+ services.openssh = {
+ enable = true;
+ hostKeys = [
+ { type = "ed25519"; path = "/etc/ssh/ssh_host_ed25519_key"; }
+ ];
+ };
+ services.cron.enable = false;
+ services.nscd.enable = false;
+ services.ntp.enable = false;
+
+ users.mutableUsers = false;
+ users.extraUsers.root.openssh.authorizedKeys.keys = [
+ # TODO
+ config.krebs.users.lass.pubkey
+ config.krebs.users.makefu.pubkey
+ config.krebs.users.tv.pubkey
+ ];
+
+ # The NixOS release to be compatible with for stateful data such as databases.
+ system.stateVersion = "15.09";
+}
diff --git a/shared/3modules/default.nix b/shared/3modules/default.nix
new file mode 100644
index 00000000..7fbdb77f
--- /dev/null
+++ b/shared/3modules/default.nix
@@ -0,0 +1,5 @@
+# TODO don't require 3modules
+_:
+
+{
+}
diff --git a/shared/5pkgs/default.nix b/shared/5pkgs/default.nix
new file mode 100644
index 00000000..fdcfbb20
--- /dev/null
+++ b/shared/5pkgs/default.nix
@@ -0,0 +1,5 @@
+# TODO don't require 5pkgs
+_:
+
+{
+}
diff --git a/tv/1systems/wu.nix b/tv/1systems/wu.nix
index a5232c9e..586ad172 100644
--- a/tv/1systems/wu.nix
+++ b/tv/1systems/wu.nix
@@ -30,7 +30,6 @@ with lib;
../2configs/git.nix
../2configs/mail-client.nix
../2configs/xserver
- ../2configs/test.nix
{
environment.systemPackages = with pkgs; [
diff --git a/tv/2configs/test.nix b/tv/2configs/test.nix
deleted file mode 100644
index f5f068d6..00000000
--- a/tv/2configs/test.nix
+++ /dev/null
@@ -1,31 +0,0 @@
-{ config, lib, pkgs, ... }:
-
-with lib;
-
-let
- out = {
- environment.systemPackages = [
- su-test
- ];
- security.sudo.extraConfig = ''
- tv ALL=(test) NOPASSWD: ALL
- '';
- users.extraUsers.test = {
- shell = "${test-shell}";
- };
- };
-
- su-test = pkgs.execveBin "su-test" rec {
- filename = "/var/setuid-wrappers/sudo";
- argv = ["sudo" "-u" "test" "-i"];
- };
-
- test-shell = pkgs.execve "test-shell" rec {
- filename = "${pkgs.bash}/bin/bash";
- argv = ["sh" "--noprofile" "-l"];
- envp.ENV = pkgs.writeText "test-env" ''
- ${shell.cat "Hello, `$(j0w\nd0g!)`!\\o/\n"} >&2
- '';
- };
-
-in out
diff --git a/tv/2configs/xserver/default.nix b/tv/2configs/xserver/default.nix
index 5d337260..c5cffbb3 100644
--- a/tv/2configs/xserver/default.nix
+++ b/tv/2configs/xserver/default.nix
@@ -44,11 +44,14 @@ let
systemd.services.display-manager = mkForce {};
services.xserver.enable = true;
+
systemd.services.xmonad = {
wantedBy = [ "multi-user.target" ];
requires = [ "xserver.service" ];
+ environment = xmonad-environment;
serviceConfig = {
- ExecStart = "${xmonad}/bin/xmonad";
+ ExecStart = "${xmonad-start}/bin/xmonad";
+ ExecStop = "${xmonad-stop}/bin/xmonad-stop";
User = user.name;
WorkingDirectory = user.home;
};
@@ -69,15 +72,30 @@ let
};
};
- xmonad = let
- pkg = pkgs.haskellPackages.callPackage src {};
- src = pkgs.runCommand "xmonad-package" {} ''
- ${pkgs.cabal2nix}/bin/cabal2nix ${./xmonad} > $out
- '';
- in pkgs.writeScriptBin "xmonad" ''
- #! /bin/sh
+ xmonad-pkg = pkgs.haskellPackages.callPackage xmonad-src {};
+ xmonad-src = pkgs.writeNixFromCabal "xmonad.nix" ./xmonad;
+
+ xmonad-environment = {
+ DISPLAY = ":${toString config.services.xserver.display}";
+ XMONAD_STATE = "/tmp/xmonad.state";
+
+ # XXX JSON is close enough :)
+ XMONAD_WORKSPACES0_FILE = pkgs.writeText "xmonad.workspaces0" (toJSON [
+ "Dashboard" # we start here
+ "23"
+ "cr"
+ "ff"
+ "hack"
+ "im"
+ "mail"
+ "stockholm"
+ "za" "zj" "zs"
+ ]);
+ };
+
+ xmonad-start = pkgs.writeScriptBin "xmonad" ''
+ #! ${pkgs.bash}/bin/bash
set -efu
- export DISPLAY; DISPLAY=:${toString config.services.xserver.display}
export PATH; PATH=${makeSearchPath "bin" [
pkgs.rxvt_unicode
]}:/var/setuid-wrappers
@@ -93,7 +111,17 @@ let
settle ${pkgs.xorg.xhost}/bin/xhost +LOCAL:
settle ${pkgs.xorg.xrdb}/bin/xrdb -merge ${import ./Xresources.nix args}
settle ${pkgs.xorg.xsetroot}/bin/xsetroot -solid '#1c1c1c'
- exec ${pkg}/bin/xmonad
+ if test -e "$XMONAD_STATE"; then
+ IFS=''$'\n'
+ exec ${xmonad-pkg}/bin/xmonad --resume $(< "$XMONAD_STATE")
+ else
+ exec ${xmonad-pkg}/bin/xmonad
+ fi
+ '';
+
+ xmonad-stop = pkgs.writeScriptBin "xmonad-stop" ''
+ #! /bin/sh
+ exec ${xmonad-pkg}/bin/xmonad --shutdown
'';
xserver-environment = {
@@ -103,7 +131,7 @@ let
[ "${pkgs.xorg.libX11}/lib" "${pkgs.xorg.libXext}/lib" ]
++ concatLists (catAttrs "libPath" config.services.xserver.drivers));
};
-
+
xserver = pkgs.writeScriptBin "xserver" ''
#! /bin/sh
set -efu
diff --git a/tv/2configs/xserver/xmonad/.gitignore b/tv/2configs/xserver/xmonad/.gitignore
new file mode 100644
index 00000000..61620454
--- /dev/null
+++ b/tv/2configs/xserver/xmonad/.gitignore
@@ -0,0 +1 @@
+/shell.nix
diff --git a/tv/2configs/xserver/xmonad/Main.hs b/tv/2configs/xserver/xmonad/Main.hs
index cca2902a..186a5e22 100644
--- a/tv/2configs/xserver/xmonad/Main.hs
+++ b/tv/2configs/xserver/xmonad/Main.hs
@@ -1,9 +1,14 @@
{-# LANGUAGE DeriveDataTypeable #-} -- for XS
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE ScopedTypeVariables #-}
module Main where
+import Control.Exception
+import Text.Read (readEither)
import XMonad
+import System.Environment (getArgs, getEnv)
import XMonad.Prompt (defaultXPConfig)
import XMonad.Actions.DynamicWorkspaces ( addWorkspacePrompt, renameWorkspace
, removeEmptyWorkspace)
@@ -30,29 +35,26 @@ import XMonad.Layout.PerWorkspace (onWorkspace)
import Util.Pager
import Util.Rhombus
import Util.Debunk
+import Util.Shutdown
---data MyState = MyState deriving Typeable
-
myTerm :: String
myTerm = "urxvtc"
myRootTerm :: String
myRootTerm = "urxvtc -name root-urxvt -e su -"
--- TODO execRootTerm = exec (shlex "urxvtc -e su -")
--- [ ("XENVIRONMENT", HOME ++ "/.Xdefaults/root-urxvt") ]
-
-
myFont :: String
myFont = "-schumacher-*-*-*-*-*-*-*-*-*-*-*-iso10646-*"
main :: IO ()
-main = do
- -- TODO exec (shlex "xrdb -merge" ++ [HOME ++ "/.Xresources"])
- -- TODO exec (shlex "xsetroot -solid '#1c1c1c'")
- --spawn "xrdb -merge \"$HOME/.Xresources\""
- --spawn "xsetroot -solid '#1c1c1c'"
+main = getArgs >>= \case
+ ["--shutdown"] -> sendShutdownEvent
+ _ -> mainNoArgs
+
+mainNoArgs :: IO ()
+mainNoArgs = do
+ workspaces0 <- getWorkspaces0
xmonad
-- $ withUrgencyHookC dzenUrgencyHook { args = ["-bg", "magenta", "-fg", "magenta", "-h", "2"], duration = 500000 }
-- urgencyConfig { remindWhen = Every 1 }
@@ -63,16 +65,7 @@ main = do
{ terminal = myTerm
, modMask = mod4Mask
, keys = myKeys
- , workspaces =
- [ "Dashboard" -- we start here
- , "23"
- , "cr"
- , "ff"
- , "hack"
- , "im"
- , "mail"
- , "zalora", "zjournal", "zskype"
- ]
+ , workspaces = workspaces0
, layoutHook = smartBorders $ myLayout
-- , handleEventHook = myHandleEventHooks <+> handleTimerEvent
--, handleEventHook = handleTimerEvent
@@ -80,6 +73,7 @@ main = do
, startupHook = spawn "echo emit XMonadStartup"
, normalBorderColor = "#1c1c1c"
, focusedBorderColor = "#f000b0"
+ , handleEventHook = handleShutdownEvent
}
where
myLayout =
@@ -87,39 +81,31 @@ main = do
(FixedColumn 1 20 80 10 ||| Full)
+getWorkspaces0 :: IO [String]
+getWorkspaces0 =
+ try (getEnv "XMONAD_WORKSPACES0_FILE") >>= \case
+ Left e -> warn (displaySomeException e)
+ Right p -> try (readFile p) >>= \case
+ Left e -> warn (displaySomeException e)
+ Right x -> case readEither x of
+ Left e -> warn e
+ Right y -> return y
+ where
+ warn msg = putStrLn ("getWorkspaces0: " ++ msg) >> return []
+
+displaySomeException :: SomeException -> String
+displaySomeException = displayException
+
+
spawnTermAt :: String -> X ()
--spawnTermAt _ = floatNext True >> spawn myTerm
--spawnTermAt "ff" = floatNext True >> spawn myTerm
spawnTermAt _ = spawn myTerm
-
---jojo w = withDisplay $ \d -> liftIO $ do
--- wa <- getWindowAttributes d w
--- printToErrors (wa_width wa, wa_height wa, wa_x wa, wa_y wa)
-
- --sh <- getWMNormalHints d w
- --bw <- fmap (fi . wa_border_width) $ getWindowAttributes d w
- --return $ applySizeHints bw sh
-
-
---data WindowDetails = WindowDetails
--- { wd_name :: Maybe String
--- , wd_rect :: Rectangle
--- } deriving (Show)
-
--- urxvtc
--- -title sets {,_NET_}WM_NAME but not WM_CLASS and {,_NET_}WM_ICON_NAME res: title
--- -name sets all res:
---mySpawn cmd = do
--- p <- xfork $ executeFile "/run/current-system/sw/bin/urxvtc" False [] Nothing
--- liftIO $ printToErrors $ (cmd, p)
-
-
myKeys :: XConfig Layout -> Map (KeyMask, KeySym) (X ())
myKeys conf = Map.fromList $
- [ ((_4C , xK_Delete ), spawn "make -C $HOME/.xmonad reload")
- , ((_4 , xK_Escape ), spawn "/var/setuid-wrappers/slock")
+ [ ((_4 , xK_Escape ), spawn "/var/setuid-wrappers/slock")
, ((_4S , xK_c ), kill)
, ((_4 , xK_x ), chooseAction spawnTermAt)
@@ -273,5 +259,3 @@ wGSConfig = defaultGSConfig
allWorkspaceNames :: W.StackSet i l a sid sd -> X [i]
allWorkspaceNames ws =
return $ map W.tag (W.hidden ws) ++ [W.tag $ W.workspace $ W.current ws]
-
--- vim:set fdm=marker:
diff --git a/tv/2configs/xserver/xmonad/Util/Shutdown.hs b/tv/2configs/xserver/xmonad/Util/Shutdown.hs
new file mode 100644
index 00000000..c5a3edb8
--- /dev/null
+++ b/tv/2configs/xserver/xmonad/Util/Shutdown.hs
@@ -0,0 +1,53 @@
+{-# LANGUAGE LambdaCase #-}
+module Util.Shutdown
+ ( sendShutdownEvent
+ , handleShutdownEvent
+ , shutdown
+ )
+ where
+
+import Control.Monad
+import Data.Monoid
+import Data.Maybe (catMaybes)
+import qualified Data.Map as Map
+import System.Environment (getEnv)
+import System.Exit (exitSuccess)
+import XMonad
+import qualified XMonad.StackSet as W
+
+sendShutdownEvent :: IO ()
+sendShutdownEvent = do
+ dpy <- openDisplay ""
+ rw <- rootWindow dpy $ defaultScreen dpy
+ a <- internAtom dpy "XMONAD_SHUTDOWN" False
+ allocaXEvent $ \e -> do
+ setEventType e clientMessage
+ setClientMessageEvent e rw a 32 0 currentTime
+ sendEvent dpy rw False structureNotifyMask e
+ sync dpy False
+
+handleShutdownEvent :: Event -> X All
+handleShutdownEvent = \case
+ ClientMessageEvent { ev_message_type = mt } -> do
+ c <- (mt ==) <$> getAtom "XMONAD_SHUTDOWN"
+ when c shutdown
+ return (All c)
+ _ ->
+ return (All True)
+
+shutdown :: X ()
+shutdown = do
+ broadcastMessage ReleaseResources
+ io . flush =<< asks display
+ let wsData = show . W.mapLayout show . windowset
+ maybeShow (t, Right (PersistentExtension ext)) = Just (t, show ext)
+ maybeShow (t, Left str) = Just (t, str)
+ maybeShow _ = Nothing
+ extState =
+ return . show . catMaybes . map maybeShow . Map.toList . extensibleState
+ s <- gets (\s -> (wsData s : extState s))
+ _ <- io $ do
+ path <- getEnv "XMONAD_STATE"
+ writeFile path (concatMap (++"\n") s)
+ exitSuccess
+ return ()