diff options
Diffstat (limited to 'nixos')
-rw-r--r-- | nixos/doc/manual/release-notes/rl-1909.xml | 10 | ||||
-rw-r--r-- | nixos/modules/hardware/openrazer.nix | 133 | ||||
-rw-r--r-- | nixos/modules/hardware/printers.nix | 135 | ||||
-rw-r--r-- | nixos/modules/module-list.nix | 4 | ||||
-rw-r--r-- | nixos/modules/services/desktops/blueman.nix | 25 | ||||
-rw-r--r-- | nixos/modules/services/desktops/gnome3/glib-networking.nix | 6 | ||||
-rw-r--r-- | nixos/modules/services/networking/syncplay.nix | 80 | ||||
-rw-r--r-- | nixos/tests/containers-tmpfs.nix | 2 | ||||
-rw-r--r-- | nixos/tests/printing.nix | 177 |
9 files changed, 487 insertions, 85 deletions
diff --git a/nixos/doc/manual/release-notes/rl-1909.xml b/nixos/doc/manual/release-notes/rl-1909.xml index 11c13b8d942b..c0b7cc48a461 100644 --- a/nixos/doc/manual/release-notes/rl-1909.xml +++ b/nixos/doc/manual/release-notes/rl-1909.xml @@ -135,7 +135,17 @@ <literal>./programs/dwm-status.nix</literal> </para> </listitem> + <listitem> + <para> + The new <varname>hardware.printers</varname> module allows to declaratively configure CUPS printers + via the <varname>ensurePrinters</varname> and + <varname>ensureDefaultPrinter</varname> options. + <varname>ensurePrinters</varname> will never delete existing printers, + but will make sure that the given printers are configured as declared. + </para> + </listitem> </itemizedlist> + </section> <section xmlns="http://docbook.org/ns/docbook" diff --git a/nixos/modules/hardware/openrazer.nix b/nixos/modules/hardware/openrazer.nix new file mode 100644 index 000000000000..883db7f2f4f1 --- /dev/null +++ b/nixos/modules/hardware/openrazer.nix @@ -0,0 +1,133 @@ +{ config, pkgs, lib, ... }: + +with lib; + +let + cfg = config.hardware.openrazer; + kernelPackages = config.boot.kernelPackages; + + toPyBoolStr = b: if b then "True" else "False"; + + daemonExe = "${pkgs.openrazer-daemon}/bin/openrazer-daemon --config ${daemonConfFile}"; + + daemonConfFile = pkgs.writeTextFile { + name = "razer.conf"; + text = '' + [General] + verbose_logging = ${toPyBoolStr cfg.verboseLogging} + + [Startup] + sync_effects_enabled = ${toPyBoolStr cfg.syncEffectsEnabled} + devices_off_on_screensaver = ${toPyBoolStr cfg.devicesOffOnScreensaver} + mouse_battery_notifier = ${toPyBoolStr cfg.mouseBatteryNotifier} + + [Statistics] + key_statistics = ${toPyBoolStr cfg.keyStatistics} + ''; + }; + + dbusServiceFile = pkgs.writeTextFile rec { + name = "org.razer.service"; + destination = "/share/dbus-1/services/${name}"; + text = '' + [D-BUS Service] + Name=org.razer + Exec=${daemonExe} + SystemdService=openrazer-daemon.service + ''; + }; + + drivers = [ + "razerkbd" + "razermouse" + "razerfirefly" + "razerkraken" + "razermug" + "razercore" + ]; +in +{ + options = { + hardware.openrazer = { + enable = mkEnableOption "OpenRazer drivers and userspace daemon."; + + verboseLogging = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable verbose logging. Logs debug messages. + ''; + }; + + syncEffectsEnabled = mkOption { + type = types.bool; + default = true; + description = '' + Set the sync effects flag to true so any assignment of + effects will work across devices. + ''; + }; + + devicesOffOnScreensaver = mkOption { + type = types.bool; + default = true; + description = '' + Turn off the devices when the systems screensaver kicks in. + ''; + }; + + mouseBatteryNotifier = mkOption { + type = types.bool; + default = true; + description = '' + Mouse battery notifier. + ''; + }; + + keyStatistics = mkOption { + type = types.bool; + default = false; + description = '' + Collects number of keypresses per hour per key used to + generate a heatmap. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + boot.extraModulePackages = [ kernelPackages.openrazer ]; + boot.kernelModules = drivers; + + # Makes the man pages available so you can succesfully run + # > systemctl --user help openrazer-daemon + environment.systemPackages = [ pkgs.python3Packages.openrazer-daemon.man ]; + + services.udev.packages = [ kernelPackages.openrazer ]; + services.dbus.packages = [ dbusServiceFile ]; + + # A user must be a member of the plugdev group in order to start + # the openrazer-daemon. Therefore we make sure that the plugdev + # group exists. + users.groups.plugdev = {}; + + systemd.user.services.openrazer-daemon = { + description = "Daemon to manage razer devices in userspace"; + unitConfig.Documentation = "man:openrazer-daemon(8)"; + # Requires a graphical session so the daemon knows when the screensaver + # starts. See the 'devicesOffOnScreensaver' option. + wantedBy = [ "graphical-session.target" ]; + partOf = [ "graphical-session.target" ]; + serviceConfig = { + Type = "dbus"; + BusName = "org.razer"; + ExecStart = "${daemonExe} --foreground"; + Restart = "always"; + }; + }; + }; + + meta = { + maintainers = with lib.maintainers; [ roelvandijk ]; + }; +} diff --git a/nixos/modules/hardware/printers.nix b/nixos/modules/hardware/printers.nix new file mode 100644 index 000000000000..12ee5516d4ed --- /dev/null +++ b/nixos/modules/hardware/printers.nix @@ -0,0 +1,135 @@ +{ config, lib, pkgs, ... }: +with lib; +let + cfg = config.hardware.printers; + ppdOptionsString = options: optionalString (options != {}) + (concatStringsSep " " + (mapAttrsToList (name: value: "-o '${name}'='${value}'") options) + ); + ensurePrinter = p: '' + ${pkgs.cups}/bin/lpadmin -p '${p.name}' -E \ + ${optionalString (p.location != null) "-L '${p.location}'"} \ + ${optionalString (p.description != null) "-D '${p.description}'"} \ + -v '${p.deviceUri}' \ + -m '${p.model}' \ + ${ppdOptionsString p.ppdOptions} + ''; + ensureDefaultPrinter = name: '' + ${pkgs.cups}/bin/lpoptions -d '${name}' + ''; + + # "graph but not # or /" can't be implemented as regex alone due to missing lookahead support + noInvalidChars = str: all (c: c != "#" && c != "/") (stringToCharacters str); + printerName = (types.addCheck (types.strMatching "[[:graph:]]+") noInvalidChars) + // { description = "printable string without spaces, # and /"; }; + + +in { + options = { + hardware.printers = { + ensureDefaultPrinter = mkOption { + type = types.nullOr printerName; + default = null; + description = '' + Ensures the named printer is the default CUPS printer / printer queue. + ''; + }; + ensurePrinters = mkOption { + description = '' + Will regularly ensure that the given CUPS printers are configured as declared here. + If a printer's options are manually changed afterwards, they will be overwritten eventually. + This option will never delete any printer, even if removed from this list. + You can check existing printers with <command>lpstat -s</command> + and remove printers with <command>lpadmin -x <printer-name></command>. + Printers not listed here can still be manually configured. + ''; + default = []; + type = types.listOf (types.submodule { + options = { + name = mkOption { + type = printerName; + example = "BrotherHL_Workroom"; + description = '' + Name of the printer / printer queue. + May contain any printable characters except "/", "#", and space. + ''; + }; + location = mkOption { + type = types.nullOr types.str; + default = null; + example = "Workroom"; + description = '' + Optional human-readable location. + ''; + }; + description = mkOption { + type = types.nullOr types.str; + default = null; + example = "Brother HL-5140"; + description = '' + Optional human-readable description. + ''; + }; + deviceUri = mkOption { + type = types.str; + example = [ + "ipp://printserver.local/printers/BrotherHL_Workroom" + "usb://HP/DESKJET%20940C?serial=CN16E6C364BH" + ]; + description = '' + How to reach the printer. + <command>lpinfo -v</command> shows a list of supported device URIs and schemes. + ''; + }; + model = mkOption { + type = types.str; + example = literalExample '' + gutenprint.''${lib.version.majorMinor (lib.getVersion pkgs.cups)}://brother-hl-5140/expert + ''; + description = '' + Location of the ppd driver file for the printer. + <command>lpinfo -m</command> shows a list of supported models. + ''; + }; + ppdOptions = mkOption { + type = types.attrsOf types.str; + example = { + "PageSize" = "A4"; + "Duplex" = "DuplexNoTumble"; + }; + default = {}; + description = '' + Sets PPD options for the printer. + <command>lpoptions [-p printername] -l</command> shows suported PPD options for the given printer. + ''; + }; + }; + }); + }; + }; + }; + + config = mkIf (cfg.ensurePrinters != [] && config.services.printing.enable) { + systemd.services."ensure-printers" = let + cupsUnit = if config.services.printing.startWhenNeeded then "cups.socket" else "cups.service"; + in { + description = "Ensure NixOS-configured CUPS printers"; + wantedBy = [ "multi-user.target" ]; + requires = [ cupsUnit ]; + # in contrast to cups.socket, for cups.service, this is actually not enough, + # as the cups service reports its activation before clients can actually interact with it. + # Because of this, commands like `lpinfo -v` will report a bad file descriptor + # due to the missing UNIX socket without sufficient sleep time. + after = [ cupsUnit ]; + + serviceConfig = { + Type = "oneshot"; + }; + + # sleep 10 is required to wait until cups.service is actually initialized and has created its UNIX socket file + script = (optionalString (!config.services.printing.startWhenNeeded) "sleep 10\n") + + (concatMapStringsSep "\n" ensurePrinter cfg.ensurePrinters) + + optionalString (cfg.ensureDefaultPrinter != null) (ensureDefaultPrinter cfg.ensureDefaultPrinter); + }; + }; +} diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 4bc37ed3f171..290c29993b58 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -58,7 +58,9 @@ ./hardware/network/intel-2200bg.nix ./hardware/nitrokey.nix ./hardware/opengl.nix + ./hardware/openrazer.nix ./hardware/pcmcia.nix + ./hardware/printers.nix ./hardware/raid/hpsa.nix ./hardware/steam-hardware.nix ./hardware/usb-wwan.nix @@ -281,6 +283,7 @@ ./services/databases/virtuoso.nix ./services/desktops/accountsservice.nix ./services/desktops/bamf.nix + ./services/desktops/blueman.nix ./services/desktops/deepin/deepin.nix ./services/desktops/dleyna-renderer.nix ./services/desktops/dleyna-server.nix @@ -698,6 +701,7 @@ ./services/networking/supybot.nix ./services/networking/syncthing.nix ./services/networking/syncthing-relay.nix + ./services/networking/syncplay.nix ./services/networking/tcpcrypt.nix ./services/networking/teamspeak3.nix ./services/networking/tedicross.nix diff --git a/nixos/modules/services/desktops/blueman.nix b/nixos/modules/services/desktops/blueman.nix new file mode 100644 index 000000000000..18ad610247ed --- /dev/null +++ b/nixos/modules/services/desktops/blueman.nix @@ -0,0 +1,25 @@ +# blueman service +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.blueman; +in { + ###### interface + options = { + services.blueman = { + enable = mkEnableOption "blueman"; + }; + }; + + ###### implementation + config = mkIf cfg.enable { + + environment.systemPackages = [ pkgs.blueman ]; + + services.dbus.packages = [ pkgs.blueman ]; + + systemd.packages = [ pkgs.blueman ]; + }; +} diff --git a/nixos/modules/services/desktops/gnome3/glib-networking.nix b/nixos/modules/services/desktops/gnome3/glib-networking.nix index 186668d7d385..fcd58509d6fc 100644 --- a/nixos/modules/services/desktops/gnome3/glib-networking.nix +++ b/nixos/modules/services/desktops/gnome3/glib-networking.nix @@ -22,11 +22,11 @@ with lib; config = mkIf config.services.gnome3.glib-networking.enable { - services.dbus.packages = [ pkgs.gnome3.glib-networking ]; + services.dbus.packages = [ pkgs.glib-networking ]; - systemd.packages = [ pkgs.gnome3.glib-networking ]; + systemd.packages = [ pkgs.glib-networking ]; - environment.variables.GIO_EXTRA_MODULES = [ "${pkgs.gnome3.glib-networking.out}/lib/gio/modules" ]; + environment.variables.GIO_EXTRA_MODULES = [ "${pkgs.glib-networking.out}/lib/gio/modules" ]; }; diff --git a/nixos/modules/services/networking/syncplay.nix b/nixos/modules/services/networking/syncplay.nix new file mode 100644 index 000000000000..e3147c10502c --- /dev/null +++ b/nixos/modules/services/networking/syncplay.nix @@ -0,0 +1,80 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.syncplay; + + cmdArgs = + [ "--port" cfg.port ] + ++ optionals (cfg.salt != null) [ "--salt" cfg.salt ] + ++ optionals (cfg.certDir != null) [ "--tls" cfg.certDir ]; + +in +{ + options = { + services.syncplay = { + enable = mkOption { + type = types.bool; + default = false; + description = "If enabled, start the Syncplay server."; + }; + + port = mkOption { + type = types.int; + default = 8999; + description = '' + TCP port to bind to. + ''; + }; + + salt = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + Salt to allow room operator passwords generated by this server + instance to still work when the server is restarted. + ''; + }; + + certDir = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + TLS certificates directory to use for encryption. See + <link xlink:href="https://github.com/Syncplay/syncplay/wiki/TLS-support"/>. + ''; + }; + + user = mkOption { + type = types.str; + default = "nobody"; + description = '' + User to use when running Syncplay. + ''; + }; + + group = mkOption { + type = types.str; + default = "nogroup"; + description = '' + Group to use when running Syncplay. + ''; + }; + }; + }; + + config = mkIf cfg.enable { + systemd.services.syncplay = { + description = "Syncplay Service"; + wantedBy = [ "multi-user.target" ]; + after = [ "network-online.target "]; + + serviceConfig = { + ExecStart = "${pkgs.syncplay}/bin/syncplay-server ${escapeShellArgs cmdArgs}"; + User = cfg.user; + Group = cfg.group; + }; + }; + }; +} diff --git a/nixos/tests/containers-tmpfs.nix b/nixos/tests/containers-tmpfs.nix index 05c21f4907bf..e29fe6bbf032 100644 --- a/nixos/tests/containers-tmpfs.nix +++ b/nixos/tests/containers-tmpfs.nix @@ -3,7 +3,7 @@ import ./make-test.nix ({ pkgs, ...} : { name = "containers-tmpfs"; meta = with pkgs.stdenv.lib.maintainers; { - maintainers = [ ckampka ]; + maintainers = [ kampka ]; }; machine = diff --git a/nixos/tests/printing.nix b/nixos/tests/printing.nix index 74583ae55623..4d0df289cf75 100644 --- a/nixos/tests/printing.nix +++ b/nixos/tests/printing.nix @@ -1,99 +1,114 @@ # Test printing via CUPS. -import ./make-test.nix ({pkgs, ... }: { +import ./make-test.nix ({pkgs, ... }: +let + printingServer = startWhenNeeded: { + services.printing.enable = true; + services.printing.startWhenNeeded = startWhenNeeded; + services.printing.listenAddresses = [ "*:631" ]; + services.printing.defaultShared = true; + services.printing.extraConf = + '' + <Location /> + Order allow,deny + Allow from all + </Location> + ''; + networking.firewall.allowedTCPPorts = [ 631 ]; + # Add a HP Deskjet printer connected via USB to the server. + hardware.printers.ensurePrinters = [{ + name = "DeskjetLocal"; + deviceUri = "usb://foobar/printers/foobar"; + model = "drv:///sample.drv/deskjet.ppd"; + }]; + }; + printingClient = startWhenNeeded: { + services.printing.enable = true; + services.printing.startWhenNeeded = startWhenNeeded; + # Add printer to the client as well, via IPP. + hardware.printers.ensurePrinters = [{ + name = "DeskjetRemote"; + deviceUri = "ipp://${if startWhenNeeded then "socketActivatedServer" else "serviceServer"}/printers/DeskjetLocal"; + model = "drv:///sample.drv/deskjet.ppd"; + }]; + hardware.printers.ensureDefaultPrinter = "DeskjetRemote"; + }; + +in + +{ name = "printing"; meta = with pkgs.stdenv.lib.maintainers; { maintainers = [ domenkozar eelco matthewbauer ]; }; nodes = { + socketActivatedServer = { ... }: (printingServer true); + serviceServer = { ... }: (printingServer false); - server = - { ... }: - { services.printing.enable = true; - services.printing.listenAddresses = [ "*:631" ]; - services.printing.defaultShared = true; - services.printing.extraConf = - '' - <Location /> - Order allow,deny - Allow from all - </Location> - ''; - networking.firewall.allowedTCPPorts = [ 631 ]; - }; - - client = - { ... }: - { services.printing.enable = true; - }; - + socketActivatedClient = { ... }: (printingClient true); + serviceClient = { ... }: (printingClient false); }; testScript = '' startAll; - $client->succeed("lpstat -r") =~ /scheduler is running/ or die; - # check local encrypted connections work without error - $client->succeed("lpstat -E -r") =~ /scheduler is running/ or die; - # Test that UNIX socket is used for connections. - $client->succeed("lpstat -H") =~ "/run/cups/cups.sock" or die; - # Test that HTTP server is available too. - $client->succeed("curl --fail http://localhost:631/"); - $client->succeed("curl --fail http://server:631/"); - $server->fail("curl --fail --connect-timeout 2 http://client:631/"); - - # Add a HP Deskjet printer connected via USB to the server. - $server->succeed("lpadmin -p DeskjetLocal -E -v usb://foobar/printers/foobar"); - - # Add it to the client as well via IPP. - $client->succeed("lpadmin -p DeskjetRemote -E -v ipp://server/printers/DeskjetLocal"); - $client->succeed("lpadmin -d DeskjetRemote"); - - # Do some status checks. - $client->succeed("lpstat -a") =~ /DeskjetRemote accepting requests/ or die; - $client->succeed("lpstat -h server:631 -a") =~ /DeskjetLocal accepting requests/ or die; - $client->succeed("cupsdisable DeskjetRemote"); - $client->succeed("lpq") =~ /DeskjetRemote is not ready.*no entries/s or die; - $client->succeed("cupsenable DeskjetRemote"); - $client->succeed("lpq") =~ /DeskjetRemote is ready.*no entries/s or die; - - # Test printing various file types. - foreach my $file ("${pkgs.groff.doc}/share/doc/*/examples/mom/penguin.pdf", - "${pkgs.groff.doc}/share/doc/*/meref.ps", - "${pkgs.cups.out}/share/doc/cups/images/cups.png", - "${pkgs.pcre.doc}/share/doc/pcre/pcre.txt") - { - $file =~ /([^\/]*)$/; my $fn = $1; - - subtest "print $fn", sub { - - # Print the file on the client. - $client->succeed("lp $file"); - $client->sleep(10); - $client->succeed("lpq") =~ /active.*root.*$fn/ or die; - - # Ensure that a raw PCL file appeared in the server's queue - # (showing that the right filters have been applied). Of - # course, since there is no actual USB printer attached, the - # file will stay in the queue forever. - $server->waitForFile("/var/spool/cups/d*-001"); - $server->sleep(10); - $server->succeed("lpq -a") =~ /$fn/ or die; - - # Delete the job on the client. It should disappear on the - # server as well. - $client->succeed("lprm"); - $client->sleep(10); - $client->succeed("lpq -a") =~ /no entries/; - Machine::retry sub { - return 1 if $server->succeed("lpq -a") =~ /no entries/; + # Make sure that cups is up on both sides. + $serviceServer->waitForUnit("cups.service"); + $serviceClient->waitForUnit("cups.service"); + # wait until cups is fully initialized and ensure-printers has executed with 10s delay + $serviceClient->sleep(20); + $socketActivatedClient->waitUntilSucceeds("systemctl status ensure-printers | grep -q -E 'code=exited, status=0/SUCCESS'"); + sub testPrinting { + my ($client, $server) = (@_); + my $clientHostname = $client->name(); + my $serverHostname = $server->name(); + $client->succeed("lpstat -r") =~ /scheduler is running/ or die; + # Test that UNIX socket is used for connections. + $client->succeed("lpstat -H") =~ "/var/run/cups/cups.sock" or die; + # Test that HTTP server is available too. + $client->succeed("curl --fail http://localhost:631/"); + $client->succeed("curl --fail http://$serverHostname:631/"); + $server->fail("curl --fail --connect-timeout 2 http://$clientHostname:631/"); + # Do some status checks. + $client->succeed("lpstat -a") =~ /DeskjetRemote accepting requests/ or die; + $client->succeed("lpstat -h $serverHostname:631 -a") =~ /DeskjetLocal accepting requests/ or die; + $client->succeed("cupsdisable DeskjetRemote"); + $client->succeed("lpq") =~ /DeskjetRemote is not ready.*no entries/s or die; + $client->succeed("cupsenable DeskjetRemote"); + $client->succeed("lpq") =~ /DeskjetRemote is ready.*no entries/s or die; + # Test printing various file types. + foreach my $file ("${pkgs.groff.doc}/share/doc/*/examples/mom/penguin.pdf", + "${pkgs.groff.doc}/share/doc/*/meref.ps", + "${pkgs.cups.out}/share/doc/cups/images/cups.png", + "${pkgs.pcre.doc}/share/doc/pcre/pcre.txt") + { + $file =~ /([^\/]*)$/; my $fn = $1; + subtest "print $fn", sub { + # Print the file on the client. + $client->succeed("lp $file"); + $client->waitUntilSucceeds("lpq | grep -q -E 'active.*root.*$fn'"); + # Ensure that a raw PCL file appeared in the server's queue + # (showing that the right filters have been applied). Of + # course, since there is no actual USB printer attached, the + # file will stay in the queue forever. + $server->waitForFile("/var/spool/cups/d*-001"); + $server->waitUntilSucceeds("lpq -a | grep -q -E '$fn'"); + # Delete the job on the client. It should disappear on the + # server as well. + $client->succeed("lprm"); + $client->waitUntilSucceeds("lpq -a | grep -q -E 'no entries'"); + Machine::retry sub { + return 1 if $server->succeed("lpq -a") =~ /no entries/; + }; + # The queue is empty already, so this should be safe. + # Otherwise, pairs of "c*"-"d*-001" files might persist. + $server->execute("rm /var/spool/cups/*"); }; - # The queue is empty already, so this should be safe. - # Otherwise, pairs of "c*"-"d*-001" files might persist. - $server->execute("rm /var/spool/cups/*"); - }; + } } - ''; + testPrinting($serviceClient, $serviceServer); + testPrinting($socketActivatedClient, $socketActivatedServer); + ''; }) |