diff options
author | Frederik Rietdijk <fridh@fridh.nl> | 2019-04-29 13:46:20 +0200 |
---|---|---|
committer | Frederik Rietdijk <fridh@fridh.nl> | 2019-04-29 13:46:20 +0200 |
commit | 2f936f85d8e1b06de4998fa5ed4b112fd96559b1 (patch) | |
tree | a17510968c4241bb8c6566433d4cf8ddabfa83b3 /nixos | |
parent | ee5f4636b09abe2dda532d2f79789f7bb3ae0292 (diff) | |
parent | 6dabc28cd0713f93d97fd86362d176f660f145af (diff) | |
download | nixpkgs-2f936f85d8e1b06de4998fa5ed4b112fd96559b1.tar nixpkgs-2f936f85d8e1b06de4998fa5ed4b112fd96559b1.tar.gz nixpkgs-2f936f85d8e1b06de4998fa5ed4b112fd96559b1.tar.bz2 nixpkgs-2f936f85d8e1b06de4998fa5ed4b112fd96559b1.tar.lz nixpkgs-2f936f85d8e1b06de4998fa5ed4b112fd96559b1.tar.xz nixpkgs-2f936f85d8e1b06de4998fa5ed4b112fd96559b1.tar.zst nixpkgs-2f936f85d8e1b06de4998fa5ed4b112fd96559b1.zip |
Merge master into staging-next
Diffstat (limited to 'nixos')
25 files changed, 721 insertions, 245 deletions
diff --git a/nixos/doc/manual/configuration/x-windows.xml b/nixos/doc/manual/configuration/x-windows.xml index 3bcb288b5eb..798d1fbdfd8 100644 --- a/nixos/doc/manual/configuration/x-windows.xml +++ b/nixos/doc/manual/configuration/x-windows.xml @@ -67,6 +67,32 @@ <xref linkend="opt-hardware.opengl.driSupport32Bit"/> = true; </programlisting> </para> + <simplesect xml:id="sec-x11-auto-login"> + <title>Auto-login</title> + <para> + The x11 login screen can be skipped entirely, automatically logging you into + your window manager and desktop environment when you boot your computer. + </para> + <para> + This is especially helpful if you have disk encryption enabled. Since you + already have to provide a password to decrypt your disk, entering a second + password to login can be redundant. + </para> + <para> + To enable auto-login, you need to define your default window manager and + desktop environment. If you wanted no desktop environment and i3 as your your + window manager, you'd define: +<programlisting> +<xref linkend="opt-services.xserver.desktopManager.default"/> = "none"; +<xref linkend="opt-services.xserver.windowManager.default"/> = "i3"; +</programlisting> + And, finally, to enable auto-login for a user <literal>johndoe</literal>: +<programlisting> +<xref linkend="opt-services.xserver.displayManager.auto.enable"/> = true; +<xref linkend="opt-services.xserver.displayManager.auto.user"/> = "johndoe"; +</programlisting> + </para> + </simplesect> <simplesect xml:id="sec-x11-graphics-cards-nvidia"> <title>Proprietary NVIDIA drivers</title> <para> diff --git a/nixos/doc/manual/release-notes/rl-1909.xml b/nixos/doc/manual/release-notes/rl-1909.xml index 2dc2dc41ff2..b16297da7ce 100644 --- a/nixos/doc/manual/release-notes/rl-1909.xml +++ b/nixos/doc/manual/release-notes/rl-1909.xml @@ -83,6 +83,14 @@ The same applies to ModemManager where modem-manager.service is now called ModemManager.service again. </para> </listitem> + <listitem> + <para> + The <option>services.nzbget.configFile</option> and <option>services.nzbget.openFirewall</option> + options were removed as they are managed internally by the nzbget. The + <option>services.nzbget.dataDir</option> option hadn't actually been used by + the module for some time and so was removed as cleanup. + </para> + </listitem> </itemizedlist> </section> diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index fca4a20eee6..56c44a43c6e 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -414,6 +414,7 @@ ./services/misc/ihaskell.nix ./services/misc/irkerd.nix ./services/misc/jackett.nix + ./services/misc/jellyfin.nix ./services/misc/logkeys.nix ./services/misc/leaps.nix ./services/misc/lidarr.nix diff --git a/nixos/modules/programs/browserpass.nix b/nixos/modules/programs/browserpass.nix index 47b9d1ccb1f..e1456d3c184 100644 --- a/nixos/modules/programs/browserpass.nix +++ b/nixos/modules/programs/browserpass.nix @@ -26,16 +26,6 @@ with lib; # brave "opt/brave/native-messaging-hosts/${appId}".source = source "hosts/chromium"; "opt/brave/policies/managed/${appId}".source = source "policies/chromium"; - } - # As with the v2 backwards compatibility in the pkgs.browserpass - # declaration, this part can be removed once the browser extension - # auto-updates to v3 (planned 2019-04-13, see - # https://github.com/browserpass/browserpass-native/issues/31) - // { - "chromium/native-messaging-hosts/com.dannyvankooten.browserpass.json".source = "${pkgs.browserpass}/etc/chrome-host.json"; - "chromium/policies/managed/com.dannyvankooten.browserpass.json".source = "${pkgs.browserpass}/etc/chrome-policy.json"; - "opt/chrome/native-messaging-hosts/com.dannyvankooten.browserpass.json".source = "${pkgs.browserpass}/etc/chrome-host.json"; - "opt/chrome/policies/managed/com.dannyvankooten.browserpass.json".source = "${pkgs.browserpass}/etc/chrome-policy.json"; }; nixpkgs.config.firefox.enableBrowserpass = true; }; diff --git a/nixos/modules/programs/sway.nix b/nixos/modules/programs/sway.nix index 457faaa3c10..b4f03151cdc 100644 --- a/nixos/modules/programs/sway.nix +++ b/nixos/modules/programs/sway.nix @@ -78,9 +78,9 @@ in { environment = { systemPackages = [ swayJoined ] ++ cfg.extraPackages; etc = { - "sway/config".source = "${swayPackage}/etc/sway/config"; - #"sway/security.d".source = "${swayPackage}/etc/sway/security.d/"; - #"sway/config.d".source = "${swayPackage}/etc/sway/config.d/"; + "sway/config".source = mkOptionDefault "${swayPackage}/etc/sway/config"; + #"sway/security.d".source = mkOptionDefault "${swayPackage}/etc/sway/security.d/"; + #"sway/config.d".source = mkOptionDefault "${swayPackage}/etc/sway/config.d/"; }; }; security.pam.services.swaylock = {}; diff --git a/nixos/modules/rename.nix b/nixos/modules/rename.nix index f6c112d9cfa..70807ccf7cd 100644 --- a/nixos/modules/rename.nix +++ b/nixos/modules/rename.nix @@ -45,6 +45,9 @@ with lib; (mkRemovedOptionModule [ "services" "neo4j" "port" ] "Use services.neo4j.http.listenAddress instead.") (mkRemovedOptionModule [ "services" "neo4j" "boltPort" ] "Use services.neo4j.bolt.listenAddress instead.") (mkRemovedOptionModule [ "services" "neo4j" "httpsPort" ] "Use services.neo4j.https.listenAddress instead.") + (mkRemovedOptionModule [ "services" "misc" "nzbget" "configFile" ] "The configuration of nzbget is now managed by users through the web interface.") + (mkRemovedOptionModule [ "services" "misc" "nzbget" "dataDir" ] "The data directory for nzbget is now /var/lib/nzbget.") + (mkRemovedOptionModule [ "services" "misc" "nzbget" "openFirewall" ] "The port used by nzbget is managed through the web interface so you should adjust your firewall rules accordingly.") (mkRemovedOptionModule [ "services" "prometheus" "alertmanager" "user" ] "The alertmanager service is now using systemd's DynamicUser mechanism which obviates a user setting.") (mkRemovedOptionModule [ "services" "prometheus" "alertmanager" "group" ] "The alertmanager service is now using systemd's DynamicUser mechanism which obviates a group setting.") (mkRenamedOptionModule [ "services" "tor" "relay" "portSpec" ] [ "services" "tor" "relay" "port" ]) diff --git a/nixos/modules/services/logging/journalwatch.nix b/nixos/modules/services/logging/journalwatch.nix index d0824df38ae..576c646c0f5 100644 --- a/nixos/modules/services/logging/journalwatch.nix +++ b/nixos/modules/services/logging/journalwatch.nix @@ -4,6 +4,8 @@ with lib; let cfg = config.services.journalwatch; user = "journalwatch"; + # for journal access + group = "systemd-journal"; dataDir = "/var/lib/${user}"; journalwatchConfig = pkgs.writeText "config" ('' @@ -31,6 +33,17 @@ let '') filterBlocks); + # can't use joinSymlinks directly, because when we point $XDG_CONFIG_HOME + # to the /nix/store path, we still need the subdirectory "journalwatch" inside that + # to match journalwatch's expectations + journalwatchConfigDir = pkgs.runCommand "journalwatch-config" + { preferLocalBuild = true; allowSubstitutes = false; } + '' + mkdir -p $out/journalwatch + ln -sf ${journalwatchConfig} $out/journalwatch/config + ln -sf ${journalwatchPatterns} $out/journalwatch/patterns + ''; + in { options = { @@ -199,33 +212,38 @@ in { users.users.${user} = { isSystemUser = true; - createHome = true; home = dataDir; - # for journal access - group = "systemd-journal"; + group = group; }; + systemd.tmpfiles.rules = [ + # present since NixOS 19.09: remove old stateful symlink join directory, + # which has been replaced with the journalwatchConfigDir store path + "R ${dataDir}/config" + ]; + systemd.services.journalwatch = { + environment = { + # journalwatch stores the last processed timpestamp here + # the share subdirectory is historic now that config home lives in /nix/store, + # but moving this in a backwards-compatible way is much more work than what's justified + # for cleaning that up. XDG_DATA_HOME = "${dataDir}/share"; - XDG_CONFIG_HOME = "${dataDir}/config"; + XDG_CONFIG_HOME = journalwatchConfigDir; }; serviceConfig = { User = user; + Group = group; Type = "oneshot"; - PermissionsStartOnly = true; + # requires a relative directory name to create beneath /var/lib + StateDirectory = user; + StateDirectoryMode = 0750; ExecStart = "${pkgs.python3Packages.journalwatch}/bin/journalwatch mail"; # lowest CPU and IO priority, but both still in best-effort class to prevent starvation Nice=19; IOSchedulingPriority=7; }; - preStart = '' - chown -R ${user}:systemd-journal ${dataDir} - chmod -R u+rwX,go-w ${dataDir} - mkdir -p ${dataDir}/config/journalwatch - ln -sf ${journalwatchConfig} ${dataDir}/config/journalwatch/config - ln -sf ${journalwatchPatterns} ${dataDir}/config/journalwatch/patterns - ''; }; systemd.timers.journalwatch = { diff --git a/nixos/modules/services/misc/gitea.nix b/nixos/modules/services/misc/gitea.nix index be4d3871978..6fd4183bd6b 100644 --- a/nixos/modules/services/misc/gitea.nix +++ b/nixos/modules/services/misc/gitea.nix @@ -8,6 +8,7 @@ let pg = config.services.postgresql; useMysql = cfg.database.type == "mysql"; usePostgresql = cfg.database.type == "postgres"; + useSqlite = cfg.database.type == "sqlite3"; configFile = pkgs.writeText "app.ini" '' APP_NAME = ${cfg.appName} RUN_USER = ${cfg.user} @@ -15,11 +16,15 @@ let [database] DB_TYPE = ${cfg.database.type} - HOST = ${if cfg.database.socket != null then cfg.database.socket else cfg.database.host + ":" + toString cfg.database.port} - NAME = ${cfg.database.name} - USER = ${cfg.database.user} - PASSWD = #dbpass# - PATH = ${cfg.database.path} + ${optionalString (usePostgresql || useMysql) '' + HOST = ${if cfg.database.socket != null then cfg.database.socket else cfg.database.host + ":" + toString cfg.database.port} + NAME = ${cfg.database.name} + USER = ${cfg.database.user} + PASSWD = #dbpass# + ''} + ${optionalString useSqlite '' + PATH = ${cfg.database.path} + ''} ${optionalString usePostgresql '' SSL_MODE = disable ''} diff --git a/nixos/modules/services/misc/jellyfin.nix b/nixos/modules/services/misc/jellyfin.nix new file mode 100644 index 00000000000..7f38dd0ff23 --- /dev/null +++ b/nixos/modules/services/misc/jellyfin.nix @@ -0,0 +1,60 @@ +{ config, pkgs, lib, ... }: + +with lib; + +let + cfg = config.services.jellyfin; +in +{ + options = { + services.jellyfin = { + enable = mkEnableOption "Jellyfin Media Server"; + + user = mkOption { + type = types.str; + default = "jellyfin"; + description = "User account under which Jellyfin runs."; + }; + + group = mkOption { + type = types.str; + default = "jellyfin"; + description = "Group under which jellyfin runs."; + }; + }; + }; + + config = mkIf cfg.enable { + systemd.services.jellyfin = { + description = "Jellyfin Media Server"; + after = [ "network.target" ]; + wantedBy = [ "multi-user.target" ]; + + serviceConfig = rec { + User = cfg.user; + Group = cfg.group; + StateDirectory = "jellyfin"; + CacheDirectory = "jellyfin"; + ExecStart = "${pkgs.jellyfin}/bin/jellyfin --datadir '/var/lib/${StateDirectory}' --cachedir '/var/cache/${CacheDirectory}'"; + Restart = "on-failure"; + }; + }; + + users.users = mkIf (cfg.user == "jellyfin") { + jellyfin.group = cfg.group; + }; + + users.groups = mkIf (cfg.group == "jellyfin") { + jellyfin = {}; + }; + + assertions = [ + { + assertion = !config.services.emby.enable; + message = "Emby and Jellyfin are incompatible, you cannot enable both"; + } + ]; + }; + + meta.maintainers = with lib.maintainers; [ minijackson ]; +} diff --git a/nixos/modules/services/misc/nzbget.nix b/nixos/modules/services/misc/nzbget.nix index 6ab98751c57..eb7b4c05d82 100644 --- a/nixos/modules/services/misc/nzbget.nix +++ b/nixos/modules/services/misc/nzbget.nix @@ -4,32 +4,34 @@ with lib; let cfg = config.services.nzbget; - dataDir = builtins.dirOf cfg.configFile; -in { - options = { - services.nzbget = { - enable = mkEnableOption "NZBGet"; + pkg = pkgs.nzbget; + stateDir = "/var/lib/nzbget"; + configFile = "${stateDir}/nzbget.conf"; + configOpts = concatStringsSep " " (mapAttrsToList (name: value: "-o ${name}=${value}") nixosOpts); - package = mkOption { - type = types.package; - default = pkgs.nzbget; - defaultText = "pkgs.nzbget"; - description = "The NZBGet package to use"; - }; + nixosOpts = { + # allows nzbget to run as a "simple" service + OutputMode = "loggable"; + # use journald for logging + WriteLog = "none"; + ErrorTarget = "screen"; + WarningTarget = "screen"; + InfoTarget = "screen"; + DetailTarget = "screen"; + # required paths + ConfigTemplate = "${pkg}/share/nzbget/nzbget.conf"; + WebDir = "${pkg}/share/nzbget/webui"; + # nixos handles package updates + UpdateCheck = "none"; + }; - dataDir = mkOption { - type = types.str; - default = "/var/lib/nzbget"; - description = "The directory where NZBGet stores its configuration files."; - }; +in +{ + # interface - openFirewall = mkOption { - type = types.bool; - default = false; - description = '' - Open ports in the firewall for the NZBGet web interface - ''; - }; + options = { + services.nzbget = { + enable = mkEnableOption "NZBGet"; user = mkOption { type = types.str; @@ -42,15 +44,11 @@ in { default = "nzbget"; description = "Group under which NZBGet runs"; }; - - configFile = mkOption { - type = types.str; - default = "/var/lib/nzbget/nzbget.conf"; - description = "Path for NZBGet's config file. (If this doesn't exist, the default config template is copied here.)"; - }; }; }; + # implementation + config = mkIf cfg.enable { systemd.services.nzbget = { description = "NZBGet Daemon"; @@ -61,50 +59,26 @@ in { p7zip ]; preStart = '' - cfgtemplate=${cfg.package}/share/nzbget/nzbget.conf - if [ ! -f ${cfg.configFile} ]; then - echo "${cfg.configFile} not found. Copying default config $cfgtemplate to ${cfg.configFile}" - install -m 0700 $cfgtemplate ${cfg.configFile} - echo "Setting temporary \$MAINDIR variable in default config required in order to allow nzbget to complete initial start" - echo "Remember to change this to a proper value once NZBGet startup has been completed" - sed -i -e 's/MainDir=.*/MainDir=\/tmp/g' ${cfg.configFile} + if [ ! -f ${configFile} ]; then + ${pkgs.coreutils}/bin/install -m 0700 ${pkg}/share/nzbget/nzbget.conf ${configFile} fi ''; - script = '' - args="--daemon --configfile ${cfg.configFile}" - # The script in preStart (above) copies nzbget's config template to datadir on first run, containing paths that point to the nzbget derivation installed at the time. - # These paths break when nzbget is upgraded & the original derivation is garbage collected. If such broken paths are found in the config file, override them to point to - # the currently installed nzbget derivation. - cfgfallback () { - local hit=`grep -Po "(?<=^$1=).*+" "${cfg.configFile}" | sed 's/[ \t]*$//'` # Strip trailing whitespace - ( test $hit && test -e $hit ) || { - echo "In ${cfg.configFile}, valid $1 not found; falling back to $1=$2" - args+=" -o $1=$2" - } - } - cfgfallback ConfigTemplate ${cfg.package}/share/nzbget/nzbget.conf - cfgfallback WebDir ${cfg.package}/share/nzbget/webui - ${cfg.package}/bin/nzbget $args - ''; - serviceConfig = { - StateDirectory = dataDir; - StateDirectoryMode = "0700"; - Type = "forking"; + StateDirectory = "nzbget"; + StateDirectoryMode = "0750"; User = cfg.user; Group = cfg.group; - PermissionsStartOnly = "true"; + UMask = "0002"; Restart = "on-failure"; + ExecStart = "${pkg}/bin/nzbget --server --configfile ${stateDir}/nzbget.conf ${configOpts}"; + ExecStop = "${pkg}/bin/nzbget --quit"; }; }; - networking.firewall = mkIf cfg.openFirewall { - allowedTCPPorts = [ 8989 ]; - }; - users.users = mkIf (cfg.user == "nzbget") { nzbget = { + home = stateDir; group = cfg.group; uid = config.ids.uids.nzbget; }; diff --git a/nixos/modules/services/monitoring/grafana.nix b/nixos/modules/services/monitoring/grafana.nix index 85879cfe0b3..5d3f2e6ac28 100644 --- a/nixos/modules/services/monitoring/grafana.nix +++ b/nixos/modules/services/monitoring/grafana.nix @@ -177,7 +177,7 @@ let folder = mkOption { type = types.str; default = ""; - description = "Add dashboards to the speciied folder"; + description = "Add dashboards to the specified folder"; }; type = mkOption { type = types.str; diff --git a/nixos/modules/services/networking/ejabberd.nix b/nixos/modules/services/networking/ejabberd.nix index ef5e2cee6f2..6a38f85c48a 100644 --- a/nixos/modules/services/networking/ejabberd.nix +++ b/nixos/modules/services/networking/ejabberd.nix @@ -11,7 +11,7 @@ let ${cfg.ctlConfig} ''; - ectl = ''${cfg.package}/bin/ejabberdctl ${if cfg.configFile == null then "" else "--config ${cfg.configFile}"} --ctl-config "${ctlcfg}" --spool "${cfg.spoolDir}" --logs "${cfg.logsDir}"''; + ectl = ''${cfg.package}/bin/ejabberdctl ${optionalString (cfg.configFile != null) "--config ${cfg.configFile}"} --ctl-config "${ctlcfg}" --spool "${cfg.spoolDir}" --logs "${cfg.logsDir}"''; dumps = lib.escapeShellArgs cfg.loadDumps; @@ -111,28 +111,17 @@ in { description = "ejabberd server"; wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; - path = [ pkgs.findutils pkgs.coreutils pkgs.runit ] ++ lib.optional cfg.imagemagick pkgs.imagemagick; + path = [ pkgs.findutils pkgs.coreutils ] ++ lib.optional cfg.imagemagick pkgs.imagemagick; serviceConfig = { - ExecStart = ''${ectl} foreground''; - # FIXME: runit is used for `chpst` -- can we get rid of this? - ExecStop = ''${pkgs.runit}/bin/chpst -u "${cfg.user}:${cfg.group}" ${ectl} stop''; - ExecReload = ''${pkgs.runit}/bin/chpst -u "${cfg.user}:${cfg.group}" ${ectl} reload_config''; User = cfg.user; Group = cfg.group; - PermissionsStartOnly = true; + ExecStart = "${ectl} foreground"; + ExecStop = "${ectl} stop"; + ExecReload = "${ectl} reload_config"; }; preStart = '' - mkdir -p -m750 "${cfg.logsDir}" - chown "${cfg.user}:${cfg.group}" "${cfg.logsDir}" - - mkdir -p -m750 "/var/lock/ejabberdctl" - chown "${cfg.user}:${cfg.group}" "/var/lock/ejabberdctl" - - mkdir -p -m750 "${cfg.spoolDir}" - chown -R "${cfg.user}:${cfg.group}" "${cfg.spoolDir}" - if [ -z "$(ls -A '${cfg.spoolDir}')" ]; then touch "${cfg.spoolDir}/.firstRun" fi @@ -149,13 +138,18 @@ in { for src in ${dumps}; do find "$src" -type f | while read dump; do echo "Loading configuration dump at $dump" - chpst -u "${cfg.user}:${cfg.group}" ${ectl} load "$dump" + ${ectl} load "$dump" done done fi ''; }; + systemd.tmpfiles.rules = [ + "d '${cfg.logsDir}' 0750 ${cfg.user} ${cfg.group} -" + "d '${cfg.spoolDir}' 0700 ${cfg.user} ${cfg.group} -" + ]; + security.pam.services.ejabberd = {}; }; diff --git a/nixos/modules/services/networking/zeronet.nix b/nixos/modules/services/networking/zeronet.nix index 8b60799891c..611a51c74ce 100644 --- a/nixos/modules/services/networking/zeronet.nix +++ b/nixos/modules/services/networking/zeronet.nix @@ -5,13 +5,15 @@ let zConfFile = pkgs.writeTextFile { name = "zeronet.conf"; - + text = '' [global] data_dir = ${cfg.dataDir} log_dir = ${cfg.logDir} '' + lib.optionalString (cfg.port != null) '' ui_port = ${toString cfg.port} + '' + lib.optionalString (cfg.fileserverPort != null) '' + fileserver_port = ${toString cfg.fileserverPort} '' + lib.optionalString (cfg.torAlways) '' tor = always '' + cfg.extraConfig; @@ -41,6 +43,15 @@ in with lib; { description = "Optional zeronet web UI port."; }; + fileserverPort = mkOption { + # Not optional: when absent zeronet tries to write one to the + # read-only config file and crashes + type = types.int; + default = 12261; + example = 12261; + description = "Zeronet fileserver port."; + }; + tor = mkOption { type = types.bool; default = false; diff --git a/nixos/modules/services/security/sks.nix b/nixos/modules/services/security/sks.nix index 8136a5c763a..1b7a2ad1398 100644 --- a/nixos/modules/services/security/sks.nix +++ b/nixos/modules/services/security/sks.nix @@ -116,20 +116,22 @@ in { ${lib.optionalString (cfg.webroot != null) "ln -sfT \"${cfg.webroot}\" web"} mkdir -p dump - # Check that both database configs are symlinks before overwriting them - if [ -e KDB/DB_CONFIG ] && [ ! -L KBD/DB_CONFIG ]; then - echo "KDB/DB_CONFIG exists but is not a symlink." >&2 - exit 1 - fi - if [ -e PTree/DB_CONFIG ] && [ ! -L PTree/DB_CONFIG ]; then - echo "PTree/DB_CONFIG exists but is not a symlink." >&2 - exit 1 - fi - ln -sf ${dbConfig} KDB/DB_CONFIG - ln -sf ${dbConfig} PTree/DB_CONFIG ${sksPkg}/bin/sks build dump/*.gpg -n 10 -cache 100 || true #*/ ${sksPkg}/bin/sks cleandb || true ${sksPkg}/bin/sks pbuild -cache 20 -ptree_cache 70 || true + # Check that both database configs are symlinks before overwriting them + # TODO: The initial build will be without DB_CONFIG, but this will + # hopefully not cause any significant problems. It might be better to + # create both directories manually but we have to check that this does + # not affect the initial build of the DB. + for CONFIG_FILE in KDB/DB_CONFIG PTree/DB_CONFIG; do + if [ -e $CONFIG_FILE ] && [ ! -L $CONFIG_FILE ]; then + echo "$CONFIG_FILE exists but is not a symlink." >&2 + echo "Please remove $PWD/$CONFIG_FILE manually to continue." >&2 + exit 1 + fi + ln -sf ${dbConfig} $CONFIG_FILE + done ''; serviceConfig = { WorkingDirectory = "~"; diff --git a/nixos/modules/services/system/earlyoom.nix b/nixos/modules/services/system/earlyoom.nix index daa46838bfa..39d1bf274bd 100644 --- a/nixos/modules/services/system/earlyoom.nix +++ b/nixos/modules/services/system/earlyoom.nix @@ -63,6 +63,17 @@ in Enable debugging messages. ''; }; + + notificationsCommand = mkOption { + type = types.nullOr types.str; + default = null; + example = "sudo -u example_user DISPLAY=:0 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus notify-send"; + description = '' + Command used to send notifications. + + See <link xlink:href="https://github.com/rfjakob/earlyoom#notifications">README</link> for details. + ''; + }; }; }; @@ -88,7 +99,9 @@ in -s ${toString ecfg.freeSwapThreshold} \ ${optionalString ecfg.useKernelOOMKiller "-k"} \ ${optionalString ecfg.ignoreOOMScoreAdjust "-i"} \ - ${optionalString ecfg.enableDebugInfo "-d"} + ${optionalString ecfg.enableDebugInfo "-d"} \ + ${optionalString (ecfg.notificationsCommand != null) + "-N ${escapeShellArg ecfg.notificationsCommand}"} ''; }; }; diff --git a/nixos/modules/services/web-apps/documize.nix b/nixos/modules/services/web-apps/documize.nix index 206617b0e5a..37359869cb6 100644 --- a/nixos/modules/services/web-apps/documize.nix +++ b/nixos/modules/services/web-apps/documize.nix @@ -3,65 +3,136 @@ with lib; let - cfg = config.services.documize; -in + mkParams = optional: concatMapStrings (name: let + predicate = optional -> cfg.${name} != null; + template = " -${name} '${toString cfg.${name}}'"; + in optionalString predicate template); - { - options.services.documize = { - enable = mkEnableOption "Documize Wiki"; +in { + options.services.documize = { + enable = mkEnableOption "Documize Wiki"; - offline = mkEnableOption "Documize offline mode"; + package = mkOption { + type = types.package; + default = pkgs.documize-community; + description = '' + Which package to use for documize. + ''; + }; - package = mkOption { - default = pkgs.documize-community; - type = types.package; - description = '' - Which package to use for documize. - ''; - }; + salt = mkOption { + type = types.nullOr types.str; + default = null; + example = "3edIYV6c8B28b19fh"; + description = '' + The salt string used to encode JWT tokens, if not set a random value will be generated. + ''; + }; - db = mkOption { - type = types.str; - example = "host=localhost port=5432 sslmode=disable user=admin password=secret dbname=documize"; - description = '' - The DB connection string to use for the database. - ''; - }; + cert = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + The <filename>cert.pem</filename> file used for https. + ''; + }; - dbtype = mkOption { - type = types.enum [ "postgresql" "percona" "mariadb" "mysql" ]; - description = '' - Which database to use for storage. - ''; - }; + key = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + The <filename>key.pem</filename> file used for https. + ''; + }; - port = mkOption { - type = types.port; - example = 3000; - description = '' - Which TCP port to serve. - ''; - }; + port = mkOption { + type = types.port; + default = 5001; + description = '' + The http/https port number. + ''; + }; + + forcesslport = mkOption { + type = types.nullOr types.port; + default = null; + description = '' + Redirect given http port number to TLS. + ''; + }; + + offline = mkOption { + type = types.bool; + default = false; + description = '' + Set <literal>true</literal> for offline mode. + ''; + apply = v: if true == v then 1 else 0; + }; + + dbtype = mkOption { + type = types.enum [ "mysql" "percona" "mariadb" "postgresql" "sqlserver" ]; + default = "postgresql"; + description = '' + Specify the database provider: + <simplelist type='inline'> + <member><literal>mysql</literal></member> + <member><literal>percona</literal></member> + <member><literal>mariadb</literal></member> + <member><literal>postgresql</literal></member> + <member><literal>sqlserver</literal></member> + </simplelist> + ''; }; - config = mkIf cfg.enable { - systemd.services.documize-server = { - wantedBy = [ "multi-user.target" ]; - - script = '' - ${cfg.package}/bin/documize \ - -db "${cfg.db}" \ - -dbtype ${cfg.dbtype} \ - -port ${toString cfg.port} \ - -offline ${if cfg.offline then "1" else "0"} - ''; - - serviceConfig = { - Restart = "always"; - DynamicUser = "yes"; - }; + db = mkOption { + type = types.str; + description = '' + Database specific connection string for example: + <itemizedlist> + <listitem><para>MySQL/Percona/MariaDB: + <literal>user:password@tcp(host:3306)/documize</literal> + </para></listitem> + <listitem><para>MySQLv8+: + <literal>user:password@tcp(host:3306)/documize?allowNativePasswords=true</literal> + </para></listitem> + <listitem><para>PostgreSQL: + <literal>host=localhost port=5432 dbname=documize user=admin password=secret sslmode=disable</literal> + </para></listitem> + <listitem><para>MSSQL: + <literal>sqlserver://username:password@localhost:1433?database=Documize</literal> or + <literal>sqlserver://sa@localhost/SQLExpress?database=Documize</literal> + </para></listitem> + </itemizedlist> + ''; + }; + + location = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + reserved + ''; + }; + }; + + config = mkIf cfg.enable { + systemd.services.documize-server = { + description = "Documize Wiki"; + documentation = [ https://documize.com/ ]; + wantedBy = [ "multi-user.target" ]; + + serviceConfig = { + ExecStart = concatStringsSep " " [ + "${cfg.package}/bin/documize" + (mkParams false [ "db" "dbtype" "port" ]) + (mkParams true [ "offline" "location" "forcesslport" "key" "cert" "salt" ]) + ]; + Restart = "always"; + DynamicUser = "yes"; }; }; - } + }; +} diff --git a/nixos/modules/services/x11/window-managers/i3.nix b/nixos/modules/services/x11/window-managers/i3.nix index c9b0669e7ba..0ef55d5f2c0 100644 --- a/nixos/modules/services/x11/window-managers/i3.nix +++ b/nixos/modules/services/x11/window-managers/i3.nix @@ -60,12 +60,15 @@ in ${cfg.extraSessionCommands} ${cfg.package}/bin/i3 ${optionalString (cfg.configFile != null) - "-c \"${cfg.configFile}\"" + "-c /etc/i3/config" } & waitPID=$! ''; }]; environment.systemPackages = [ cfg.package ] ++ cfg.extraPackages; + environment.etc."i3/config" = mkIf (cfg.configFile != null) { + source = cfg.configFile; + }; }; imports = [ diff --git a/nixos/modules/system/activation/switch-to-configuration.pl b/nixos/modules/system/activation/switch-to-configuration.pl index 397b308b731..8ff00fa11dc 100644 --- a/nixos/modules/system/activation/switch-to-configuration.pl +++ b/nixos/modules/system/activation/switch-to-configuration.pl @@ -166,24 +166,6 @@ while (my ($unit, $state) = each %{$activePrev}) { if (-e $prevUnitFile && ($state->{state} eq "active" || $state->{state} eq "activating")) { if (! -e $newUnitFile || abs_path($newUnitFile) eq "/dev/null") { - # Ignore (i.e. never stop) these units: - if ($unit eq "system.slice") { - # TODO: This can be removed a few months after 18.09 is out - # (i.e. after everyone switched away from 18.03). - # Problem: Restarting (stopping) system.slice would not only - # stop X11 but also most system units/services. We obviously - # don't want this happening to users when they switch from 18.03 - # to 18.09 or nixos-unstable. - # Reason: The following change in systemd: - # https://github.com/systemd/systemd/commit/d8e5a9338278d6602a0c552f01f298771a384798 - # The commit adds system.slice to the perpetual units, which - # means removing the unit file and adding it to the source code. - # This is done so that system.slice can't be stopped anymore but - # in our case it ironically would cause this script to stop - # system.slice because the unit was removed (and an older - # systemd version is still running). - next; - } my $unitInfo = parseUnit($prevUnitFile); $unitsToStop{$unit} = 1 if boolIsTrue($unitInfo->{'X-StopOnRemoval'} // "yes"); } diff --git a/nixos/modules/system/boot/luksroot.nix b/nixos/modules/system/boot/luksroot.nix index aa4a5f8abcc..3841074f043 100644 --- a/nixos/modules/system/boot/luksroot.nix +++ b/nixos/modules/system/boot/luksroot.nix @@ -87,6 +87,9 @@ let mkdir -p /crypt-ramfs mount -t ramfs none /crypt-ramfs + # Cryptsetup locking directory + mkdir -p /run/cryptsetup + # For Yubikey salt storage mkdir -p /crypt-storage diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 016e695b3ad..5639b2668c3 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -72,6 +72,7 @@ in #ec2-config = (handleTestOn ["x86_64-linux"] ./ec2.nix {}).boot-ec2-config or {}; ec2-nixops = (handleTestOn ["x86_64-linux"] ./ec2.nix {}).boot-ec2-nixops or {}; ecryptfs = handleTest ./ecryptfs.nix {}; + ejabberd = handleTest ./ejabberd.nix {}; elk = handleTestOn ["x86_64-linux"] ./elk.nix {}; env = handleTest ./env.nix {}; etcd = handleTestOn ["x86_64-linux"] ./etcd.nix {}; @@ -172,6 +173,7 @@ in nix-ssh-serve = handleTest ./nix-ssh-serve.nix {}; novacomd = handleTestOn ["x86_64-linux"] ./novacomd.nix {}; nsd = handleTest ./nsd.nix {}; + nzbget = handleTest ./nzbget.nix {}; openldap = handleTest ./openldap.nix {}; opensmtpd = handleTest ./opensmtpd.nix {}; openssh = handleTest ./openssh.nix {}; diff --git a/nixos/tests/ejabberd.nix b/nixos/tests/ejabberd.nix new file mode 100644 index 00000000000..0691dfe780d --- /dev/null +++ b/nixos/tests/ejabberd.nix @@ -0,0 +1,262 @@ +import ./make-test.nix ({ pkgs, ... }: { + name = "ejabberd"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ ajs124 ]; + }; + nodes = { + client = { nodes, pkgs, ... }: { + environment.systemPackages = [ + (pkgs.callPackage ./xmpp-sendmessage.nix { connectTo = nodes.server.config.networking.primaryIPAddress; }) + ]; + }; + server = { config, pkgs, ... }: { + networking.extraHosts = '' + ${config.networking.primaryIPAddress} example.com + ''; + + services.ejabberd = { + enable = true; + configFile = "/etc/ejabberd.yml"; + }; + + environment.etc."ejabberd.yml" = { + user = "ejabberd"; + mode = "0600"; + text = '' + loglevel: 3 + + hosts: + - "example.com" + + listen: + - + port: 5222 + module: ejabberd_c2s + zlib: false + max_stanza_size: 65536 + shaper: c2s_shaper + access: c2s + - + port: 5269 + ip: "::" + module: ejabberd_s2s_in + - + port: 5347 + ip: "127.0.0.1" + module: ejabberd_service + access: local + shaper_rule: fast + ip: "127.0.0.1" + + ## Disabling digest-md5 SASL authentication. digest-md5 requires plain-text + ## password storage (see auth_password_format option). + disable_sasl_mechanisms: "digest-md5" + + ## Outgoing S2S options + ## Preferred address families (which to try first) and connect timeout + ## in seconds. + outgoing_s2s_families: + - ipv4 + - ipv6 + + ## auth_method: Method used to authenticate the users. + ## The default method is the internal. + ## If you want to use a different method, + ## comment this line and enable the correct ones. + auth_method: internal + + ## Store the plain passwords or hashed for SCRAM: + ## auth_password_format: plain + auth_password_format: scram + + ###' TRAFFIC SHAPERS + shaper: + # in B/s + normal: 1000000 + fast: 50000000 + + ## This option specifies the maximum number of elements in the queue + ## of the FSM. Refer to the documentation for details. + max_fsm_queue: 1000 + + ###' ACCESS CONTROL LISTS + acl: + ## The 'admin' ACL grants administrative privileges to XMPP accounts. + ## You can put here as many accounts as you want. + admin: + user: + - "root": "example.com" + + ## Local users: don't modify this. + local: + user_regexp: "" + + ## Loopback network + loopback: + ip: + - "127.0.0.0/8" + - "::1/128" + - "::FFFF:127.0.0.1/128" + + ###' SHAPER RULES + shaper_rules: + ## Maximum number of simultaneous sessions allowed for a single user: + max_user_sessions: 10 + ## Maximum number of offline messages that users can have: + max_user_offline_messages: + - 5000: admin + - 1024 + ## For C2S connections, all users except admins use the "normal" shaper + c2s_shaper: + - none: admin + - normal + ## All S2S connections use the "fast" shaper + s2s_shaper: fast + + ###' ACCESS RULES + access_rules: + ## This rule allows access only for local users: + local: + - allow: local + ## Only non-blocked users can use c2s connections: + c2s: + - deny: blocked + - allow + ## Only admins can send announcement messages: + announce: + - allow: admin + ## Only admins can use the configuration interface: + configure: + - allow: admin + ## Only accounts of the local ejabberd server can create rooms: + muc_create: + - allow: local + ## Only accounts on the local ejabberd server can create Pubsub nodes: + pubsub_createnode: + - allow: local + ## In-band registration allows registration of any possible username. + ## To disable in-band registration, replace 'allow' with 'deny'. + register: + - allow + ## Only allow to register from localhost + trusted_network: + - allow: loopback + + ## =============== + ## API PERMISSIONS + ## =============== + ## + ## This section allows you to define who and using what method + ## can execute commands offered by ejabberd. + ## + ## By default "console commands" section allow executing all commands + ## issued using ejabberdctl command, and "admin access" section allows + ## users in admin acl that connect from 127.0.0.1 to execute all + ## commands except start and stop with any available access method + ## (ejabberdctl, http-api, xmlrpc depending what is enabled on server). + ## + ## If you remove "console commands" there will be one added by + ## default allowing executing all commands, but if you just change + ## permissions in it, version from config file will be used instead + ## of default one. + ## + api_permissions: + "console commands": + from: + - ejabberd_ctl + who: all + what: "*" + + language: "en" + + ###' MODULES + ## Modules enabled in all ejabberd virtual hosts. + modules: + mod_adhoc: {} + mod_announce: # recommends mod_adhoc + access: announce + mod_blocking: {} # requires mod_privacy + mod_caps: {} + mod_carboncopy: {} + mod_client_state: {} + mod_configure: {} # requires mod_adhoc + ## mod_delegation: {} # for xep0356 + mod_echo: {} + #mod_irc: + # host: "irc.@HOST@" + # default_encoding: "utf-8" + ## mod_bosh: {} + ## mod_http_fileserver: + ## docroot: "/var/www" + ## accesslog: "/var/log/ejabberd/access.log" + #mod_http_upload: + # thumbnail: false # otherwise needs the identify command from ImageMagick installed + # put_url: "https://@HOST@:5444" + ## # docroot: "@HOME@/upload" + #mod_http_upload_quota: + # max_days: 14 + mod_last: {} + ## XEP-0313: Message Archive Management + ## You might want to setup a SQL backend for MAM because the mnesia database is + ## limited to 2GB which might be exceeded on large servers + mod_mam: {} + mod_muc: + host: "muc.@HOST@" + access: + - allow + access_admin: + - allow: admin + access_create: muc_create + access_persistent: muc_create + mod_muc_admin: {} + mod_muc_log: {} + mod_offline: + access_max_user_messages: max_user_offline_messages + mod_ping: {} + ## mod_pres_counter: + ## count: 5 + ## interval: 60 + mod_privacy: {} + mod_private: {} + mod_roster: + versioning: true + mod_shared_roster: {} + mod_stats: {} + mod_time: {} + mod_vcard: + search: false + mod_vcard_xupdate: {} + ## Convert all avatars posted by Android clients from WebP to JPEG + mod_avatar: {} + # convert: + # webp: jpeg + mod_version: {} + mod_stream_mgmt: {} + ## The module for S2S dialback (XEP-0220). Please note that you cannot + ## rely solely on dialback if you want to federate with other servers, + ## because a lot of servers have dialback disabled and instead rely on + ## PKIX authentication. Make sure you have proper certificates installed + ## and check your accessibility at https://check.messaging.one/ + mod_s2s_dialback: {} + mod_pubsub: + plugins: + - "pep" + mod_push: {} + ''; + }; + + networking.firewall.enable = false; + }; + }; + + testScript = { nodes, ... }: '' + $server->waitForUnit('ejabberd.service'); + $server->succeed('su ejabberd -s $(which ejabberdctl) status|grep started') =~ /ejabberd is running/; + $server->succeed('su ejabberd -s $(which ejabberdctl) register azurediamond example.com hunter2'); + $server->succeed('su ejabberd -s $(which ejabberdctl) register cthon98 example.com nothunter2'); + $server->fail('su ejabberd -s $(which ejabberdctl) register asdf wrong.domain'); + $client->succeed('send-message'); + $server->succeed('su ejabberd -s $(which ejabberdctl) unregister cthon98 example.com'); + $server->succeed('su ejabberd -s $(which ejabberdctl) unregister azurediamond example.com'); + ''; +}) diff --git a/nixos/tests/jellyfin.nix b/nixos/tests/jellyfin.nix new file mode 100644 index 00000000000..b60c6eb94f4 --- /dev/null +++ b/nixos/tests/jellyfin.nix @@ -0,0 +1,16 @@ +import ./make-test.nix ({ lib, ...}: + +{ + name = "jellyfin"; + meta.maintainers = with lib.maintainers; [ minijackson ]; + + machine = + { ... }: + { services.jellyfin.enable = true; }; + + testScript = '' + $machine->waitForUnit('jellyfin.service'); + $machine->waitForOpenPort('8096'); + $machine->succeed("curl --fail http://localhost:8096/"); + ''; +}) diff --git a/nixos/tests/nzbget.nix b/nixos/tests/nzbget.nix new file mode 100644 index 00000000000..042ccec98cf --- /dev/null +++ b/nixos/tests/nzbget.nix @@ -0,0 +1,26 @@ +import ./make-test.nix ({ pkgs, ...} : { + name = "nzbget"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ aanderse flokli ]; + }; + + nodes = { + server = { ... }: { + services.nzbget.enable = true; + + # hack, don't add (unfree) unrar to nzbget's path, + # so we can run this test in CI + systemd.services.nzbget.path = pkgs.stdenv.lib.mkForce [ pkgs.p7zip ]; + }; + }; + + testScript = '' + startAll; + + $server->waitForUnit("nzbget.service"); + $server->waitForUnit("network.target"); + $server->waitForOpenPort(6789); + $server->succeed("curl -s -u nzbget:tegbzn6789 http://127.0.0.1:6789 | grep -q 'This file is part of nzbget'"); + $server->succeed("${pkgs.nzbget}/bin/nzbget -n -o ControlIP=127.0.0.1 -o ControlPort=6789 -o ControlPassword=tegbzn6789 -V"); + ''; +}) diff --git a/nixos/tests/prosody.nix b/nixos/tests/prosody.nix index 61ae5bb38ed..a39bae7898d 100644 --- a/nixos/tests/prosody.nix +++ b/nixos/tests/prosody.nix @@ -9,70 +9,30 @@ import ./make-test.nix { extraConfig = '' storage = "sql" ''; + virtualHosts.test = { + domain = "example.com"; + enabled = true; + }; }; - environment.systemPackages = let - sendMessage = pkgs.writeScriptBin "send-message" '' - #!/usr/bin/env python3 - # Based on the sleekxmpp send_client example, look there for more details: - # https://github.com/fritzy/SleekXMPP/blob/develop/examples/send_client.py - import sleekxmpp - - class SendMsgBot(sleekxmpp.ClientXMPP): - """ - A basic SleekXMPP bot that will log in, send a message, - and then log out. - """ - def __init__(self, jid, password, recipient, message): - sleekxmpp.ClientXMPP.__init__(self, jid, password) - - self.recipient = recipient - self.msg = message - - self.add_event_handler("session_start", self.start, threaded=True) - - def start(self, event): - self.send_presence() - self.get_roster() - - self.send_message(mto=self.recipient, - mbody=self.msg, - mtype='chat') - - self.disconnect(wait=True) - - - if __name__ == '__main__': - xmpp = SendMsgBot("test1@localhost", "test1", "test2@localhost", "Hello World!") - xmpp.register_plugin('xep_0030') # Service Discovery - xmpp.register_plugin('xep_0199') # XMPP Ping - - # TODO: verify certificate - # If you want to verify the SSL certificates offered by a server: - # xmpp.ca_certs = "path/to/ca/cert" - - if xmpp.connect(('localhost', 5222)): - xmpp.process(block=True) - else: - print("Unable to connect.") - sys.exit(1) - ''; - in [ (pkgs.python3.withPackages (ps: [ ps.sleekxmpp ])) sendMessage ]; + environment.systemPackages = [ + (pkgs.callPackage ./xmpp-sendmessage.nix {}) + ]; }; testScript = '' $machine->waitForUnit('prosody.service'); $machine->succeed('prosodyctl status') =~ /Prosody is running/; - # set password to 'test' (it's asked twice) - $machine->succeed('yes test1 | prosodyctl adduser test1@localhost'); + # set password to 'nothunter2' (it's asked twice) + $machine->succeed('yes nothunter2 | prosodyctl adduser cthon98@example.com'); # set password to 'y' - $machine->succeed('yes | prosodyctl adduser test2@localhost'); - # correct password to 'test2' - $machine->succeed('yes test2 | prosodyctl passwd test2@localhost'); + $machine->succeed('yes | prosodyctl adduser azurediamond@example.com'); + # correct password to 'hunter2' + $machine->succeed('yes hunter2 | prosodyctl passwd azurediamond@example.com'); $machine->succeed("send-message"); - $machine->succeed('prosodyctl deluser test1@localhost'); - $machine->succeed('prosodyctl deluser test2@localhost'); + $machine->succeed('prosodyctl deluser cthon98@example.com'); + $machine->succeed('prosodyctl deluser azurediamond@example.com'); ''; } diff --git a/nixos/tests/xmpp-sendmessage.nix b/nixos/tests/xmpp-sendmessage.nix new file mode 100644 index 00000000000..2a075a01813 --- /dev/null +++ b/nixos/tests/xmpp-sendmessage.nix @@ -0,0 +1,46 @@ +{ writeScriptBin, python3, connectTo ? "localhost" }: +writeScriptBin "send-message" '' + #!${(python3.withPackages (ps: [ ps.sleekxmpp ])).interpreter} + # Based on the sleekxmpp send_client example, look there for more details: + # https://github.com/fritzy/SleekXMPP/blob/develop/examples/send_client.py + import sleekxmpp + + class SendMsgBot(sleekxmpp.ClientXMPP): + """ + A basic SleekXMPP bot that will log in, send a message, + and then log out. + """ + def __init__(self, jid, password, recipient, message): + sleekxmpp.ClientXMPP.__init__(self, jid, password) + + self.recipient = recipient + self.msg = message + + self.add_event_handler("session_start", self.start, threaded=True) + + def start(self, event): + self.send_presence() + self.get_roster() + + self.send_message(mto=self.recipient, + mbody=self.msg, + mtype='chat') + + self.disconnect(wait=True) + + + if __name__ == '__main__': + xmpp = SendMsgBot("cthon98@example.com", "nothunter2", "azurediamond@example.com", "hey, if you type in your pw, it will show as stars") + xmpp.register_plugin('xep_0030') # Service Discovery + xmpp.register_plugin('xep_0199') # XMPP Ping + + # TODO: verify certificate + # If you want to verify the SSL certificates offered by a server: + # xmpp.ca_certs = "path/to/ca/cert" + + if xmpp.connect(('${connectTo}', 5222)): + xmpp.process(block=True) + else: + print("Unable to connect.") + sys.exit(1) +'' |