diff options
author | Alyssa Ross <hi@alyssa.is> | 2023-10-22 19:49:40 +0000 |
---|---|---|
committer | Alyssa Ross <hi@alyssa.is> | 2023-10-22 19:49:40 +0000 |
commit | 20437996b9a2db0a9d32391d15fbd1e8872f3baf (patch) | |
tree | 6b3287be5160b30523675178d52ba440e4267465 /nixpkgs/nixos | |
parent | 0f33cd5208992fe4992f932374a872f7687b9a6d (diff) | |
parent | a37d70e902fed9b9c0d1d4ac978e4373b86cc9bb (diff) | |
download | nixlib-20437996b9a2db0a9d32391d15fbd1e8872f3baf.tar nixlib-20437996b9a2db0a9d32391d15fbd1e8872f3baf.tar.gz nixlib-20437996b9a2db0a9d32391d15fbd1e8872f3baf.tar.bz2 nixlib-20437996b9a2db0a9d32391d15fbd1e8872f3baf.tar.lz nixlib-20437996b9a2db0a9d32391d15fbd1e8872f3baf.tar.xz nixlib-20437996b9a2db0a9d32391d15fbd1e8872f3baf.tar.zst nixlib-20437996b9a2db0a9d32391d15fbd1e8872f3baf.zip |
Merge commit 'a37d70e902fed9b9c0d1d4ac978e4373b86cc9bb'
Diffstat (limited to 'nixpkgs/nixos')
27 files changed, 867 insertions, 43 deletions
diff --git a/nixpkgs/nixos/doc/manual/release-notes/rl-2311.section.md b/nixpkgs/nixos/doc/manual/release-notes/rl-2311.section.md index 688d7036d458..9e2afe5fd201 100644 --- a/nixpkgs/nixos/doc/manual/release-notes/rl-2311.section.md +++ b/nixpkgs/nixos/doc/manual/release-notes/rl-2311.section.md @@ -80,6 +80,8 @@ - [Jool](https://nicmx.github.io/Jool/en/index.html), a kernelspace NAT64 and SIIT implementation, providing translation between IPv4 and IPv6. Available as [networking.jool.enable](#opt-networking.jool.enable). +- [Home Assistant Satellite], a streaming audio satellite for Home Assistant voice pipelines, where you can reuse existing mic/speaker hardware. Available as [services.homeassistant-satellite](#opt-services.homeassistant-satellite.enable). + - [Apache Guacamole](https://guacamole.apache.org/), a cross-platform, clientless remote desktop gateway. Available as [services.guacamole-server](#opt-services.guacamole-server.enable) and [services.guacamole-client](#opt-services.guacamole-client.enable) services. - [pgBouncer](https://www.pgbouncer.org), a PostgreSQL connection pooler. Available as [services.pgbouncer](#opt-services.pgbouncer.enable). @@ -321,6 +323,8 @@ - `win-virtio` package was renamed to `virtio-win` to be consistent with the upstream package name. +- `ps3netsrv` has been replaced with the webman-mod fork, the executable has been renamed from `ps3netsrv++` to `ps3netsrv` and cli parameters have changed. + ## Other Notable Changes {#sec-release-23.11-notable-changes} - The Cinnamon module now enables XDG desktop integration by default. If you are experiencing collisions related to xdg-desktop-portal-gtk you can safely remove `xdg.portal.extraPortals = [ pkgs.xdg-desktop-portal-gtk ];` from your NixOS configuration. @@ -493,3 +497,5 @@ The module update takes care of the new config syntax and the data itself (user - The `electron` packages now places its application files in `$out/libexec/electron` instead of `$out/lib/electron`. Packages using electron-builder will fail to build and need to be adjusted by changing `lib` to `libexec`. - `teleport` has been upgraded from major version 12 to major version 14. Please see upstream [upgrade instructions](https://goteleport.com/docs/management/operations/upgrading/) and release notes for versions [13](https://goteleport.com/docs/changelog/#1300-050823) and [14](https://goteleport.com/docs/changelog/#1400-092023). Note that Teleport does not officially support upgrades across more than one major version at a time. If you're running Teleport server components, it is recommended to first upgrade to an intermediate 13.x version by setting `services.teleport.package = pkgs.teleport_13`. Afterwards, this option can be removed to upgrade to the default version (14). + +- The Linux kernel module `msr` (see [`msr(4)`](https://man7.org/linux/man-pages/man4/msr.4.html)), which provides an interface to read and write the model-specific registers (MSRs) of an x86 CPU, can now be configured via `hardware.cpu.x86.msr`. diff --git a/nixpkgs/nixos/lib/test-driver/test_driver/machine.py b/nixpkgs/nixos/lib/test-driver/test_driver/machine.py index b1688cd3b64f..529de41d892a 100644 --- a/nixpkgs/nixos/lib/test-driver/test_driver/machine.py +++ b/nixpkgs/nixos/lib/test-driver/test_driver/machine.py @@ -19,6 +19,8 @@ from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple from test_driver.logger import rootlog +from .qmp import QMPSession + CHAR_TO_KEY = { "A": "shift-a", "N": "shift-n", @@ -144,6 +146,7 @@ class StartCommand: def cmd( self, monitor_socket_path: Path, + qmp_socket_path: Path, shell_socket_path: Path, allow_reboot: bool = False, ) -> str: @@ -167,6 +170,7 @@ class StartCommand: return ( f"{self._cmd}" + f" -qmp unix:{qmp_socket_path},server=on,wait=off" f" -monitor unix:{monitor_socket_path}" f" -chardev socket,id=shell,path={shell_socket_path}" f"{qemu_opts}" @@ -194,11 +198,14 @@ class StartCommand: state_dir: Path, shared_dir: Path, monitor_socket_path: Path, + qmp_socket_path: Path, shell_socket_path: Path, allow_reboot: bool, ) -> subprocess.Popen: return subprocess.Popen( - self.cmd(monitor_socket_path, shell_socket_path, allow_reboot), + self.cmd( + monitor_socket_path, qmp_socket_path, shell_socket_path, allow_reboot + ), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, @@ -309,6 +316,7 @@ class Machine: shared_dir: Path state_dir: Path monitor_path: Path + qmp_path: Path shell_path: Path start_command: StartCommand @@ -317,6 +325,7 @@ class Machine: process: Optional[subprocess.Popen] pid: Optional[int] monitor: Optional[socket.socket] + qmp_client: Optional[QMPSession] shell: Optional[socket.socket] serial_thread: Optional[threading.Thread] @@ -352,6 +361,7 @@ class Machine: self.state_dir = self.tmp_dir / f"vm-state-{self.name}" self.monitor_path = self.state_dir / "monitor" + self.qmp_path = self.state_dir / "qmp" self.shell_path = self.state_dir / "shell" if (not self.keep_vm_state) and self.state_dir.exists(): self.cleanup_statedir() @@ -360,6 +370,7 @@ class Machine: self.process = None self.pid = None self.monitor = None + self.qmp_client = None self.shell = None self.serial_thread = None @@ -1112,11 +1123,13 @@ class Machine: self.state_dir, self.shared_dir, self.monitor_path, + self.qmp_path, self.shell_path, allow_reboot, ) self.monitor, _ = monitor_socket.accept() self.shell, _ = shell_socket.accept() + self.qmp_client = QMPSession.from_path(self.qmp_path) # Store last serial console lines for use # of wait_for_console_text diff --git a/nixpkgs/nixos/lib/test-driver/test_driver/qmp.py b/nixpkgs/nixos/lib/test-driver/test_driver/qmp.py new file mode 100644 index 000000000000..62ca6d7d5b80 --- /dev/null +++ b/nixpkgs/nixos/lib/test-driver/test_driver/qmp.py @@ -0,0 +1,98 @@ +import json +import logging +import os +import socket +from collections.abc import Iterator +from pathlib import Path +from queue import Queue +from typing import Any + +logger = logging.getLogger(__name__) + + +class QMPAPIError(RuntimeError): + def __init__(self, message: dict[str, Any]): + assert "error" in message, "Not an error message!" + try: + self.class_name = message["class"] + self.description = message["desc"] + # NOTE: Some errors can occur before the Server is able to read the + # id member; in these cases the id member will not be part of the + # error response, even if provided by the client. + self.transaction_id = message.get("id") + except KeyError: + raise RuntimeError("Malformed QMP API error response") + + def __str__(self) -> str: + return f"<QMP API error related to transaction {self.transaction_id} [{self.class_name}]: {self.description}>" + + +class QMPSession: + def __init__(self, sock: socket.socket) -> None: + self.sock = sock + self.results: Queue[dict[str, str]] = Queue() + self.pending_events: Queue[dict[str, Any]] = Queue() + self.reader = sock.makefile("r") + self.writer = sock.makefile("w") + # Make the reader non-blocking so we can kind of select on it. + os.set_blocking(self.reader.fileno(), False) + hello = self._wait_for_new_result() + logger.debug(f"Got greeting from QMP API: {hello}") + # The greeting message format is: + # { "QMP": { "version": json-object, "capabilities": json-array } } + assert "QMP" in hello, f"Unexpected result: {hello}" + self.send("qmp_capabilities") + + @classmethod + def from_path(cls, path: Path) -> "QMPSession": + sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + sock.connect(str(path)) + return cls(sock) + + def __del__(self) -> None: + self.sock.close() + + def _wait_for_new_result(self) -> dict[str, str]: + assert self.results.empty(), "Results set is not empty, missed results!" + while self.results.empty(): + self.read_pending_messages() + return self.results.get() + + def read_pending_messages(self) -> None: + line = self.reader.readline() + if not line: + return + evt_or_result = json.loads(line) + logger.debug(f"Received a message: {evt_or_result}") + + # It's a result + if "return" in evt_or_result or "QMP" in evt_or_result: + self.results.put(evt_or_result) + # It's an event + elif "event" in evt_or_result: + self.pending_events.put(evt_or_result) + else: + raise QMPAPIError(evt_or_result) + + def wait_for_event(self, timeout: int = 10) -> dict[str, Any]: + while self.pending_events.empty(): + self.read_pending_messages() + + return self.pending_events.get(timeout=timeout) + + def events(self, timeout: int = 10) -> Iterator[dict[str, Any]]: + while not self.pending_events.empty(): + yield self.pending_events.get(timeout=timeout) + + def send(self, cmd: str, args: dict[str, str] = {}) -> dict[str, str]: + self.read_pending_messages() + assert self.results.empty(), "Results set is not empty, missed results!" + data: dict[str, Any] = dict(execute=cmd) + if args != {}: + data["arguments"] = args + + logger.debug(f"Sending {data} to QMP...") + json.dump(data, self.writer) + self.writer.write("\n") + self.writer.flush() + return self._wait_for_new_result() diff --git a/nixpkgs/nixos/modules/hardware/cpu/x86-msr.nix b/nixpkgs/nixos/modules/hardware/cpu/x86-msr.nix new file mode 100644 index 000000000000..554bec1b7db1 --- /dev/null +++ b/nixpkgs/nixos/modules/hardware/cpu/x86-msr.nix @@ -0,0 +1,91 @@ +{ lib +, config +, options +, ... +}: +let + inherit (builtins) hasAttr; + inherit (lib) mkIf mdDoc; + cfg = config.hardware.cpu.x86.msr; + opt = options.hardware.cpu.x86.msr; + defaultGroup = "msr"; + isDefaultGroup = cfg.group == defaultGroup; + set = "to set for devices of the `msr` kernel subsystem."; + + # Generates `foo=bar` parameters to pass to the kernel. + # If `module = baz` is passed, generates `baz.foo=bar`. + # Adds double quotes on demand to handle `foo="bar baz"`. + kernelParam = { module ? null }: name: value: + assert lib.asserts.assertMsg (!lib.strings.hasInfix "=" name) "kernel parameter cannot have '=' in name"; + let + key = (if module == null then "" else module + ".") + name; + valueString = lib.generators.mkValueStringDefault {} value; + quotedValueString = if lib.strings.hasInfix " " valueString + then lib.strings.escape ["\""] valueString + else valueString; + in "${key}=${quotedValueString}"; + msrKernelParam = kernelParam { module = "msr"; }; +in +{ + options.hardware.cpu.x86.msr = with lib.options; with lib.types; { + enable = mkEnableOption (mdDoc "the `msr` (Model-Specific Registers) kernel module and configure `udev` rules for its devices (usually `/dev/cpu/*/msr`)"); + owner = mkOption { + type = str; + default = "root"; + example = "nobody"; + description = mdDoc "Owner ${set}"; + }; + group = mkOption { + type = str; + default = defaultGroup; + example = "nobody"; + description = mdDoc "Group ${set}"; + }; + mode = mkOption { + type = str; + default = "0640"; + example = "0660"; + description = mdDoc "Mode ${set}"; + }; + settings = mkOption { + type = submodule { + freeformType = attrsOf (oneOf [ bool int str ]); + options.allow-writes = mkOption { + type = nullOr (enum ["on" "off"]); + default = null; + description = "Whether to allow writes to MSRs (`\"on\"`) or not (`\"off\"`)."; + }; + }; + default = {}; + description = "Parameters for the `msr` kernel module."; + }; + }; + + config = mkIf cfg.enable { + assertions = [ + { + assertion = hasAttr cfg.owner config.users.users; + message = "Owner '${cfg.owner}' set in `${opt.owner}` is not configured via `${options.users.users}.\"${cfg.owner}\"`."; + } + { + assertion = isDefaultGroup || (hasAttr cfg.group config.users.groups); + message = "Group '${cfg.group}' set in `${opt.group}` is not configured via `${options.users.groups}.\"${cfg.group}\"`."; + } + ]; + + boot = { + kernelModules = [ "msr" ]; + kernelParams = lib.attrsets.mapAttrsToList msrKernelParam (lib.attrsets.filterAttrs (_: value: value != null) cfg.settings); + }; + + users.groups.${cfg.group} = mkIf isDefaultGroup { }; + + services.udev.extraRules = '' + SUBSYSTEM=="msr", OWNER="${cfg.owner}", GROUP="${cfg.group}", MODE="${cfg.mode}" + ''; + }; + + meta = with lib; { + maintainers = with maintainers; [ lorenzleutgeb ]; + }; +} diff --git a/nixpkgs/nixos/modules/module-list.nix b/nixpkgs/nixos/modules/module-list.nix index a9d6bf817d49..16541be4e120 100644 --- a/nixpkgs/nixos/modules/module-list.nix +++ b/nixpkgs/nixos/modules/module-list.nix @@ -55,6 +55,7 @@ ./hardware/cpu/amd-sev.nix ./hardware/cpu/intel-microcode.nix ./hardware/cpu/intel-sgx.nix + ./hardware/cpu/x86-msr.nix ./hardware/decklink.nix ./hardware/device-tree.nix ./hardware/digitalbitbox.nix @@ -520,6 +521,7 @@ ./services/hardware/hddfancontrol.nix ./services/hardware/illum.nix ./services/hardware/interception-tools.nix + ./services/hardware/iptsd.nix ./services/hardware/irqbalance.nix ./services/hardware/joycond.nix ./services/hardware/kanata.nix @@ -558,6 +560,7 @@ ./services/home-automation/esphome.nix ./services/home-automation/evcc.nix ./services/home-automation/home-assistant.nix + ./services/home-automation/homeassistant-satellite.nix ./services/home-automation/zigbee2mqtt.nix ./services/logging/SystemdJournal2Gelf.nix ./services/logging/awstats.nix @@ -724,6 +727,7 @@ ./services/misc/ripple-data-api.nix ./services/misc/rippled.nix ./services/misc/rmfakecloud.nix + ./services/misc/rkvm.nix ./services/misc/rshim.nix ./services/misc/safeeyes.nix ./services/misc/sdrplay.nix @@ -735,6 +739,7 @@ ./services/misc/soft-serve.nix ./services/misc/sonarr.nix ./services/misc/sourcehut + ./services/misc/spice-autorandr.nix ./services/misc/spice-vdagentd.nix ./services/misc/spice-webdavd.nix ./services/misc/ssm-agent.nix diff --git a/nixpkgs/nixos/modules/security/acme/default.nix b/nixpkgs/nixos/modules/security/acme/default.nix index 92bed172f452..f8e17bc71ee1 100644 --- a/nixpkgs/nixos/modules/security/acme/default.nix +++ b/nixpkgs/nixos/modules/security/acme/default.nix @@ -592,7 +592,7 @@ let description = lib.mdDoc '' Key type to use for private keys. For an up to date list of supported values check the --key-type option - at <https://go-acme.github.io/lego/usage/cli/#usage>. + at <https://go-acme.github.io/lego/usage/cli/options/>. ''; }; diff --git a/nixpkgs/nixos/modules/services/audio/wyoming/openwakeword.nix b/nixpkgs/nixos/modules/services/audio/wyoming/openwakeword.nix index e1993407dad1..06b7dd585fda 100644 --- a/nixpkgs/nixos/modules/services/audio/wyoming/openwakeword.nix +++ b/nixpkgs/nixos/modules/services/audio/wyoming/openwakeword.nix @@ -136,7 +136,7 @@ in ProtectKernelTunables = true; ProtectControlGroups = true; ProtectProc = "invisible"; - ProcSubset = "pid"; + ProcSubset = "all"; # reads /proc/cpuinfo RestrictAddressFamilies = [ "AF_INET" "AF_INET6" diff --git a/nixpkgs/nixos/modules/services/hardware/iptsd.nix b/nixpkgs/nixos/modules/services/hardware/iptsd.nix new file mode 100644 index 000000000000..8af0a6d6bbe1 --- /dev/null +++ b/nixpkgs/nixos/modules/services/hardware/iptsd.nix @@ -0,0 +1,53 @@ +{ config, lib, pkgs, ... }: + +let + cfg = config.services.iptsd; + format = pkgs.formats.ini { }; + configFile = format.generate "iptsd.conf" cfg.config; +in { + options.services.iptsd = { + enable = lib.mkEnableOption (lib.mdDoc "the userspace daemon for Intel Precise Touch & Stylus"); + + config = lib.mkOption { + default = { }; + description = lib.mdDoc '' + Configuration for IPTSD. See the + [reference configuration](https://github.com/linux-surface/iptsd/blob/master/etc/iptsd.conf) + for available options and defaults. + ''; + type = lib.types.submodule { + freeformType = format.type; + options = { + Touch = { + DisableOnPalm = lib.mkOption { + default = false; + description = lib.mdDoc "Ignore all touch inputs if a palm was registered on the display."; + type = lib.types.bool; + }; + DisableOnStylus = lib.mkOption { + default = false; + description = lib.mdDoc "Ignore all touch inputs if a stylus is in proximity."; + type = lib.types.bool; + }; + }; + Stylus = { + Disable = lib.mkOption { + default = false; + description = lib.mdDoc "Disables the stylus. No stylus data will be processed."; + type = lib.types.bool; + }; + }; + }; + }; + }; + }; + + config = lib.mkIf cfg.enable { + systemd.packages = [ pkgs.iptsd ]; + environment.etc."iptsd.conf".source = configFile; + systemd.services."iptsd@".restartTriggers = [ configFile ]; + services.udev.packages = [ pkgs.iptsd ]; + }; + + meta.maintainers = with lib.maintainers; [ dotlambda ]; +} diff --git a/nixpkgs/nixos/modules/services/hardware/throttled.nix b/nixpkgs/nixos/modules/services/hardware/throttled.nix index afca24d976e1..9fa495886119 100644 --- a/nixpkgs/nixos/modules/services/hardware/throttled.nix +++ b/nixpkgs/nixos/modules/services/hardware/throttled.nix @@ -29,8 +29,7 @@ in { # Kernel 5.9 spams warnings whenever userspace writes to CPU MSRs. # See https://github.com/erpalma/throttled/issues/215 - boot.kernelParams = - optional (versionAtLeast config.boot.kernelPackages.kernel.version "5.9") - "msr.allow_writes=on"; + hardware.cpu.x86.msr.settings.allow-writes = + mkIf (versionAtLeast config.boot.kernelPackages.kernel.version "5.9") "on"; }; } diff --git a/nixpkgs/nixos/modules/services/hardware/tlp.nix b/nixpkgs/nixos/modules/services/hardware/tlp.nix index cad510e571cb..0b7f98ab6a6d 100644 --- a/nixpkgs/nixos/modules/services/hardware/tlp.nix +++ b/nixpkgs/nixos/modules/services/hardware/tlp.nix @@ -47,7 +47,7 @@ in ###### implementation config = mkIf cfg.enable { - boot.kernelModules = [ "msr" ]; + hardware.cpu.x86.msr.enable = true; warnings = optional (cfg.extraConfig != "") '' Using config.services.tlp.extraConfig is deprecated and will become unsupported in a future release. Use config.services.tlp.settings instead. diff --git a/nixpkgs/nixos/modules/services/hardware/undervolt.nix b/nixpkgs/nixos/modules/services/hardware/undervolt.nix index 944777475401..258f09bbab09 100644 --- a/nixpkgs/nixos/modules/services/hardware/undervolt.nix +++ b/nixpkgs/nixos/modules/services/hardware/undervolt.nix @@ -159,7 +159,7 @@ in }; config = mkIf cfg.enable { - boot.kernelModules = [ "msr" ]; + hardware.cpu.x86.msr.enable = true; environment.systemPackages = [ cfg.package ]; diff --git a/nixpkgs/nixos/modules/services/home-automation/homeassistant-satellite.nix b/nixpkgs/nixos/modules/services/home-automation/homeassistant-satellite.nix new file mode 100644 index 000000000000..e3f0617cf01c --- /dev/null +++ b/nixpkgs/nixos/modules/services/home-automation/homeassistant-satellite.nix @@ -0,0 +1,225 @@ +{ config +, lib +, pkgs +, ... +}: + +let + cfg = config.services.homeassistant-satellite; + + inherit (lib) + escapeShellArg + escapeShellArgs + mkOption + mdDoc + mkEnableOption + mkIf + mkPackageOptionMD + types + ; + + inherit (builtins) + toString + ; + + # override the package with the relevant vad dependencies + package = cfg.package.overridePythonAttrs (oldAttrs: { + propagatedBuildInputs = oldAttrs.propagatedBuildInputs + ++ lib.optional (cfg.vad == "webrtcvad") cfg.package.optional-dependencies.webrtc + ++ lib.optional (cfg.vad == "silero") cfg.package.optional-dependencies.silerovad + ++ lib.optional (cfg.pulseaudio.enable) cfg.package.optional-dependencies.pulseaudio; + }); + +in + +{ + meta.buildDocsInSandbox = false; + + options.services.homeassistant-satellite = with types; { + enable = mkEnableOption (mdDoc "Home Assistant Satellite"); + + package = mkPackageOptionMD pkgs "homeassistant-satellite" { }; + + user = mkOption { + type = str; + example = "alice"; + description = mdDoc '' + User to run homeassistant-satellite under. + ''; + }; + + group = mkOption { + type = str; + default = "users"; + description = mdDoc '' + Group to run homeassistant-satellite under. + ''; + }; + + host = mkOption { + type = str; + example = "home-assistant.local"; + description = mdDoc '' + Hostname on which your Home Assistant instance can be reached. + ''; + }; + + port = mkOption { + type = port; + example = 8123; + description = mdDoc '' + Port on which your Home Assistance can be reached. + ''; + apply = toString; + }; + + protocol = mkOption { + type = enum [ "http" "https" ]; + default = "http"; + example = "https"; + description = mdDoc '' + The transport protocol used to connect to Home Assistant. + ''; + }; + + tokenFile = mkOption { + type = path; + example = "/run/keys/hass-token"; + description = mdDoc '' + Path to a file containing a long-lived access token for your Home Assistant instance. + ''; + apply = escapeShellArg; + }; + + sounds = { + awake = mkOption { + type = nullOr str; + default = null; + description = mdDoc '' + Audio file to play when the wake word is detected. + ''; + }; + + done = mkOption { + type = nullOr str; + default = null; + description = mdDoc '' + Audio file to play when the voice command is done. + ''; + }; + }; + + vad = mkOption { + type = enum [ "disabled" "webrtcvad" "silero" ]; + default = "disabled"; + example = "silero"; + description = mdDoc '' + Voice activity detection model. With `disabled` sound will be transmitted continously. + ''; + }; + + pulseaudio = { + enable = mkEnableOption "recording/playback via PulseAudio or PipeWire"; + + socket = mkOption { + type = nullOr str; + default = null; + example = "/run/user/1000/pulse/native"; + description = mdDoc '' + Path or hostname to connect with the PulseAudio server. + ''; + }; + + duckingVolume = mkOption { + type = nullOr float; + default = null; + example = 0.4; + description = mdDoc '' + Reduce output volume (between 0 and 1) to this percentage value while recording. + ''; + }; + + echoCancellation = mkEnableOption "acoustic echo cancellation"; + }; + + extraArgs = mkOption { + type = listOf str; + default = [ ]; + description = mdDoc '' + Extra arguments to pass to the commandline. + ''; + apply = escapeShellArgs; + }; + }; + + config = mkIf cfg.enable { + systemd.services."homeassistant-satellite" = { + description = "Home Assistant Satellite"; + after = [ + "network-online.target" + ]; + wants = [ + "network-online.target" + ]; + wantedBy = [ + "multi-user.target" + ]; + path = with pkgs; [ + ffmpeg-headless + ] ++ lib.optionals (!cfg.pulseaudio.enable) [ + alsa-utils + ]; + serviceConfig = { + User = cfg.user; + Group = cfg.group; + # https://github.com/rhasspy/hassio-addons/blob/master/assist_microphone/rootfs/etc/s6-overlay/s6-rc.d/assist_microphone/run + ExecStart = '' + ${package}/bin/homeassistant-satellite \ + --host ${cfg.host} \ + --port ${cfg.port} \ + --protocol ${cfg.protocol} \ + --token-file ${cfg.tokenFile} \ + --vad ${cfg.vad} \ + ${lib.optionalString cfg.pulseaudio.enable "--pulseaudio"}${lib.optionalString (cfg.pulseaudio.socket != null) "=${cfg.pulseaudio.socket}"} \ + ${lib.optionalString (cfg.pulseaudio.enable && cfg.pulseaudio.duckingVolume != null) "--ducking-volume=${toString cfg.pulseaudio.duckingVolume}"} \ + ${lib.optionalString (cfg.pulseaudio.enable && cfg.pulseaudio.echoCancellation) "--echo-cancel"} \ + ${lib.optionalString (cfg.sounds.awake != null) "--awake-sound=${toString cfg.sounds.awake}"} \ + ${lib.optionalString (cfg.sounds.done != null) "--done-sound=${toString cfg.sounds.done}"} \ + ${cfg.extraArgs} + ''; + CapabilityBoundingSet = ""; + DeviceAllow = ""; + DevicePolicy = "closed"; + LockPersonality = true; + MemoryDenyWriteExecute = false; # onnxruntime/capi/onnxruntime_pybind11_state.so: cannot enable executable stack as shared object requires: Operation not permitted + PrivateDevices = true; + PrivateUsers = true; + ProtectHome = false; # Would deny access to local pulse/pipewire server + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectControlGroups = true; + ProtectProc = "invisible"; + ProcSubset = "all"; # Error in cpuinfo: failed to parse processor information from /proc/cpuinfo + Restart = "always"; + RestrictAddressFamilies = [ + "AF_INET" + "AF_INET6" + "AF_UNIX" + ]; + RestrictNamespaces = true; + RestrictRealtime = true; + SupplementaryGroups = [ + "audio" + ]; + SystemCallArchitectures = "native"; + SystemCallFilter = [ + "@system-service" + "~@privileged" + ]; + UMask = "0077"; + }; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/forgejo.nix b/nixpkgs/nixos/modules/services/misc/forgejo.nix index f26658b7bcb4..b2920981efbd 100644 --- a/nixpkgs/nixos/modules/services/misc/forgejo.nix +++ b/nixpkgs/nixos/modules/services/misc/forgejo.nix @@ -428,6 +428,17 @@ in ]; }; + # Work around 'pq: permission denied for schema public' with postgres v15, until a + # solution for `services.postgresql.ensureUsers` is found. + # See https://github.com/NixOS/nixpkgs/issues/216989 + systemd.services.postgresql.postStart = lib.mkIf ( + usePostgresql + && cfg.database.createDatabase + && lib.strings.versionAtLeast config.services.postgresql.package.version "15.0" + ) (lib.mkAfter '' + $PSQL -tAc 'ALTER DATABASE "${cfg.database.name}" OWNER TO "${cfg.database.user}";' + ''); + services.mysql = optionalAttrs (useMysql && cfg.database.createDatabase) { enable = mkDefault true; package = mkDefault pkgs.mariadb; diff --git a/nixpkgs/nixos/modules/services/misc/rkvm.nix b/nixpkgs/nixos/modules/services/misc/rkvm.nix new file mode 100644 index 000000000000..582e8511ed96 --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/rkvm.nix @@ -0,0 +1,164 @@ +{ options, config, pkgs, lib, ... }: + +with lib; +let + opt = options.services.rkvm; + cfg = config.services.rkvm; + toml = pkgs.formats.toml { }; +in +{ + meta.maintainers = with maintainers; [ ckie ]; + + options.services.rkvm = { + enable = mkOption { + default = cfg.server.enable || cfg.client.enable; + defaultText = literalExpression "config.${opt.server.enable} || config.${opt.client.enable}"; + type = types.bool; + description = mdDoc '' + Whether to enable rkvm, a Virtual KVM switch for Linux machines. + ''; + }; + + package = mkPackageOption pkgs "rkvm" { }; + + server = { + enable = mkEnableOption "the rkvm server daemon (input transmitter)"; + + settings = mkOption { + type = types.submodule + { + freeformType = toml.type; + options = { + listen = mkOption { + type = types.str; + default = "0.0.0.0:5258"; + description = mdDoc '' + An internet socket address to listen on, either IPv4 or IPv6. + ''; + }; + + switch-keys = mkOption { + type = types.listOf types.str; + default = [ "left-alt" "left-ctrl" ]; + description = mdDoc '' + A key list specifying a host switch combination. + + _A list of key names is available in <https://github.com/htrefil/rkvm/blob/master/switch-keys.md>._ + ''; + }; + + certificate = mkOption { + type = types.path; + default = "/etc/rkvm/certificate.pem"; + description = mdDoc '' + TLS certificate path. + + ::: {.note} + This should be generated with {command}`rkvm-certificate-gen`. + ::: + ''; + }; + + key = mkOption { + type = types.path; + default = "/etc/rkvm/key.pem"; + description = mdDoc '' + TLS key path. + + ::: {.note} + This should be generated with {command}`rkvm-certificate-gen`. + ::: + ''; + }; + + password = mkOption { + type = types.str; + description = mdDoc '' + Shared secret token to authenticate the client. + Make sure this matches your client's config. + ''; + }; + }; + }; + + default = { }; + description = mdDoc "Structured server daemon configuration"; + }; + }; + + client = { + enable = mkEnableOption "the rkvm client daemon (input receiver)"; + + settings = mkOption { + type = types.submodule + { + freeformType = toml.type; + options = { + server = mkOption { + type = types.str; + example = "192.168.0.123:5258"; + description = mdDoc '' + An RKVM server's internet socket address, either IPv4 or IPv6. + ''; + }; + + certificate = mkOption { + type = types.path; + default = "/etc/rkvm/certificate.pem"; + description = mdDoc '' + TLS ceritficate path. + + ::: {.note} + This should be generated with {command}`rkvm-certificate-gen`. + ::: + ''; + }; + + password = mkOption { + type = types.str; + description = mdDoc '' + Shared secret token to authenticate the client. + Make sure this matches your server's config. + ''; + }; + }; + }; + + default = {}; + description = mdDoc "Structured client daemon configuration"; + }; + }; + + }; + + config = mkIf cfg.enable { + environment.systemPackages = [ cfg.package ]; + + systemd.services = + let + mkBase = component: { + description = "RKVM ${component}"; + wantedBy = [ "multi-user.target" ]; + after = { + server = [ "network.target" ]; + client = [ "network-online.target" ]; + }.${component}; + wants = { + server = [ ]; + client = [ "network-online.target" ]; + }.${component}; + serviceConfig = { + ExecStart = "${cfg.package}/bin/rkvm-${component} ${toml.generate "rkvm-${component}.toml" cfg.${component}.settings}"; + Restart = "always"; + RestartSec = 5; + Type = "simple"; + }; + }; + in + { + rkvm-server = mkIf cfg.server.enable (mkBase "server"); + rkvm-client = mkIf cfg.client.enable (mkBase "client"); + }; + }; + +} diff --git a/nixpkgs/nixos/modules/services/misc/spice-autorandr.nix b/nixpkgs/nixos/modules/services/misc/spice-autorandr.nix new file mode 100644 index 000000000000..8437441c752a --- /dev/null +++ b/nixpkgs/nixos/modules/services/misc/spice-autorandr.nix @@ -0,0 +1,26 @@ +{ config, pkgs, lib, ... }: + +let + cfg = config.services.spice-autorandr; +in +{ + options = { + services.spice-autorandr = { + enable = lib.mkEnableOption (lib.mdDoc "spice-autorandr service that will automatically resize display to match SPICE client window size."); + package = lib.mkPackageOptionMD pkgs "spice-autorandr" { }; + }; + }; + + config = lib.mkIf cfg.enable { + environment.systemPackages = [ cfg.package ]; + + systemd.user.services.spice-autorandr = { + wantedBy = [ "default.target" ]; + after = [ "spice-vdagentd.service" ]; + serviceConfig = { + ExecStart = "${cfg.package}/bin/spice-autorandr"; + Restart = "on-failure"; + }; + }; + }; +} diff --git a/nixpkgs/nixos/modules/services/misc/xmrig.nix b/nixpkgs/nixos/modules/services/misc/xmrig.nix index d2aa3df45d53..05e63c773205 100644 --- a/nixpkgs/nixos/modules/services/misc/xmrig.nix +++ b/nixpkgs/nixos/modules/services/misc/xmrig.nix @@ -52,7 +52,7 @@ with lib; }; config = mkIf cfg.enable { - boot.kernelModules = [ "msr" ]; + hardware.cpu.x86.msr.enable = true; systemd.services.xmrig = { wantedBy = [ "multi-user.target" ]; diff --git a/nixpkgs/nixos/modules/system/boot/initrd-network.nix b/nixpkgs/nixos/modules/system/boot/initrd-network.nix index 1d95742face3..5bf38b6fa200 100644 --- a/nixpkgs/nixos/modules/system/boot/initrd-network.nix +++ b/nixpkgs/nixos/modules/system/boot/initrd-network.nix @@ -80,7 +80,7 @@ in }; boot.initrd.network.udhcpc.enable = mkOption { - default = config.networking.useDHCP; + default = config.networking.useDHCP && !config.boot.initrd.systemd.enable; defaultText = "networking.useDHCP"; type = types.bool; description = lib.mdDoc '' diff --git a/nixpkgs/nixos/modules/system/boot/networkd.nix b/nixpkgs/nixos/modules/system/boot/networkd.nix index cbb521f0b037..4be040927540 100644 --- a/nixpkgs/nixos/modules/system/boot/networkd.nix +++ b/nixpkgs/nixos/modules/system/boot/networkd.nix @@ -2985,10 +2985,10 @@ in stage2Config (mkIf config.boot.initrd.systemd.enable { assertions = [{ - assertion = config.boot.initrd.network.udhcpc.extraArgs == []; + assertion = !config.boot.initrd.network.udhcpc.enable && config.boot.initrd.network.udhcpc.extraArgs == []; message = '' - boot.initrd.network.udhcpc.extraArgs is not supported when - boot.initrd.systemd.enable is enabled + systemd stage 1 networking does not support 'boot.initrd.network.udhcpc'. Configure + DHCP with 'networking.*' options or with 'boot.initrd.systemd.network' options. ''; }]; diff --git a/nixpkgs/nixos/modules/system/boot/systemd.nix b/nixpkgs/nixos/modules/system/boot/systemd.nix index 8e38072b4c6d..68a8c1f37ed5 100644 --- a/nixpkgs/nixos/modules/system/boot/systemd.nix +++ b/nixpkgs/nixos/modules/system/boot/systemd.nix @@ -575,7 +575,7 @@ in system.requiredKernelConfig = map config.lib.kernelConfig.isEnabled [ "DEVTMPFS" "CGROUPS" "INOTIFY_USER" "SIGNALFD" "TIMERFD" "EPOLL" "NET" "SYSFS" "PROC_FS" "FHANDLE" "CRYPTO_USER_API_HASH" "CRYPTO_HMAC" - "CRYPTO_SHA256" "DMIID" "AUTOFS4_FS" "TMPFS_POSIX_ACL" + "CRYPTO_SHA256" "DMIID" "AUTOFS_FS" "TMPFS_POSIX_ACL" "TMPFS_XATTR" "SECCOMP" ]; diff --git a/nixpkgs/nixos/modules/system/boot/systemd/repart.nix b/nixpkgs/nixos/modules/system/boot/systemd/repart.nix index 2431c68ea17b..5ac2ace56ba0 100644 --- a/nixpkgs/nixos/modules/system/boot/systemd/repart.nix +++ b/nixpkgs/nixos/modules/system/boot/systemd/repart.nix @@ -74,6 +74,15 @@ in }; config = lib.mkIf (cfg.enable || initrdCfg.enable) { + assertions = [ + { + assertion = initrdCfg.enable -> config.boot.initrd.systemd.enable; + message = '' + 'boot.initrd.systemd.repart.enable' requires 'boot.initrd.systemd.enable' to be enabled. + ''; + } + ]; + boot.initrd.systemd = lib.mkIf initrdCfg.enable { additionalUpstreamUnits = [ "systemd-repart.service" diff --git a/nixpkgs/nixos/modules/virtualisation/lxc-container.nix b/nixpkgs/nixos/modules/virtualisation/lxc-container.nix index c40c7bee1886..1034c699629d 100644 --- a/nixpkgs/nixos/modules/virtualisation/lxc-container.nix +++ b/nixpkgs/nixos/modules/virtualisation/lxc-container.nix @@ -66,7 +66,7 @@ in { system.build.installBootLoader = pkgs.writeScript "install-lxd-sbin-init.sh" '' #!${pkgs.runtimeShell} - ln -fs "$1/init" /sbin/init + ${pkgs.coreutils}/bin/ln -fs "$1/init" /sbin/init ''; systemd.additionalUpstreamSystemUnits = lib.mkIf cfg.nestedContainer ["systemd-udev-trigger.service"]; diff --git a/nixpkgs/nixos/release-combined.nix b/nixpkgs/nixos/release-combined.nix index 6c5c98afa4d0..20bcb6802881 100644 --- a/nixpkgs/nixos/release-combined.nix +++ b/nixpkgs/nixos/release-combined.nix @@ -79,6 +79,7 @@ in rec { (onFullSupported "nixos.tests.firewall") (onFullSupported "nixos.tests.fontconfig-default-fonts") + (onFullSupported "nixos.tests.gitlab") (onFullSupported "nixos.tests.gnome") (onFullSupported "nixos.tests.gnome-xorg") (onSystems ["x86_64-linux"] "nixos.tests.hibernate") diff --git a/nixpkgs/nixos/tests/all-tests.nix b/nixpkgs/nixos/tests/all-tests.nix index ef98efd7dbca..1c8ee32428ea 100644 --- a/nixpkgs/nixos/tests/all-tests.nix +++ b/nixpkgs/nixos/tests/all-tests.nix @@ -699,6 +699,7 @@ in { restartByActivationScript = handleTest ./restart-by-activation-script.nix {}; restic = handleTest ./restic.nix {}; retroarch = handleTest ./retroarch.nix {}; + rkvm = handleTest ./rkvm {}; robustirc-bridge = handleTest ./robustirc-bridge.nix {}; roundcube = handleTest ./roundcube.nix {}; rshim = handleTest ./rshim.nix {}; diff --git a/nixpkgs/nixos/tests/mosquitto.nix b/nixpkgs/nixos/tests/mosquitto.nix index 8eca4f259225..c0980b23e78f 100644 --- a/nixpkgs/nixos/tests/mosquitto.nix +++ b/nixpkgs/nixos/tests/mosquitto.nix @@ -4,7 +4,6 @@ let port = 1888; tlsPort = 1889; anonPort = 1890; - bindTestPort = 18910; password = "VERY_secret"; hashedPassword = "$7$101$/WJc4Mp+I+uYE9sR$o7z9rD1EYXHPwEP5GqQj6A7k4W1yVbePlb8TqNcuOLV9WNCiDgwHOB0JHC1WCtdkssqTBduBNUnUGd6kmZvDSw=="; topic = "test/foo"; @@ -127,10 +126,6 @@ in { }; }; } - { - settings.bind_interface = "eth0"; - port = bindTestPort; - } ]; }; }; @@ -140,8 +135,6 @@ in { }; testScript = '' - import json - def mosquitto_cmd(binary, user, topic, port): return ( "mosquitto_{} " @@ -174,27 +167,6 @@ in { start_all() server.wait_for_unit("mosquitto.service") - with subtest("bind_interface"): - addrs = dict() - for iface in json.loads(server.succeed("ip -json address show")): - for addr in iface['addr_info']: - # don't want to deal with multihoming here - assert addr['local'] not in addrs - addrs[addr['local']] = (iface['ifname'], addr['family']) - - # mosquitto grabs *one* random address per type for bind_interface - (has4, has6) = (False, False) - for line in server.succeed("ss -HlptnO sport = ${toString bindTestPort}").splitlines(): - items = line.split() - if "mosquitto" not in items[5]: continue - listener = items[3].rsplit(':', maxsplit=1)[0].strip('[]') - assert listener in addrs - assert addrs[listener][0] == "eth0" - has4 |= addrs[listener][1] == 'inet' - has6 |= addrs[listener][1] == 'inet6' - assert has4 - assert has6 - with subtest("check passwords"): client1.succeed(publish("-m test", "password_store")) client1.succeed(publish("-m test", "password_file")) diff --git a/nixpkgs/nixos/tests/rkvm/cert.pem b/nixpkgs/nixos/tests/rkvm/cert.pem new file mode 100644 index 000000000000..933efe520578 --- /dev/null +++ b/nixpkgs/nixos/tests/rkvm/cert.pem @@ -0,0 +1,18 @@ +-----BEGIN CERTIFICATE----- +MIIC3jCCAcagAwIBAgIUWW1hb9xdRtxAhA42jkS89goW9LUwDQYJKoZIhvcNAQEL +BQAwDzENMAsGA1UEAwwEcmt2bTAeFw0yMzA4MjIxOTI1NDlaFw0zMzA4MTkxOTI1 +NDlaMA8xDTALBgNVBAMMBHJrdm0wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQCuBsh0+LDXN4b2o/PJjzuiZ9Yv9Pz1Oho9WRiXtNIuHTRdBCcht/iu3PGF +ICIX+H3dqQOziGSCTAQGJD2p+1ik8d+boJbpa0oxXuHuomsMAT3mib3GpipQoBLP +KaEbWEsvQbr3RMx8WOtG4dmRQFzSVVtmAXyM0pNyisd4eUCplyIl9gsRJIvsO/0M +OkgOZW9XLfKiAWlZoyXEkBmPAshg3EkwQtmwxPA/NgWbAOW3zJKSChxnnGYiuIIu +R/wJ8OQXHP6boQLQGUhCWBKa1uK1gEBmV3Pj6uK8RzTkQq6/47F5sPa6VfqQYdyl +TCs9bSqHXZjqMBoiSp22uH6+Lh9RAgMBAAGjMjAwMA8GA1UdEQQIMAaHBAoAAAEw +HQYDVR0OBBYEFEh9HEsnY3dfNKVyPWDbwfR0qHopMA0GCSqGSIb3DQEBCwUAA4IB +AQB/r+K20JqegUZ/kepPxIU95YY81aUUoxvLbu4EAgh8o46Fgm75qrTZPg4TaIZa +wtVejekrF+p3QVf0ErUblh/iCjTZPSzCmKHZt8cc9OwTH7bt3bx7heknzLDyIa5z +szAL+6241UggQ5n5NUGn5+xZHA7TMe47xAZPaRMlCQ/tp5pWFjH6WSSQSP5t4Ag9 +ObhY+uudFjmWi3QIBTr3iIscbWx7tD8cjus7PzM7+kszSDRV04xb6Ox8JzW9MKIN +GwgwVgs3zCuyqBmTGnR1og3aMk6VtlyZUYE78uuc+fMBxqoBZ0mykeOp0Tbzgtf7 +gPkYcQ6vonoQhuTXYj/NrY+b +-----END CERTIFICATE----- diff --git a/nixpkgs/nixos/tests/rkvm/default.nix b/nixpkgs/nixos/tests/rkvm/default.nix new file mode 100644 index 000000000000..22425948d8bf --- /dev/null +++ b/nixpkgs/nixos/tests/rkvm/default.nix @@ -0,0 +1,104 @@ +import ../make-test-python.nix ({ pkgs, ... }: +let + # Generated with + # + # nix shell .#rkvm --command "rkvm-certificate-gen --ip-addresses 10.0.0.1 cert.pem key.pem" + # + snakeoil-cert = ./cert.pem; + snakeoil-key = ./key.pem; +in +{ + name = "rkvm"; + + nodes = { + server = { pkgs, ... }: { + imports = [ ../common/user-account.nix ]; + + virtualisation.vlans = [ 1 ]; + + networking = { + useNetworkd = true; + useDHCP = false; + firewall.enable = false; + }; + + systemd.network.networks."01-eth1" = { + name = "eth1"; + networkConfig.Address = "10.0.0.1/24"; + }; + + services.getty.autologinUser = "alice"; + + services.rkvm.server = { + enable = true; + settings = { + certificate = snakeoil-cert; + key = snakeoil-key; + password = "snakeoil"; + switch-keys = [ "left-alt" "right-alt" ]; + }; + }; + }; + + client = { pkgs, ... }: { + imports = [ ../common/user-account.nix ]; + + virtualisation.vlans = [ 1 ]; + + networking = { + useNetworkd = true; + useDHCP = false; + firewall.enable = false; + }; + + systemd.network.networks."01-eth1" = { + name = "eth1"; + networkConfig.Address = "10.0.0.2/24"; + }; + + services.getty.autologinUser = "alice"; + + services.rkvm.client = { + enable = true; + settings = { + server = "10.0.0.1:5258"; + certificate = snakeoil-cert; + key = snakeoil-key; + password = "snakeoil"; + }; + }; + }; + }; + + testScript = '' + server.wait_for_unit("getty@tty1.service") + server.wait_until_succeeds("pgrep -f 'agetty.*tty1'") + server.wait_for_unit("rkvm-server") + server.wait_for_open_port(5258) + + client.wait_for_unit("getty@tty1.service") + client.wait_until_succeeds("pgrep -f 'agetty.*tty1'") + client.wait_for_unit("rkvm-client") + + server.sleep(1) + + # Switch to client + server.send_key("alt-alt_r", delay=0.2) + server.send_chars("echo 'hello client' > /tmp/test.txt\n") + + # Switch to server + server.send_key("alt-alt_r", delay=0.2) + server.send_chars("echo 'hello server' > /tmp/test.txt\n") + + server.sleep(1) + + client.systemctl("stop rkvm-client.service") + server.systemctl("stop rkvm-server.service") + + server_file = server.succeed("cat /tmp/test.txt") + assert server_file.strip() == "hello server" + + client_file = client.succeed("cat /tmp/test.txt") + assert client_file.strip() == "hello client" + ''; +}) diff --git a/nixpkgs/nixos/tests/rkvm/key.pem b/nixpkgs/nixos/tests/rkvm/key.pem new file mode 100644 index 000000000000..7197decff8d3 --- /dev/null +++ b/nixpkgs/nixos/tests/rkvm/key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCuBsh0+LDXN4b2 +o/PJjzuiZ9Yv9Pz1Oho9WRiXtNIuHTRdBCcht/iu3PGFICIX+H3dqQOziGSCTAQG +JD2p+1ik8d+boJbpa0oxXuHuomsMAT3mib3GpipQoBLPKaEbWEsvQbr3RMx8WOtG +4dmRQFzSVVtmAXyM0pNyisd4eUCplyIl9gsRJIvsO/0MOkgOZW9XLfKiAWlZoyXE +kBmPAshg3EkwQtmwxPA/NgWbAOW3zJKSChxnnGYiuIIuR/wJ8OQXHP6boQLQGUhC +WBKa1uK1gEBmV3Pj6uK8RzTkQq6/47F5sPa6VfqQYdylTCs9bSqHXZjqMBoiSp22 +uH6+Lh9RAgMBAAECggEABo2V1dBu5E51zsAiFCMdypdLZEyUNphvWC5h3oXowONz +pH8ICYfXyEnkma/kk2+ALy0dSRDn6/94dVIUX7Fpx0hJCcoJyhSysK+TJWfIonqX +ffYOMeFG8vicIgs+GFKs/hoPtB5LREbFkUqRj/EoWE6Y3aX3roaCwTZC8vaUk0OK +54gExcNXRwQtFmfM9BiPT76F2J641NVsddgKumrryMi605CgZ57OFfSYEena6T3t +JbQ1TKB3SH1LvSQIspyp56E3bjh8bcwSh72g88YxWZI9yarOesmyU+fXnmVqcBc+ +CiJDX3Te1C2GIkBiH3HZJo4P88aXrkJ7J8nub/812QKBgQDfCHjBy5uWzzbDnqZc +cllIyUqMHq1iY2/btdZQbz83maZhQhH2UL4Zvoa7qgMX7Ou5jn1xpDaMeXNaajGK +Fz66nmqQEUFX1i+2md2J8TeKD37yUJRdlrMiAc+RNp5wiOH9EI18g2m6h/nj3s/P +MdNyxsz+wqOiJT0sZatarKiFhQKBgQDHv+lPy4OPH1MeSv5vmv3Pa41O/CeiPy+T +gi6nEZayVRVog3zF9T6gNIHrZ1fdIppWPiPXv9fmC3s/IVEftLG6YC+MAfigYhiz +Iceoal0iJJ8DglzOhlKgHEnxEwENCz8aJxjpvbxHHcpvgXdBSEVfHvVqDkAFTsvF +JA5YTmqGXQKBgQCL6uqm2S7gq1o12p+PO4VbrjwAL3aiVLNl6Gtsxn2oSdIhDavr +FLhNukMYFA4gwlcXb5au5k/6TG7bd+dgNDj8Jkm/27NcgVgpe9mJojQvfo0rQvXw +yIvUd8JZ3SQEgTsU4X+Bb4eyp39TPwKrfxyh0qnj4QN6w1XfNmELX2nRaQKBgEq6 +a0ik9JTovSnKGKIcM/QTYow4HYO/a8cdnuJ13BDfb+DnwBg3BbTdr/UndmGOfnrh +SHuAk/7GMNePWVApQ4xcS61vV1p5GJB7hLxm/my1kp+3d4z0B5lKvAbqeywsFvFr +yxA3IWbhqEhLARh1Ny684EdLCXxy3Bzmvk8fFw8pAoGAGkt9pJC2wkk9fnJIHq+f +h/WnEO0YrGzYnVA+RyCNKrimRd+GylGHJ/Ev6PRZvMwyGE7RCB+fHVrrEcEJAcxL +SaOg5NA8cwrG+UpTQqi4gt6tCW87afVCyL6dC/E8giJlzI0LY9DnFGoVqYL0qJvm +Sj4SU0fyLsW/csOLd5T+Bf8= +-----END PRIVATE KEY----- |