From eb46c6c5c81695af475f7e1e416d05e51157fe60 Mon Sep 17 00:00:00 2001 From: Pierre Langlois Date: Sat, 10 Jul 2021 14:12:02 +0100 Subject: gnu: u-boot: Update to 2021.07. * gnu/packages/patches/u-boot-sifive-prevent-reloc-initrd-fdt.patch: New patch. * gnu/local.mk (dist_patch_DATA): Add it. * gnu/packages/bootloaders.scm (%u-boot-sifive-prevent-relocating-initrd-fdt): New variable. (u-boot): Update to 2021.07. [native-inputs]: Add python-pycryptodomex. (u-boot-2021.07): Delete variable. (u-boot-tools)[arguments]: Adapt 'patch phase, disable failing test_spl test. (u-boot-sifive-unmatched): Use default u-boot package. (u-boot-pinebook-pro-rk3328): Use default u-boot package. --- gnu/local.mk | 1 + 1 file changed, 1 insertion(+) (limited to 'gnu/local.mk') diff --git a/gnu/local.mk b/gnu/local.mk index c530507b1a..4c6aa6945e 100644 --- a/gnu/local.mk +++ b/gnu/local.mk @@ -1780,6 +1780,7 @@ dist_patch_DATA = \ %D%/packages/patches/tuxpaint-stamps-path.patch \ %D%/packages/patches/twinkle-bcg729.patch \ %D%/packages/patches/u-boot-rockchip-inno-usb.patch \ + %D%/packages/patches/u-boot-sifive-prevent-reloc-initrd-fdt.patch \ %D%/packages/patches/u-boot-riscv64-fix-extlinux.patch \ %D%/packages/patches/ucx-tcp-iface-ioctl.patch \ %D%/packages/patches/udiskie-no-appindicator.patch \ -- cgit v1.2.3 From 6da2b35b60aee8086c7ad1ee1c1492ed5e730a92 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Mon, 2 Aug 2021 12:17:38 +0200 Subject: gnu: Add onnx-optimizer. * gnu/packages/machine-learning.scm (onnx-optimizer): New variable. * gnu/packages/patches/onnx-optimizer-system-library.patch: New file. * gnu/local.mk (dist_patch_DATA): Add it. --- gnu/local.mk | 1 + gnu/packages/machine-learning.scm | 44 ++++++++++++++++++ .../patches/onnx-optimizer-system-library.patch | 53 ++++++++++++++++++++++ 3 files changed, 98 insertions(+) create mode 100644 gnu/packages/patches/onnx-optimizer-system-library.patch (limited to 'gnu/local.mk') diff --git a/gnu/local.mk b/gnu/local.mk index 4c6aa6945e..9e8f2d702d 100644 --- a/gnu/local.mk +++ b/gnu/local.mk @@ -1512,6 +1512,7 @@ dist_patch_DATA = \ %D%/packages/patches/ocaml-dose3-Install-mli-cmx-etc.patch \ %D%/packages/patches/omake-fix-non-determinism.patch \ %D%/packages/patches/oneko-remove-nonfree-characters.patch \ + %D%/packages/patches/onnx-optimizer-system-library.patch \ %D%/packages/patches/onnx-use-system-googletest.patch \ %D%/packages/patches/onnx-shared-libraries.patch \ %D%/packages/patches/onnx-skip-model-downloads.patch \ diff --git a/gnu/packages/machine-learning.scm b/gnu/packages/machine-learning.scm index 177d1aaa4f..b32e5ef289 100644 --- a/gnu/packages/machine-learning.scm +++ b/gnu/packages/machine-learning.scm @@ -675,6 +675,50 @@ standard data types.") ;; headers, hence the name change. (deprecated-package "python-onnx" onnx)) +(define-public onnx-optimizer + (package + (name "onnx-optimizer") + ;; Note: 0.2.x is *more* recent than 1.5.0. + (version "0.2.6") + (home-page "https://github.com/onnx/optimizer") + (source (origin + (method git-fetch) + (uri (git-reference + (url home-page) + (commit (string-append "v" version)))) + (sha256 + (base32 + "1wkqqdxcxpfbf8zpbdfdd3zz5jkw775g31gyykj11z4y6pp659l6")) + (file-name (git-file-name name version)) + (patches (search-patches "onnx-optimizer-system-library.patch")) + (modules '((guix build utils))) + (snippet '(delete-file-recursively "third_party")))) + (build-system python-build-system) + (arguments (package-arguments onnx)) ;reuse build system tweaks + (native-inputs + `(("cmake" ,cmake) + ("python-pytest" ,python-pytest) + ("python-pytest-runner" ,python-pytest-runner) + ("python-nbval" ,python-nbval) + ("python-coverage" ,python-coverage))) + (inputs + `(("onnx" ,onnx) + ("protobuf" ,protobuf) + ("pybind11" ,pybind11))) + (propagated-inputs + `(("python-numpy" ,python-numpy))) + (synopsis "Library to optimize ONNX models") + (description + "This package provides a C++ and Python library for performing arbitrary +optimizations on ONNX models, as well as a growing list of prepackaged +optimization passes. + +Not all possible optimizations can be directly implemented on ONNX graphs--- +some will need additional backend-specific information---but many can, and the +aim is to provide all such passes along with ONNX so that they can be re-used +with a single function call.") + (license license:expat))) + (define-public rxcpp (package (name "rxcpp") diff --git a/gnu/packages/patches/onnx-optimizer-system-library.patch b/gnu/packages/patches/onnx-optimizer-system-library.patch new file mode 100644 index 0000000000..5c592597e0 --- /dev/null +++ b/gnu/packages/patches/onnx-optimizer-system-library.patch @@ -0,0 +1,53 @@ +Arrange so that onnx-optimizer (1) uses our own ONNX build, +(2) builds as a shared library, and (3) links against the shared +libraries of ONNX. + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index c2e48b35..8af51076 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -9,8 +9,6 @@ endif(NOT MSVC) + + set(CMAKE_POSITION_INDEPENDENT_CODE ON) + +-set(ONNX_ROOT ${PROJECT_SOURCE_DIR}/third_party/onnx) +-add_subdirectory(${ONNX_ROOT}) + + file(READ "${PROJECT_SOURCE_DIR}/VERSION_NUMBER" ONNX_OPTIMIZER_VERSION) + string(STRIP "${ONNX_OPTIMIZER_VERSION}" ONNX_OPTIMIZER_VERSION) +@@ -21,14 +19,18 @@ file(GLOB_RECURSE onnx_opt_srcs "onnxoptimizer/*.cc" + list(REMOVE_ITEM onnx_opt_srcs "${PROJECT_SOURCE_DIR}/onnxoptimizer/cpp2py_export.cc") + + add_library(onnx_optimizer ${onnx_opt_srcs}) +-target_link_libraries(onnx_optimizer PUBLIC onnx) ++target_link_libraries(onnx_optimizer PUBLIC onnx onnx_proto) + target_include_directories(onnx_optimizer PUBLIC + $ + $ + ) + ++# These cpp macros must be defined so the ONNX headers behave ++# correctly. ++set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DONNX_ML=1 -DONNX_NAMESPACE=onnx") ++ + add_executable(onnx_optimizer_exec examples/onnx_optimizer_exec.cpp) +-target_link_libraries(onnx_optimizer_exec onnx_optimizer) ++target_link_libraries(onnx_optimizer_exec onnx_optimizer protobuf) + + if(BUILD_ONNX_PYTHON) + if("${PY_EXT_SUFFIX}" STREQUAL "") +@@ -79,11 +81,10 @@ if(BUILD_ONNX_PYTHON) + PRIVATE $) + else() + # Assume everything else is like gcc +- target_link_libraries(onnx_opt_cpp2py_export +- PRIVATE "-Wl,--whole-archive" $ +- "-Wl,--no-whole-archive") ++ target_link_libraries(onnx_opt_cpp2py_export PRIVATE onnx_optimizer) + set_target_properties(onnx_opt_cpp2py_export +- PROPERTIES LINK_FLAGS "-Wl,--exclude-libs,ALL") ++ PROPERTIES LINK_FLAGS ++ "-Wl,-rpath=${CMAKE_INSTALL_PREFIX}/lib") + endif() + + target_link_libraries(onnx_opt_cpp2py_export PRIVATE onnx_optimizer) -- cgit v1.2.3 From 69dcc24c9f0cdfea674eb690e7755d26a25ced2b Mon Sep 17 00:00:00 2001 From: Maxim Cournoyer Date: Thu, 15 Apr 2021 01:18:24 -0400 Subject: services: Add a service for Jami. * gnu/services/telephony.scm (string-or-computed-file?) (string-list?, account-fingerprint-list?): New procedures. (maybe-string-list, maybe-account-fingerprint-list) (maybe-boolean, maybe-string, jami-account-list): New configuration field types. (serialize-string-list, serialize-boolean, serialize-string) (jami-account, jami-account->alist, jami-configuration) (jami-account-list?, jami-account-list-maybe): New procedures. (%jami-accounts): New variable. (jami-configuration->command-line-arguments): New procedure. (jami-dbus-session-activation, jami-shepherd-services): New procedures. (jami-service-type): New variable. * gnu/build/jami-service.scm: New file. * gnu/tests/data/jami-dummy-account.dat: Likewise. * gnu/tests/telephony.scm: Likewise. * gnu/local.mk (GNU_SYSTEM_MODULES): Register them. * Makefile.am (SCM_TESTS): Register the test file. (dist_patch_DATA): Register the new data file. * doc/guix.texi (Telephony Services): Document it. --- Makefile.am | 1 + doc/guix.texi | 228 ++++++++++++ gnu/build/jami-service.scm | 587 +++++++++++++++++++++++++++++ gnu/local.mk | 5 +- gnu/services/telephony.scm | 684 +++++++++++++++++++++++++++++++++- gnu/tests/data/jami-dummy-account.dat | 392 +++++++++++++++++++ gnu/tests/telephony.scm | 366 ++++++++++++++++++ tests/services/telephony.scm | 446 ++++++++++++++++++++++ 8 files changed, 2706 insertions(+), 3 deletions(-) create mode 100644 gnu/build/jami-service.scm create mode 100644 gnu/tests/data/jami-dummy-account.dat create mode 100644 gnu/tests/telephony.scm create mode 100644 tests/services/telephony.scm (limited to 'gnu/local.mk') diff --git a/Makefile.am b/Makefile.am index d5ec909213..5542aa1c56 100644 --- a/Makefile.am +++ b/Makefile.am @@ -489,6 +489,7 @@ SCM_TESTS = \ tests/services/file-sharing.scm \ tests/services/configuration.scm \ tests/services/linux.scm \ + tests/services/telephony.scm \ tests/sets.scm \ tests/size.scm \ tests/status.scm \ diff --git a/doc/guix.texi b/doc/guix.texi index 2298d512a1..9a9c85678c 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -22526,6 +22526,234 @@ and Error. @node Telephony Services @subsection Telephony Services +@cindex telephony, services +The @code{(gnu services telephony)} module contains Guix service +definitions for telephony services. Currently it provides the following +services: + +@subsubheading Jami + +@cindex jami, service + +This section describes how to configure a Jami server that can be used +to host video (or audio) conferences, among other uses. The following +example demonstrates how to specify Jami account archives (backups) to +be provisioned automatically: + +@lisp +(service jami-service-type + (jami-configuration + (accounts + (list (jami-account + (archive "/etc/jami/unencrypted-account-1.gz")) + (jami-account + (archive "/etc/jami/unencrypted-account-2.gz")))))) +@end lisp + +When the accounts field is specified, the Jami account files of the +service found under @file{/var/lib/jami} are recreated every time the +service starts. + +Jami accounts and their corresponding backup archives can be generated +using either the @code{jami-qt} or @code{jami-gnome} Jami clients. The +accounts should not be password-protected, but it is wise to ensure +their files are only readable by @samp{root}. + +The next example shows how to declare that only some contacts should be +allowed to communicate with a given account: + +@lisp +(service jami-service-type + (jami-configuration + (accounts + (list (jami-account + (archive "/etc/jami/unencrypted-account-1.gz") + (peer-discovery? #t) + (rendezvous-point? #t) + (allowed-contacts + '("1dbcb0f5f37324228235564b79f2b9737e9a008f" + "2dbcb0f5f37324228235564b79f2b9737e9a008f"))))))) +@end lisp + +In this mode, only the declared @code{allowed-contacts} can initiate +communication with the Jami account. This can be used, for example, +with rendezvous point accounts to create a private video conferencing +space. + +To put the system administrator in full control of the conferences +hosted on their system, the Jami service supports the following actions: + +@example sh +# herd doc jami list-actions jami +(list-accounts + list-account-details + list-banned-contacts + list-contacts + list-moderators + add-moderator + ban-contact + enable-account + disable-account) +@end example + +The above actions aim to provide the most valuable actions for +moderation purposes, not to cover the whole Jami API. Users wanting to +interact with the Jami daemon from Guile may be interested in +experimenting with the @code{(gnu build jami-service)} module, which +powers the above Shepherd actions. + +@c TODO: This should be auto-generated from the doc already defined on +@c the shepherd-actions themselves in (gnu services telephony). +The @code{add-moderator} and @code{ban-contact} actions accept a contact +@emph{fingerprint} (40 characters long hash) as first argument and an +account fingerprint or username as second argument: + +@example sh +# herd add-moderator jami 1dbcb0f5f37324228235564b79f2b9737e9a008f \ + f3345f2775ddfe07a4b0d95daea111d15fbc1199 + +# herd list-moderators jami +Moderators for account f3345f2775ddfe07a4b0d95daea111d15fbc1199: + - 1dbcb0f5f37324228235564b79f2b9737e9a008f + +@end example + +In the case of @code{ban-contact}, the second username argument is +optional; when omitted, the account is banned from all Jami accounts: + +@example sh +# herd ban-contact jami 1dbcb0f5f37324228235564b79f2b9737e9a008f + +# herd list-banned-contacts jami +Banned contacts for account f3345f2775ddfe07a4b0d95daea111d15fbc1199: + - 1dbcb0f5f37324228235564b79f2b9737e9a008f + +@end example + +Banned contacts are also stripped from their moderation privileges. + +The @code{disable-account} action allows to completely disconnect an +account from the network, making it unreachable, while +@code{enable-account} does the inverse. They accept a single account +username or fingerprint as first argument: + +@example sh +# herd disable-account jami f3345f2775ddfe07a4b0d95daea111d15fbc1199 + +# herd list-accounts jami +The following Jami accounts are available: + - f3345f2775ddfe07a4b0d95daea111d15fbc1199 (dummy) [disabled] + +@end example + +The @code{list-account-details} action prints the detailed parameters of +each accounts in the Recutils format, which means the @command{recsel} +command can be used to select accounts of interest (@pxref{Selection +Expressions,,,recutils, GNU recutils manual}). Note that period +characters (@samp{.}) found in the account parameter keys are mapped to +underscores (@samp{_}) in the output, to meet the requirements of the +Recutils format. The following example shows how to print the account +fingerprints for all accounts operating in the rendezvous point mode: + +@example sh +# herd list-account-details jami | \ + recsel -p Account.username -e 'Account.rendezVous ~ "true"' +Account_username: f3345f2775ddfe07a4b0d95daea111d15fbc1199 +@end example + +The remaining actions should be self-explanatory. + +The complete set of available configuration options is detailed below. + +@c TODO: Ideally, the following fragments would be auto-generated at +@c build time, so that they needn't be manually duplicated. +@c Auto-generated via (configuration->documentation 'jami-configuration) +@deftp {Data Type} jami-configuration +Available @code{jami-configuration} fields are: + +@table @asis +@item @code{jamid} (default: @code{libring}) (type: package) +The Jami daemon package to use. + +@item @code{dbus} (default: @code{dbus}) (type: package) +The D-Bus package to use to start the required D-Bus session. + +@item @code{nss-certs} (default: @code{nss-certs}) (type: package) +The nss-certs package to use to provide TLS certificates. + +@item @code{enable-logging?} (default: @code{#t}) (type: boolean) +Whether to enable logging to syslog. + +@item @code{debug?} (default: @code{#f}) (type: boolean) +Whether to enable debug level messages. + +@item @code{auto-answer?} (default: @code{#f}) (type: boolean) +Whether to force automatic answer to incoming calls. + +@item @code{accounts} (default: @code{disabled}) (type: maybe-jami-account-list) +A list of Jami accounts to be (re-)provisioned every time the Jami +daemon service starts. When providing this field, the account +directories under @file{/var/lib/jami/} are recreated every time the +service starts, ensuring a consistent state. + +@end table + +@end deftp + +@c Auto-generated via (configuration->documentation 'jami-account) +@deftp {Data Type} jami-account +Available @code{jami-account} fields are: + +@table @asis +@item @code{archive} (type: string-or-computed-file) +The account archive (backup) file name of the account. This is used to +provision the account when the service starts. The account archive +should @emph{not} be encrypted. It is highly recommended to make it +readable only to the @samp{root} user (i.e., not in the store), to guard +against leaking the secret key material of the Jami account it contains. + +@item @code{allowed-contacts} (default: @code{disabled}) (type: maybe-account-fingerprint-list) +The list of allowed contacts for the account, entered as their 40 +characters long fingerprint. Messages or calls from accounts not in +that list will be rejected. When unspecified, the configuration of the +account archive is used as-is with respect to contacts and public +inbound calls/messaging allowance, which typically defaults to allow any +contact to communicate with the account. + +@item @code{moderators} (default: @code{disabled}) (type: maybe-account-fingerprint-list) +The list of contacts that should have moderation privileges (to ban, +mute, etc. other users) in rendezvous conferences, entered as their 40 +characters long fingerprint. When unspecified, the configuration of the +account archive is used as-is with respect to moderation, which +typically defaults to allow anyone to moderate. + +@item @code{rendezvous-point?} (default: @code{disabled}) (type: maybe-boolean) +Whether the account should operate in the rendezvous mode. In this +mode, all the incoming audio/video calls are mixed into a conference. +When left unspecified, the value from the account archive prevails. + +@item @code{peer-discovery?} (default: @code{disabled}) (type: maybe-boolean) +Whether peer discovery should be enabled. Peer discovery is used to +discover other OpenDHT nodes on the local network, which can be useful +to maintain communication between devices on such network even when the +connection to the the Internet has been lost. When left unspecified, +the value from the account archive prevails. + +@item @code{bootstrap-hostnames} (default: @code{disabled}) (type: maybe-string-list) +A list of hostnames or IPs pointing to OpenDHT nodes, that should be +used to initially join the OpenDHT network. When left unspecified, the +value from the account archive prevails. + +@item @code{name-server-uri} (default: @code{disabled}) (type: maybe-string) +The URI of the name server to use, that can be used to retrieve the +account fingerprint for a registered username. + +@end table + +@end deftp + +@subsubheading Murmur (VoIP server) + @cindex Murmur (VoIP server) @cindex VoIP server This section describes how to set up and run a Murmur server. Murmur is diff --git a/gnu/build/jami-service.scm b/gnu/build/jami-service.scm new file mode 100644 index 0000000000..d44e87387d --- /dev/null +++ b/gnu/build/jami-service.scm @@ -0,0 +1,587 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2021 Maxim Cournoyer +;;; +;;; This file is part of GNU Guix. +;;; +;;; GNU Guix is free software; you can redistribute it and/or modify it +;;; under the terms of the GNU General Public License as published by +;;; the Free Software Foundation; either version 3 of the License, or (at +;;; your option) any later version. +;;; +;;; GNU Guix is distributed in the hope that it will be useful, but +;;; WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Guix. If not, see . + +;;; Commentary: +;;; +;;; This module contains helpers used as part of the jami-service-type +;;; definition. +;;; +;;; Code: + +(define-module (gnu build jami-service) + #:use-module (ice-9 format) + #:use-module (ice-9 match) + #:use-module (ice-9 peg) + #:use-module (ice-9 rdelim) + #:use-module (ice-9 regex) + #:use-module (rnrs io ports) + #:autoload (shepherd service) (fork+exec-command) + #:use-module (srfi srfi-1) + #:use-module (srfi srfi-26) + #:export (account-fingerprint? + account-details->recutil + get-accounts + get-usernames + set-account-details + add-account + account->username + username->account + username->contacts + enable-account + disable-account + + add-contact + remove-contact + + set-all-moderators + set-moderator + username->all-moderators? + username->moderators + + dbus-available-services + dbus-service-available? + + %send-dbus-binary + %send-dbus-bus + %send-dbus-user + %send-dbus-group + %send-dbus-debug + send-dbus + + with-retries)) + +;;; +;;; Utilities. +;;; + +(define-syntax-rule (with-retries n delay body ...) + "Retry the code in BODY up to N times until it doesn't raise an exception +nor return #f, else raise an error. A delay of DELAY seconds is inserted +before each retry." + (let loop ((attempts 0)) + (catch #t + (lambda () + (let ((result (begin body ...))) + (if (not result) + (error "failed attempt" attempts) + result))) + (lambda args + (if (< attempts n) + (begin + (sleep delay) ;else wait and retry + (loop (+ 1 attempts))) + (error "maximum number of retry attempts reached" + body ... args)))))) + +(define (alist->list alist) + "Flatten ALIST into a list." + (append-map (match-lambda + (() '()) + ((key . value) + (list key value))) + alist)) + +(define account-fingerprint-rx (make-regexp "[0-9A-f]{40}")) + +(define (account-fingerprint? val) + "A Jami account fingerprint is 40 characters long and only contains +hexadecimal characters." + (and (string? val) + (regexp-exec account-fingerprint-rx val))) + + +;;; +;;; D-Bus reply parser. +;;; + +(define (parse-dbus-reply reply) + "Return the parse tree of REPLY, a string returned by the 'dbus-send' +command." + ;; Refer to 'man 1 dbus-send' for the grammar reference. Note that the + ;; format of the replies doesn't match the format of the input, which is the + ;; one documented, but it gives an idea. For an even better reference, see + ;; the `print_iter' procedure of the 'dbus-print-message.c' file from the + ;; 'dbus' package sources. + (define-peg-string-patterns + "contents <- header (item / container (item / container*)?) + item <-- WS type WS value NL + container <- array / dict / variant + array <-- array-start (item / container)* array-end + dict <-- array-start dict-entry* array-end + dict-entry <-- dict-entry-start item item dict-entry-end + variant <-- variant-start item + type <-- 'string' / 'int16' / 'uint16' / 'int32' / 'uint32' / 'int64' / + 'uint64' / 'double' / 'byte' / 'boolean' / 'objpath' + value <-- (!NL .)* NL + header < (!NL .)* NL + variant-start < WS 'variant' + array-start < WS 'array [' NL + array-end < WS ']' NL + dict-entry-start < WS 'dict entry(' NL + dict-entry-end < WS ')' NL + DQ < '\"' + WS < ' '* + NL < '\n'*") + + (peg:tree (match-pattern contents reply))) + +(define (strip-quotes text) + "Strip the leading and trailing double quotes (\") characters from TEXT." + (let* ((text* (if (string-prefix? "\"" text) + (string-drop text 1) + text)) + (text** (if (string-suffix? "\"" text*) + (string-drop-right text* 1) + text*))) + text**)) + +(define (deserialize-item item) + "Return the value described by the ITEM parse tree as a Guile object." + ;; Strings are printed wrapped in double quotes (see the print_iter + ;; procedure in dbus-print-message.c). + (match item + (('item ('type "string") ('value value)) + (strip-quotes value)) + (('item ('type "boolean") ('value value)) + (if (string=? "true" value) + #t + #f)) + (('item _ ('value value)) + value))) + +(define (serialize-boolean bool) + "Return the serialized format expected by dbus-send for BOOL." + (format #f "boolean:~:[false~;true~]" bool)) + +(define (dict->alist dict-parse-tree) + "Translate a dict parse tree to an alist." + (define (tuples->alist tuples) + (map (lambda (x) (apply cons x)) tuples)) + + (match dict-parse-tree + ('dict + '()) + (('dict ('dict-entry keys values) ...) + (let ((keys* (map deserialize-item keys)) + (values* (map deserialize-item values))) + (tuples->alist (zip keys* values*)))))) + +(define (array->list array-parse-tree) + "Translate an array parse tree to a list." + (match array-parse-tree + ('array + '()) + (('array items ...) + (map deserialize-item items)))) + + +;;; +;;; Low-level, D-Bus-related procedures. +;;; + +;;; The following parameters are used in the jami-service-type service +;;; definition to conveniently customize the behavior of the send-dbus helper, +;;; even when called indirectly. +(define %send-dbus-binary (make-parameter "dbus-send")) +(define %send-dbus-bus (make-parameter #f)) +(define %send-dbus-user (make-parameter #f)) +(define %send-dbus-group (make-parameter #f)) +(define %send-dbus-debug (make-parameter #f)) + +(define* (send-dbus #:key service path interface method + bus + dbus-send + user group + timeout + arguments) + "Return the response of DBUS-SEND, else raise an error. Unless explicitly +provided, DBUS-SEND takes the value of the %SEND-DBUS-BINARY parameter. BUS +can be used to specify the bus address, such as 'unix:path=/var/run/jami/bus'. +Alternatively, the %SEND-DBUS-BUS parameter can be used. ARGUMENTS can be +used to pass input values to a D-Bus method call. TIMEOUT is the amount of +time to wait for a reply in milliseconds before giving up with an error. USER +and GROUP allow choosing under which user/group the DBUS-SEND command is +executed. Alternatively, the %SEND-DBUS-USER and %SEND-DBUS-GROUP parameters +can be used instead." + (let* ((command `(,(if dbus-send + dbus-send + (%send-dbus-binary)) + ,@(if (or bus (%send-dbus-bus)) + (list (string-append "--bus=" + (or bus (%send-dbus-bus)))) + '()) + "--print-reply" + ,@(if timeout + (list (format #f "--reply-timeout=~d" timeout)) + '()) + ,(string-append "--dest=" service) ;e.g., cx.ring.Ring + ,path ;e.g., /cx/ring/Ring/ConfigurationManager + ,(string-append interface "." method) + ,@(or arguments '()))) + (temp-port (mkstemp! (string-copy "/tmp/dbus-send-output-XXXXXXX"))) + (temp-file (port-filename temp-port))) + (dynamic-wind + (lambda () + (let* ((uid (or (and=> (or user (%send-dbus-user)) + (compose passwd:uid getpwnam)) -1)) + (gid (or (and=> (or group (%send-dbus-group)) + (compose group:gid getgrnam)) -1))) + (chown temp-port uid gid))) + (lambda () + (let ((pid (fork+exec-command command + #:user (or user (%send-dbus-user)) + #:group (or group (%send-dbus-group)) + #:log-file temp-file))) + (match (waitpid pid) + ((_ . status) + (let ((exit-status (status:exit-val status)) + (output (call-with-port temp-port get-string-all))) + (if (= 0 exit-status) + output + (error "the send-dbus command exited with: " + command exit-status output))))))) + (lambda () + (false-if-exception (delete-file temp-file)))))) + +(define (parse-account-ids reply) + "Return the Jami account IDs from REPLY, which is assumed to be the output +of the Jami D-Bus `getAccountList' method." + (array->list (parse-dbus-reply reply))) + +(define (parse-account-details reply) + "Parse REPLY, which is assumed to be the output of the Jami D-Bus +`getAccountDetails' method, and return its content as an alist." + (dict->alist (parse-dbus-reply reply))) + +(define (parse-contacts reply) + "Parse REPLY, which is assumed to be the output of the Jamid D-Bus +`getContacts' method, and return its content as an alist." + (match (parse-dbus-reply reply) + ('array + '()) + (('array dicts ...) + (map dict->alist dicts)))) + + +;;; +;;; Higher-level, D-Bus-related procedures. +;;; + +(define (validate-fingerprint fingerprint) + "Validate that fingerprint is 40 characters long." + (unless (account-fingerprint? fingerprint) + (error "Account fingerprint is not valid:" fingerprint))) + +(define (dbus-available-services) + "Return the list of available (acquired) D-Bus services." + (let ((reply (parse-dbus-reply + (send-dbus #:service "org.freedesktop.DBus" + #:path "/org/freedesktop/DBus" + #:interface "org.freedesktop.DBus" + #:method "ListNames")))) + ;; Remove entries such as ":1.7". + (remove (cut string-prefix? ":" <>) + (array->list reply)))) + +(define (dbus-service-available? service) + "Predicate to check for the D-Bus SERVICE availability." + (member service (dbus-available-services))) + +(define* (send-dbus/configuration-manager #:key method arguments timeout) + "Query the Jami D-Bus ConfigurationManager service." + (send-dbus #:service "cx.ring.Ring" + #:path "/cx/ring/Ring/ConfigurationManager" + #:interface "cx.ring.Ring.ConfigurationManager" + #:method method + #:arguments arguments + #:timeout timeout)) + +;;; The following methods are for internal use; they make use of the account +;;; ID, an implementation detail of Jami the user should not need to be +;;; concerned with. +(define (get-account-ids) + "Return the available Jami account identifiers (IDs). Account IDs are an +implementation detail used to identify the accounts in Jami." + (parse-account-ids + (send-dbus/configuration-manager #:method "getAccountList"))) + +(define (id->account-details id) + "Retrieve the account data associated with the given account ID." + (parse-account-details + (send-dbus/configuration-manager + #:method "getAccountDetails" + #:arguments (list (string-append "string:" id))))) + +(define (id->volatile-account-details id) + "Retrieve the account data associated with the given account ID." + (parse-account-details + (send-dbus/configuration-manager + #:method "getVolatileAccountDetails" + #:arguments (list (string-append "string:" id))))) + +(define (id->account id) + "Retrieve the complete account data associated with the given account ID." + (append (id->volatile-account-details id) + (id->account-details id))) + +(define %username-to-id-cache #f) + +(define (invalidate-username-to-id-cache!) + (set! %username-to-id-cache #f)) + +(define (username->id username) + "Return the first account ID corresponding to USERNAME." + (unless (assoc-ref %username-to-id-cache username) + (set! %username-to-id-cache + (append-map + (lambda (id) + (let* ((account (id->account id)) + (username (assoc-ref account "Account.username")) + (registered-name (assoc-ref account + "Account.registeredName"))) + `(,@(if username + (list (cons username id)) + '()) + ,@(if registered-name + (list (cons registered-name id)) + '())))) + (get-account-ids)))) + (or (assoc-ref %username-to-id-cache username) + (let ((message (format #f "Could not retrieve a local account ID\ + for ~:[username~;fingerprint~]" (account-fingerprint? username)))) + (error message username)))) + +(define (account->username account) + "Return USERNAME, the registered username associated with ACCOUNT, else its +public key fingerprint." + (or (assoc-ref account "Account.registeredName") + (assoc-ref account "Account.username"))) + +(define (id->username id) + "Return USERNAME, the registered username associated with ID, else its +public key fingerprint, else #f." + (account->username (id->account id))) + +(define (get-accounts) + "Return the list of all accounts, as a list of alists." + (map id->account (get-account-ids))) + +(define (get-usernames) + "Return the list of the usernames associated with the present accounts." + (map account->username (get-accounts))) + +(define (username->account username) + "Return the first account associated with USERNAME, else #f. +USERNAME can be either the account 40 characters public key fingerprint or a +registered username." + (find (lambda (account) + (member username + (list (assoc-ref account "Account.username") + (assoc-ref account "Account.registeredName")))) + (get-accounts))) + +(define (add-account archive) + "Import the Jami account ARCHIVE and return its account ID. The archive +should *not* be encrypted with a password. Return the username associated +with the account." + (invalidate-username-to-id-cache!) + (let ((reply (send-dbus/configuration-manager + #:method "addAccount" + #:arguments (list (string-append + "dict:string:string:Account.archivePath," + archive + ",Account.type,RING"))))) + ;; The account information takes some time to be populated. + (let ((id (deserialize-item (parse-dbus-reply reply)))) + (with-retries 20 1 + (let ((username (id->username id))) + (if (string-null? username) + #f + username)))))) + +(define (remove-account username) + "Delete the Jami account associated with USERNAME, the account 40 characters +fingerprint or a registered username." + (let ((id (username->id username))) + (send-dbus/configuration-manager + #:method "removeAccount" + #:arguments (list (string-append "string:" id)))) + (invalidate-username-to-id-cache!)) + +(define* (username->contacts username) + "Return the contacts associated with the account of USERNAME as two values; +the first one being the regular contacts and the second one the banned +contacts. USERNAME can be either the account 40 characters public key +fingerprint or a registered username. The contacts returned are represented +using their 40 characters fingerprint." + (let* ((id (username->id username)) + (reply (send-dbus/configuration-manager + #:method "getContacts" + #:arguments (list (string-append "string:" id)))) + (all-contacts (parse-contacts reply)) + (banned? (lambda (contact) + (and=> (assoc-ref contact "banned") + (cut string=? "true" <>)))) + (banned (filter banned? all-contacts)) + (not-banned (filter (negate banned?) all-contacts)) + (fingerprint (cut assoc-ref <> "id"))) + (values (map fingerprint not-banned) + (map fingerprint banned)))) + +(define* (remove-contact contact username #:key ban?) + "Remove CONTACT, the 40 characters public key fingerprint of a contact, from +the account associated with USERNAME (either a fingerprint or a registered +username). When BAN? is true, also mark the contact as banned." + (validate-fingerprint contact) + (let ((id (username->id username))) + (send-dbus/configuration-manager + #:method "removeContact" + #:arguments (list (string-append "string:" id) + (string-append "string:" contact) + (serialize-boolean ban?))))) + +(define (add-contact contact username) + "Add CONTACT, the 40 characters public key fingerprint of a contact, to the +account of USERNAME (either a fingerprint or a registered username)." + (validate-fingerprint contact) + (let ((id (username->id username))) + (send-dbus/configuration-manager + #:method "addContact" + #:arguments (list (string-append "string:" id) + (string-append "string:" contact))))) + +(define* (set-account-details details username #:key timeout) + "Set DETAILS, an alist containing the key value pairs to set for the account +of USERNAME, a registered username or account fingerprint. The value of the +parameters not provided are unchanged. TIMEOUT is a value in milliseconds to +pass to the `send-dbus/configuration-manager' procedure." + (let* ((id (username->id username)) + (current-details (id->account-details id)) + (updated-details (map (match-lambda + ((key . value) + (or (and=> (assoc-ref details key) + (cut cons key <>)) + (cons key value)))) + current-details)) + ;; dbus-send does not permit sending null strings (it throws a + ;; "malformed dictionary" error). Luckily they seem to have the + ;; semantic of "default account value" in Jami; so simply drop them. + (updated-details* (remove (match-lambda + ((_ . value) + (string-null? value))) + updated-details))) + (send-dbus/configuration-manager + #:timeout timeout + #:method "setAccountDetails" + #:arguments + (list (string-append "string:" id) + (string-append "dict:string:string:" + (string-join (alist->list updated-details*) + ",")))))) + +(define (set-all-moderators enabled? username) + "Set the 'AllModerators' property to enabled? for the account of USERNAME, a +registered username or account fingerprint." + (let ((id (username->id username))) + (send-dbus/configuration-manager + #:method "setAllModerators" + #:arguments + (list (string-append "string:" id) + (serialize-boolean enabled?))))) + +(define (username->all-moderators? username) + "Return the 'AllModerators' property for the account of USERNAME, a +registered username or account fingerprint." + (let* ((id (username->id username)) + (reply (send-dbus/configuration-manager + #:method "isAllModerators" + #:arguments + (list (string-append "string:" id))))) + (deserialize-item (parse-dbus-reply reply)))) + +(define (username->moderators username) + "Return the moderators for the account of USERNAME, a registered username or +account fingerprint." + (let* ((id (username->id username)) + (reply (send-dbus/configuration-manager + #:method "getDefaultModerators" + #:arguments + (list (string-append "string:" id))))) + (array->list (parse-dbus-reply reply)))) + +(define (set-moderator contact enabled? username) + "Set the moderator flag to ENABLED? for CONTACT, the 40 characters public +key fingerprint of a contact for the account of USERNAME, a registered +username or account fingerprint." + (validate-fingerprint contact) + (let* ((id (username->id username))) + (send-dbus/configuration-manager #:method "setDefaultModerator" + #:arguments + (list (string-append "string:" id) + (string-append "string:" contact) + (serialize-boolean enabled?))))) + +(define (disable-account username) + "Disable the account known by USERNAME, a registered username or account +fingerprint." + (set-account-details '(("Account.enable" . "false")) username + ;; Waiting for the reply on this command takes a very + ;; long time that trips the default D-Bus timeout value + ;; (25 s), for some reason. + #:timeout 60000)) + +(define (enable-account username) + "Enable the account known by USERNAME, a registered username or account +fingerprint." + (set-account-details '(("Account.enable" . "true")) username)) + + +;;; +;;; Presentation procedures. +;;; + +(define (.->_ text) + "Map each period character to underscore characters." + (string-map (match-lambda + (#\. #\_) + (c c)) + text)) + +(define (account-details->recutil account-details) + "Serialize the account-details alist into a recutil string. Period +characters in the keys are normalized to underscore to meet Recutils' format +requirements." + (define (pair->recutil-property pair) + (match pair + ((key . value) + (string-append (.->_ key) ": " value)))) + + (define sorted-account-details + ;; Have the account username, display name and alias appear first, for + ;; convenience. + (let ((first-items '("Account.username" + "Account.displayName" + "Account.alias"))) + (append (map (cut assoc <> account-details) first-items) + (fold alist-delete account-details first-items)))) + + (string-join (map pair->recutil-property sorted-account-details) "\n")) + +;; Local Variables: +;; eval: (put 'with-retries 'scheme-indent-function 2) +;; End: diff --git a/gnu/local.mk b/gnu/local.mk index 9e8f2d702d..66a2ba52bc 100644 --- a/gnu/local.mk +++ b/gnu/local.mk @@ -685,6 +685,7 @@ GNU_SYSTEM_MODULES = \ %D%/build/chromium-extension.scm \ %D%/build/cross-toolchain.scm \ %D%/build/image.scm \ + %D%/build/jami-service.scm \ %D%/build/file-systems.scm \ %D%/build/hurd-boot.scm \ %D%/build/install.scm \ @@ -722,6 +723,7 @@ GNU_SYSTEM_MODULES = \ %D%/tests/security-token.scm \ %D%/tests/singularity.scm \ %D%/tests/ssh.scm \ + %D%/tests/telephony.scm \ %D%/tests/version-control.scm \ %D%/tests/virtualization.scm \ %D%/tests/web.scm @@ -1881,7 +1883,8 @@ dist_patch_DATA = \ %D%/packages/patches/ytnef-CVE-2021-3403.patch \ %D%/packages/patches/ytnef-CVE-2021-3404.patch \ %D%/packages/patches/zstd-CVE-2021-24031_CVE-2021-24032.patch \ - %D%/packages/patches/zziplib-CVE-2018-16548.patch + %D%/packages/patches/zziplib-CVE-2018-16548.patch \ + %D%/tests/data/jami-dummy-account.dat MISC_DISTRO_FILES = \ %D%/packages/ld-wrapper.in diff --git a/gnu/services/telephony.scm b/gnu/services/telephony.scm index e1259cc2df..fd90840324 100644 --- a/gnu/services/telephony.scm +++ b/gnu/services/telephony.scm @@ -1,5 +1,6 @@ ;;; GNU Guix --- Functional package management for GNU ;;; Copyright © 2017 nee +;;; Copyright © 2021 Maxim Cournoyer ;;; ;;; This file is part of GNU Guix. ;;; @@ -17,16 +18,45 @@ ;;; along with GNU Guix. If not, see . (define-module (gnu services telephony) - #:use-module (gnu services) + #:use-module ((gnu build jami-service) #:select (account-fingerprint?)) + #:use-module ((gnu services) #:hide (delete)) + #:use-module (gnu services configuration) #:use-module (gnu services shepherd) #:use-module (gnu system shadow) #:use-module (gnu packages admin) + #:use-module (gnu packages certs) + #:use-module (gnu packages glib) + #:use-module (gnu packages jami) #:use-module (gnu packages telephony) #:use-module (guix records) + #:use-module (guix modules) + #:use-module (guix packages) #:use-module (guix gexp) #:use-module (srfi srfi-1) + #:use-module (srfi srfi-2) + #:use-module (srfi srfi-26) + #:use-module (ice-9 format) #:use-module (ice-9 match) - #:export (murmur-configuration + #:export (jami-account + jami-account-archive + jami-account-allowed-contacts + jami-account-moderators + jami-account-rendezvous-point? + jami-account-discovery? + jami-account-bootstrap-uri + jami-account-name-server-uri + + jami-configuration + jami-configuration-jamid + jami-configuration-dbus + jami-configuration-enable-logging? + jami-configuration-debug? + jami-configuration-auto-answer? + jami-configuration-accounts + + jami-service-type + + murmur-configuration make-murmur-configuration murmur-configuration? murmur-configuration-package @@ -74,6 +104,652 @@ murmur-service-type)) + +;;; +;;; Jami daemon. +;;; + +;;; XXX: Passing a computed-file object as the account is used for tests. +(define (string-or-computed-file? val) + (or (string? val) + (computed-file? val))) + +(define (string-list? val) + (and (list? val) + (and-map string? val))) + +(define (account-fingerprint-list? val) + (and (list? val) + (and-map account-fingerprint? val))) + +(define-maybe string-list) + +(define-maybe/no-serialization account-fingerprint-list) + +(define-maybe boolean) + +(define-maybe string) + +;;; The following serializers are used to derive an account details alist from +;;; a record. +(define (serialize-string-list _ val) + (string-join val ";")) + +(define (serialize-boolean _ val) + (format #f "~:[false~;true~]" val)) + +(define (serialize-string _ val) + val) + +;;; Note: Serialization is used to produce an account details alist that can +;;; be passed to the SET-ACCOUNT-DETAILS procedure. Fields that do not map to +;;; a Jami account 'detail' should have their serialization disabled via the +;;; 'empty-serializer' procedure. +(define-configuration jami-account + (archive + (string-or-computed-file) + "The account archive (backup) file name of the account. This is used to +provision the account when the service starts. The account archive should +@emph{not} be encrypted. It is highly recommended to make it readable only to +the @samp{root} user (i.e., not in the store), to guard against leaking the +secret key material of the Jami account it contains." + empty-serializer) + (allowed-contacts + (maybe-account-fingerprint-list 'disabled) + "The list of allowed contacts for the account, entered as their 40 +characters long fingerprint. Messages or calls from accounts not in that list +will be rejected. When unspecified, the configuration of the account archive +is used as-is with respect to contacts and public inbound calls/messaging +allowance, which typically defaults to allow any contact to communicate with +the account." + empty-serializer) + (moderators + (maybe-account-fingerprint-list 'disabled) + "The list of contacts that should have moderation privileges (to ban, mute, +etc. other users) in rendezvous conferences, entered as their 40 characters +long fingerprint. When unspecified, the configuration of the account archive +is used as-is with respect to moderation, which typically defaults to allow +anyone to moderate." + empty-serializer) + ;; The serializable fields below are to be set with set-account-details. + (rendezvous-point? + (maybe-boolean 'disabled) + "Whether the account should operate in the rendezvous mode. In this mode, +all the incoming audio/video calls are mixed into a conference. When left +unspecified, the value from the account archive prevails.") + (peer-discovery? + (maybe-boolean 'disabled) + "Whether peer discovery should be enabled. Peer discovery is used to +discover other OpenDHT nodes on the local network, which can be useful to +maintain communication between devices on such network even when the +connection to the the Internet has been lost. When left unspecified, the +value from the account archive prevails.") + (bootstrap-hostnames + (maybe-string-list 'disabled) + "A list of hostnames or IPs pointing to OpenDHT nodes, that should be used +to initially join the OpenDHT network. When left unspecified, the value from +the account archive prevails.") + (name-server-uri + (maybe-string 'disabled) + "The URI of the name server to use, that can be used to retrieve the +account fingerprint for a registered username.")) + +(define (jami-account->alist jami-account-object) + "Serialize the JAMI-ACCOUNT object as an alist suitable to be passed to +SET-ACCOUNT-DETAILS." + (define (field-name->account-detail name) + (match name + ('rendezvous-point? "Account.rendezVous") + ('peer-discovery? "Account.peerDiscovery") + ('bootstrap-hostnames "Account.hostname") + ('name-server-uri "RingNS.uri") + (_ #f))) + + (filter-map (lambda (field) + (and-let* ((name (field-name->account-detail + (configuration-field-name field))) + (value ((configuration-field-serializer field) + name ((configuration-field-getter field) + jami-account-object))) + ;; The define-maybe default serializer produces an + ;; empty string for the 'disabled value. + (value* (if (string-null? value) + #f + value))) + (cons name value*))) + jami-account-fields)) + +(define (jami-account-list? val) + (and (list? val) + (and-map jami-account? val))) + +(define-maybe/no-serialization jami-account-list) + +(define-configuration/no-serialization jami-configuration + (jamid + (package libring) + "The Jami daemon package to use.") + (dbus + (package dbus) + "The D-Bus package to use to start the required D-Bus session.") + (nss-certs + (package nss-certs) + "The nss-certs package to use to provide TLS certificates.") + (enable-logging? + (boolean #t) + "Whether to enable logging to syslog.") + (debug? + (boolean #f) + "Whether to enable debug level messages.") + (auto-answer? + (boolean #f) + "Whether to force automatic answer to incoming calls.") + (accounts + (maybe-jami-account-list 'disabled) + "A list of Jami accounts to be (re-)provisioned every time the Jami daemon +service starts. When providing this field, the account directories under +@file{/var/lib/jami/} are recreated every time the service starts, ensuring a +consistent state.")) + +(define %jami-accounts + (list (user-group (name "jami") (system? #t)) + (user-account + (name "jami") + (group "jami") + (system? #t) + (comment "Jami daemon user") + (home-directory "/var/lib/jami")))) + +(define (jami-configuration->command-line-arguments config) + "Derive the command line arguments to used to launch the Jami daemon from +CONFIG, a object." + (match-record config + (jamid dbus enable-logging? debug? auto-answer?) + `(,(file-append jamid "/lib/ring/dring") + "--persistent" ;stay alive after client quits + ,@(if enable-logging? + '() ;logs go to syslog by default + (list "--console")) ;else stdout/stderr + ,@(if debug? + (list "--debug") + '()) + ,@(if auto-answer? + (list "--auto-answer") + '())))) + +(define (jami-dbus-session-activation config) + "Create a directory to hold the Jami D-Bus session socket." + (with-imported-modules (source-module-closure '((gnu build activation))) + #~(begin + (use-modules (gnu build activation)) + (let ((user (getpwnam "jami"))) + (mkdir-p/perms "/var/run/jami" user #o700))))) + +(define (jami-shepherd-services config) + "Return a running the Jami daemon." + (let* ((jamid (jami-configuration-jamid config)) + (nss-certs (jami-configuration-nss-certs config)) + (dbus (jami-configuration-dbus config)) + (dbus-daemon (file-append dbus "/bin/dbus-daemon")) + (dbus-send (file-append dbus "/bin/dbus-send")) + (accounts (jami-configuration-accounts config)) + (declarative-mode? (not (eq? 'disabled accounts)))) + + (with-imported-modules (source-module-closure + '((gnu build jami-service) + (gnu build shepherd) + (gnu system file-systems))) + + (define list-accounts-action + (shepherd-action + (name 'list-accounts) + (documentation "List the available Jami accounts. Return the account +details alists keyed by their account username.") + (procedure + #~(lambda _ + (parameterize ((%send-dbus-binary #$dbus-send) + (%send-dbus-bus "unix:path=/var/run/jami/bus") + (%send-dbus-user "jami") + (%send-dbus-group "jami")) + ;; Print the accounts summary or long listing, according to + ;; user-provided option. + (let* ((usernames (get-usernames)) + (accounts (map-in-order username->account usernames))) + (match accounts + (() ;empty list + (format #t "There is no Jami account available.~%")) + ((one two ...) + (format #t "The following Jami accounts are available:~%") + (for-each + (lambda (account) + (define fingerprint (assoc-ref account + "Account.username")) + (define human-friendly-name + (or (assoc-ref account + "Account.registeredName") + (assoc-ref account + "Account.displayName") + (assoc-ref account + "Account.alias"))) + (define disabled? + (and=> (assoc-ref account "Account.enable") + (cut string=? "false" <>))) + + (format #t " - ~a~@[ (~a)~] ~:[~;[disabled]~]~%" + fingerprint human-friendly-name disabled?)) + accounts) + (display "\n"))) + ;; Return the account-details-list alist. + (map cons usernames accounts))))))) + + (define list-account-details-action + (shepherd-action + (name 'list-account-details) + (documentation "Display the account details of the available Jami +accounts in the @code{recutils} format. Return the account details alists +keyed by their account username.") + (procedure + #~(lambda _ + (parameterize ((%send-dbus-binary #$dbus-send) + (%send-dbus-bus "unix:path=/var/run/jami/bus") + (%send-dbus-user "jami") + (%send-dbus-group "jami")) + (let* ((usernames (get-usernames)) + (accounts (map-in-order username->account usernames))) + (for-each (lambda (account) + (display (account-details->recutil account)) + (display "\n\n")) + accounts) + (map cons usernames accounts))))))) + + (define list-contacts-action + (shepherd-action + (name 'list-contacts) + (documentation "Display the contacts for each Jami account. Return +an alist containing the contacts keyed by the account usernames.") + (procedure + #~(lambda _ + (parameterize ((%send-dbus-binary #$dbus-send) + (%send-dbus-bus "unix:path=/var/run/jami/bus") + (%send-dbus-user "jami") + (%send-dbus-group "jami")) + (let* ((usernames (get-usernames)) + (contacts (map-in-order username->contacts usernames))) + (for-each (lambda (username contacts) + (format #t "Contacts for account ~a:~%" + username) + (format #t "~{ - ~a~%~}~%" contacts)) + usernames contacts) + (map cons usernames contacts))))))) + + (define list-moderators-action + (shepherd-action + (name 'list-moderators) + (documentation "Display the moderators for each Jami account. Return +an alist containing the moderators keyed by the account usernames.") + (procedure + #~(lambda _ + (parameterize ((%send-dbus-binary #$dbus-send) + (%send-dbus-bus "unix:path=/var/run/jami/bus") + (%send-dbus-user "jami") + (%send-dbus-group "jami")) + (let* ((usernames (get-usernames)) + (moderators (map-in-order username->moderators + usernames))) + (for-each + (lambda (username moderators) + (if (username->all-moderators? username) + (format #t "Anyone can moderate for account ~a~%" + username) + (begin + (format #t "Moderators for account ~a:~%" username) + (format #t "~{ - ~a~%~}~%" moderators)))) + usernames moderators) + (map cons usernames moderators))))))) + + (define add-moderator-action + (shepherd-action + (name 'add-moderator) + (documentation "Add a moderator for a given Jami account. The +MODERATOR contact must be given as its 40 characters fingerprint, while the +Jami account can be provided as its registered USERNAME or fingerprint. + +@example +herd add-moderator jami 1dbcb0f5f37324228235564b79f2b9737e9a008f username +@end example + +Return the moderators for the account known by USERNAME.") + (procedure + #~(lambda (_ moderator username) + (parameterize ((%send-dbus-binary #$dbus-send) + (%send-dbus-bus "unix:path=/var/run/jami/bus") + (%send-dbus-user "jami") + (%send-dbus-group "jami")) + (set-all-moderators #f username) + (add-contact moderator username) + (set-moderator moderator #t username) + (username->moderators username)))))) + + (define ban-contact-action + (shepherd-action + (name 'ban-contact) + (documentation "Ban a contact for a given or all Jami accounts, and +clear their moderator flag. The CONTACT must be given as its 40 characters +fingerprint, while the Jami account can be provided as its registered USERNAME +or fingerprint, or omitted. When the account is omitted, CONTACT is banned +from all accounts. + +@example +herd ban-contact jami 1dbcb0f5f37324228235564b79f2b9737e9a008f [username] +@end example") + (procedure + #~(lambda* (_ contact #:optional username) + (parameterize ((%send-dbus-binary #$dbus-send) + (%send-dbus-bus "unix:path=/var/run/jami/bus") + (%send-dbus-user "jami") + (%send-dbus-group "jami")) + (let ((usernames (or (and=> username list) + (get-usernames)))) + (for-each (lambda (username) + (set-moderator contact #f username) + (remove-contact contact username #:ban? #t)) + usernames))))))) + + (define list-banned-contacts-action + (shepherd-action + (name 'list-banned-contacts) + (documentation "List the banned contacts for each accounts. Return +an alist of the banned contacts, keyed by the account usernames.") + (procedure + #~(lambda _ + (parameterize ((%send-dbus-binary #$dbus-send) + (%send-dbus-bus "unix:path=/var/run/jami/bus") + (%send-dbus-user "jami") + (%send-dbus-group "jami")) + + (define banned-contacts + (let ((usernames (get-usernames))) + (map cons usernames + (map-in-order (lambda (x) + (receive (_ banned) + (username->contacts x) + banned)) + usernames)))) + + (for-each (match-lambda + ((username . banned) + (unless (null? banned) + (format #t "Banned contacts for account ~a:~%" + username) + (format #t "~{ - ~a~%~}~%" banned)))) + banned-contacts) + banned-contacts))))) + + (define enable-account-action + (shepherd-action + (name 'enable-account) + (documentation "Enable an account. It takes USERNAME as an argument, +either a registered username or the fingerprint of the account.") + (procedure + #~(lambda (_ username) + (parameterize ((%send-dbus-binary #$dbus-send) + (%send-dbus-bus "unix:path=/var/run/jami/bus") + (%send-dbus-user "jami") + (%send-dbus-group "jami")) + (enable-account username)))))) + + (define disable-account-action + (shepherd-action + (name 'disable-account) + (documentation "Disable an account. It takes USERNAME as an +argument, either a registered username or the fingerprint of the account.") + (procedure + #~(lambda (_ username) + (parameterize ((%send-dbus-binary #$dbus-send) + (%send-dbus-bus "unix:path=/var/run/jami/bus") + (%send-dbus-user "jami") + (%send-dbus-group "jami")) + (disable-account username)))))) + + (list (shepherd-service + (documentation "Run a D-Bus session for the Jami daemon.") + (provision '(jami-dbus-session)) + (modules `((gnu build shepherd) + (gnu build jami-service) + (gnu system file-systems) + ,@%default-modules)) + ;; The requirement on dbus-system is to ensure other required + ;; activation for D-Bus, such as a /etc/machine-id file. + (requirement '(dbus-system syslogd)) + (start + #~(lambda args + (define pid + ((make-forkexec-constructor/container + (list #$dbus-daemon "--session" + "--address=unix:path=/var/run/jami/bus" + "--nofork" "--syslog-only" "--nopidfile") + #:mappings (list (file-system-mapping + (source "/dev/log") ;for syslog + (target source)) + (file-system-mapping + (source "/var/run/jami") + (target source) + (writable? #t))) + #:user "jami" + #:group "jami" + #:environment-variables + ;; This is so that the cx.ring.Ring service D-Bus + ;; definition is found by dbus-send. + (list (string-append "XDG_DATA_DIRS=" + #$jamid "/share"))))) + + ;; XXX: This manual synchronization probably wouldn't be + ;; needed if we were using a PID file, but providing it via a + ;; customized config file with would not override + ;; the one inherited from the base config of D-Bus. + (let ((sock (socket PF_UNIX SOCK_STREAM 0))) + (with-retries 20 1 (catch 'system-error + (lambda () + (connect sock AF_UNIX + "/var/run/jami/bus") + (close-port sock) + #t) + (lambda args + #f)))) + + pid)) + (stop #~(make-kill-destructor))) + + (shepherd-service + (documentation "Run the Jami daemon.") + (provision '(jami)) + (actions (list list-accounts-action + list-account-details-action + list-contacts-action + list-moderators-action + add-moderator-action + ban-contact-action + list-banned-contacts-action + enable-account-action + disable-account-action)) + (requirement '(jami-dbus-session)) + (modules `((ice-9 format) + (ice-9 ftw) + (ice-9 match) + (ice-9 receive) + (srfi srfi-1) + (srfi srfi-26) + (gnu build jami-service) + (gnu build shepherd) + (gnu system file-systems) + ,@%default-modules)) + (start + #~(lambda args + (define (delete-file-recursively/safe file) + ;; Ensure we're not deleting things outside of + ;; /var/lib/jami. This prevents a possible attack in case + ;; the daemon is compromised and an attacker gains write + ;; access to /var/lib/jami. + (let ((parent-directory (dirname file))) + (if (eq? 'symlink (stat:type (stat parent-directory))) + (error "abnormality detected; unexpected symlink found at" + parent-directory) + (delete-file-recursively file)))) + + (when #$declarative-mode? + ;; Clear the Jami configuration and accounts, to enforce the + ;; declared state. + (catch #t + (lambda () + (for-each (cut delete-file-recursively/safe <>) + '("/var/lib/jami/.cache/jami" + "/var/lib/jami/.config/jami" + "/var/lib/jami/.local/share/jami" + "/var/lib/jami/accounts"))) + (lambda args + #t)) + ;; Copy the Jami account archives from somewhere readable + ;; by root to a place only the jami user can read. + (let* ((accounts-dir "/var/lib/jami/accounts/") + (pwd (getpwnam "jami")) + (user (passwd:uid pwd)) + (group (passwd:gid pwd))) + (mkdir-p accounts-dir) + (chown accounts-dir user group) + (for-each (lambda (f) + (let ((dest (string-append accounts-dir + (basename f)))) + (copy-file f dest) + (chown dest user group))) + '#$(and declarative-mode? + (map jami-account-archive accounts))))) + + ;; Start the daemon. + (define daemon-pid + ((make-forkexec-constructor/container + '#$(jami-configuration->command-line-arguments config) + #:mappings + (list (file-system-mapping + (source "/dev/log") ;for syslog + (target source)) + (file-system-mapping + (source "/var/lib/jami") + (target source) + (writable? #t)) + (file-system-mapping + (source "/var/run/jami") + (target source) + (writable? #t)) + ;; Expose TLS certificates for GnuTLS. + (file-system-mapping + (source #$(file-append nss-certs "/etc/ssl/certs")) + (target "/etc/ssl/certs"))) + #:user "jami" + #:group "jami" + #:environment-variables + (list (string-append "DBUS_SESSION_BUS_ADDRESS=" + "unix:path=/var/run/jami/bus") + ;; Expose TLS certificates for OpenSSL. + "SSL_CERT_DIR=/etc/ssl/certs")))) + + (parameterize ((%send-dbus-binary #$dbus-send) + (%send-dbus-bus "unix:path=/var/run/jami/bus") + (%send-dbus-user "jami") + (%send-dbus-group "jami")) + + ;; Wait until the service name has been acquired by D-Bus. + (with-retries 20 1 + (dbus-service-available? "cx.ring.Ring")) + + (when #$declarative-mode? + ;; Provision the accounts via the D-Bus API of the daemon. + (let* ((jami-account-archives + (map (cut string-append + "/var/lib/jami/accounts/" <>) + (scandir "/var/lib/jami/accounts/" + (lambda (f) + (not (member f '("." ".."))))))) + (usernames (map-in-order (cut add-account <>) + jami-account-archives))) + + (define (archive-name->username archive) + (list-ref + usernames + (list-index (lambda (f) + (string-suffix? (basename archive) f)) + jami-account-archives))) + + (for-each + (lambda (archive allowed-contacts moderators + account-details) + (let ((username (archive-name->username + archive))) + (when (not (eq? 'disabled allowed-contacts)) + ;; Reject calls from unknown contacts. + (set-account-details + '(("DHT.PublicInCalls" . "false")) username) + ;; Remove all contacts. + (for-each (cut remove-contact <> username) + (username->contacts username)) + ;; Add allowed ones. + (for-each (cut add-contact <> username) + allowed-contacts)) + (when (not (eq? 'disabled moderators)) + ;; Disable the 'AllModerators' property. + (set-all-moderators #f username) + ;; Remove all moderators. + (for-each (cut set-moderator <> #f username) + (username->moderators username)) + ;; Add declared moderators. + (for-each (cut set-moderator <> #t username) + moderators)) + ;; Set the various account parameters. + (set-account-details account-details username))) + '#$(and declarative-mode? + (map-in-order (cut jami-account-archive <>) + accounts)) + '#$(and declarative-mode? + (map-in-order + (cut jami-account-allowed-contacts <>) + accounts)) + '#$(and declarative-mode? + (map-in-order (cut jami-account-moderators <>) + accounts)) + '#$(and declarative-mode? + (map-in-order jami-account->alist accounts)))))) + + ;; Finally, return the PID of the daemon process. + daemon-pid)) + (stop + #~(lambda (pid . args) + (kill pid SIGKILL) + ;; Wait for the process to exit; this prevents overlapping + ;; processes when issuing 'herd restart'. + (waitpid pid) + #f))))))) + +(define jami-service-type + (service-type + (name 'jami) + (default-value (jami-configuration)) + (extensions + (list (service-extension shepherd-root-service-type + jami-shepherd-services) + (service-extension account-service-type + (const %jami-accounts)) + (service-extension activation-service-type + jami-dbus-session-activation))) + (description "Run the Jami daemon (@command{dring}). This service is +geared toward the use case of hosting Jami rendezvous points over a headless +server. If you use Jami on your local machine, you may prefer to setup a user +Shepherd service for it instead; this way, the daemon will be shared via your +normal user D-Bus session bus."))) + + +;;; +;;; Murmur. +;;; + ;; https://github.com/mumble-voip/mumble/blob/master/scripts/murmur.ini (define-record-type* murmur-configuration @@ -305,3 +981,7 @@ suite.") (service-extension account-service-type murmur-accounts))) (default-value (murmur-configuration)))) + +;; Local Variables: +;; eval: (put 'with-retries 'scheme-indent-function 2) +;; End: diff --git a/gnu/tests/data/jami-dummy-account.dat b/gnu/tests/data/jami-dummy-account.dat new file mode 100644 index 0000000000..0e908396ca --- /dev/null +++ b/gnu/tests/data/jami-dummy-account.dat @@ -0,0 +1,392 @@ +;;; -*- mode: scheme; -*- +;;; JSON extracted from an actual Jami account and processed with +;;; Emacs/guile-json. +(define %jami-account-content-sexp + '(("RINGCAKEY" . "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUpRd0lCQURBTkJna3F\ +oa2lHOXcwQkFRRUZBQVNDQ1Mwd2dna3BBZ0VBQW9JQ0FRQzBxWUozSkYvTzhQRGEKRnUwRnpRcHBCaD\ +gybGJMdURrNTlVU0I0MUJSaS9kdDZGV1BRN29YOVpsY25vNGZzM2dmUHQ0dU1hRVBkVFBGKwowbGN2Q\ +jc2cytQTEFlcjlOZGpVQzQ2ZXp0UnNiNE9aQXc4ZUk1M3EwSU04QWJFd0o0ZjllLzBmQUFueHgrK3Qw\ +CmZDeGV1YTBUaVBqVHBpZVJMNmpwQkd5UGI2Qk5pU2ViTkZCNzJOMTBnbzI4REVQYlhkNE9CNkN1blZ\ +5RGZKSU0KOC9PRy8rMndNamI4WkRwT3JrYy93U2ZHbnQyZXA3U0xKSkgwOFIzV1FKWklsSndrcGdLTH\ +FRakVwWFNpclN4dAozSkdtdXdBdE9LaXFFTXh1R043elV3ZlNINEtHdkRaUFNkZklZVXJ3eEp4aDJZZ\ +3lobG5RRC9SSnhRN3d4YlJBCjFhMUZVV0FzbDhLODk5cEtESk1GL09VOWZMRUx6QlViblpaRDRmSlg1\ +NmcyTlluUnJobS94NG9FbFk0MFNYMUUKcHYzb01hNnZrVGN5RjJnUFhKL2FkUVJoS0dFaGRjaHBpeDl\ +5UVphaDFCUFBGYW5jNzBMcjhOaDZJeHFNQ1hiMQozMG9vWHpWZmZNMVFOd28rL3hzRnBlRkRqUTAxQ0\ +9pdWZocitKREcyc0txb0o0V0JwYVhubWI1YXVrVWUvV1RKCjAxVmRyaEkvSVExd3V4QzNMMnpac3dVU\ +1NTaDk0aXg1M0hpU3pWbkI5UkxmaVhZUUVCcFEyNHVoRTdiYlo0bm0KZTczZC9zenpPTXMzYUt3OWtW\ +a2VLMTVtYWhSVWZjdEdhSVQxTkhGWUNYYXByaWExakdNdVpmSk1pSUtZUzNidQpMbUhZckF6dEptNDZ\ +0aHpjdnN3NHlhMnFoa2xUUlFJREFRQUJBb0lDQVFDaHZaUm85KzZ5aFhFTHZ6U3FXZHcxCkZGOERibG\ +hIMmhVWkNuV0kxMDM5SmdyRkxMczFSU1krSzg1aFZYMk9hV1VTNk44TmNCYzUyL1hrdFltS09HUFQKM\ +WZqMnE2M3pPcDNSSFdGNWVPMXhNeExRN3JZSDhqMGZZTFFTUytKemdwb3ZRVnJLSXkrb21JSSt3aUN6\ +R1laRApGQUM0ODJzL0J5MHdtRjVjdC9JTEdIeVY3ZXNVUlo1Vi9iL0ltQzUwQ1lDUWpQR2xBb3JkeUx\ +1MHp2NjZZUXc2CkQycTg0VHAyVUg3SExEVmhFNytUbDg4Q04xWll0VGtpSkthbkNpMFVmbStPKzJFM0\ +5HM01hajk1aDl2NktqYkoKUlkxeTNDRTVmQmkyUFNLbVVzRjN3SzdhbzJDRks5MTgybmlxL2FaNm5WO\ +Xc3NmVrRjhEOWUvS1pqUE5ZT0xkaApFczBSL2laV3RpbUx2RHdXQWNWNFNnSFFjNXJvNU9yOEFUS1ZK\ +VmlzZGFuWXkvdUhmVXZWN3U5cDVLK2E4SHU2CllabW13ZTh4bnF5M3V2M0VabE9LY20zTnZvWllVMnJ\ +HUUFQQW1sWWQ4WlRsZGxPa1JCSGxxYzllMmJuSnNTQW8KNUhhS0N3aDJsWmZpalVGNXFrMXNQcm1kN3\ +BlMld2VVV3QmVuSjJnS1ZoTE5VVGtHWmtTWGhzNlV3WWRRMjVtRQppQzl6WjhXNkQ2OXBvb0lsTTVXT\ +01ySEs0Rng1ck9vT05kUHQ4NEk1bTI3cnpnbXM4QnJXVUlGLytZZjJ0bGdmClBIR0V4c3ZCK3JRQk52\ +WHU3dXoxcVdFTlJTL2YwR2E3dVF4ZW5sZ2dubHc5M1pNOW1GWXpXb1RpdWFmdnphTnAKWEsrTEVrV2F\ +RYUs1Q0VaNEhmUlhBUUtDQVFFQTUyK240OUxQODlyQkR2bFdsTkxNanJqTDdSb0xyQ3FVZGpQWQpyT1\ +hZS3ZkTkxyS2NTc1hNdkY4YW5HQng1UG5oVDZGY05ic2dzQ3BUUXowMThZYmcrbUUxQno5ZTdFNTJGZ\ +i9NCk9BbWZqSllXUnZueUtiNnB3SGlvOHlXUXlVVk1zZU1CcmpvWk1kNkpPZEZ0K2JITHBWOS9iSkdR\ +a3NTRE04WTkKbWxGQUlUL0gyNTh1K1ZKTWsrT0prU28zZmJQSk5Ja1Q2WVBKVmNaSnZTRGI5QU83WDF\ +lendCOXVRL0FEblZ6YwpSQkJOUVZaTStZS2ZNWFJBdmFuWnlmWFFwaUxCQW8rRVRPSHJCR1dDRUhtSF\ +RCaEZIMTkyamtxNlcrTStvS0R1Cm1xMitMc2hZWTVFc2NpL2hPOVZjK2FCM0hhaGliME03M092MHFNc\ +WZoTncyU1BncHdRS0NBUUVBeDlaR1gxQnQKL3MwdGtNcGV1QWhlWjFqTklnZmFEY0RRTWlmU0k4QjRx\ +WUhiL1hOREQ5NjFQME9zMDdCN2wzNE1iZ3U2QlNwcwpXdSt0Y1hjSFlqQVJUc0Qzd0pSaVRIb0RSQzR\ +YYkxEa2pHRUVCVzRKbFVqZTA1QWZrU0QrdkZSMkJwZStxQlBLCm5yb3Mwd1BWL3RXa3MzY2VFOUlBTV\ +pWWDhQeFA3RVNXbitVZDJEWkZhcVFLb0JybHZXRXhxelpYUEJSVjhoS3QKcFBqWnFkZXFQLzhUZTBtS\ +zh5MEVreXVXOWhFdGZ2Sm5HWXhMNStrSG9xd2hQVk1tODZ5YlZNVHRQYWJTdCtPUwp0WHhJTE9RMWRN\ +QkFabzRxSnNkUUZJcTJnSHA5WFYwa2ZNUms1ajdJT1Q4c2Z6TlpKVkRNK1k5VHVlSGJXSnduCnZsWld\ +VZ2NVZTlBaWhRS0NBUUVBbFJaK2h1ckUvNGdLR2dWUld5bTRrTEJHM2dTTFJHdGhuQXVtSnlzaFoveE\ +wKZ2l1Wk55bll5L2hRQWpDMjdoUnlxb04rRFRid3hjdGVPOUJ3c1poNzBZOVJROHYwOERGVExMVE43O\ +E56UG5OcApBbXY5TGhzZTYxaFBMZU1qTkNVcVZPV3hyWFRMeWk1YkpCM2Z4SnhlWGJmNU5BMUpudUpz\ +eXF1SC82TWJ0cytKCmhkY3p3WFRjMCtBZVBKOS9nOENQZXdKYkMzRFVBQ2R1VlNHWHo4ZWZxcm1xbDd\ +jbnB5ZzBpK2pJRkNpVU8rVEcKVFcxeDg3KzUvUFF2MGtSQ0Z1UUloZ2ZCNkcwWW9vcHBrUWRZdXhKZl\ +pPaHdUUldpbTVMMlF5K294WWZySGVQOQozSlltbGFCMmJiN3kxL1FoQjcvek9VMk1nTEtYdHl4Z09ve\ +EpoQlFwZ1FLQ0FRQkIwSUE4dy9CMkNuMEhRcDhQClhUSTZOelRZRUYzd1NhQkg1SFdBOE5MTWdNaERJ\ +TUxsWnlPcVFrK1pLSGFMM2llWjFxTGRNS3VmQjNESC9idWcKeXRQb2JBVXNsN0lJSGVjVmZWaVpvMml\ +pRXhHUCtEMlB2UUFtRFVGWU90V3FrT2FPSlV2VmJ5ODhOM1NyeW9lZgo5aHpZUGxMWmxFQWNGR055S3\ +FibjJXOENHaU5LSWhXYW1Zd21UclY3T1pkeUcrTi9GZk40Vms1NkZyc1pCTDQ5CmRYU2xGZ045TTBaZ\ +WNleTEvZEpPRE9lSHNuME5VK0gvNFZEUk1hR1NmelpwSkxJOXE4T2FiSWpVM0ttb24wQTcKdzFWeWNU\ +L1FwYlBxRUFVckt5dytvMzV3MlAyaUZ1czZiMlBvUUxFTGFTRVl6K3R6UEw5UTM1ejNRdGdMQytuagp\ +IUmxCQW9JQkFES0Q4NGhrYkphczlIQ00zanNNSU9kb213ZEMvcktxVUxKNHFWZU5QR0xDY2VpNEdocn\ +dlQnNICnNoN0hibFlZSDN2U2Y3RW1iZEVCb2xlWVJsaUx5ZzJDQU1ZOGpNK0lpUEwydk1yM2Y3SzZPT\ +mU0UkVPUFJSZkcKWlJDcTh4a0ZPQlg1SUJUbkVCV3QzdVdyb1NGY2x4RTdUa2I4VkUyVzExTG9ZNlkw\ +TUNPaHdxN21xYXRUVnNrawpTRDNySmkrTFR6a2Y4OEx1bjZZNjdiaFNOTWpKZkFaUXNQc0FTRkJBUTJ\ +rQnE5alRLZGVuaU4yYTJIbm0xNCtrCnJDeU9ZVE14Q2hQbWNpS25pVy9MWnFUL0U1dlNRUGdBVzc0dT\ +VLazJoSjRBajNjRW9NVEwxSytZbStWYWh2U0cKTi8xOFdYQ1JRQkg1d0p2eXJYczBtT29GQlRnTWg4d\ +z0KLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLQo=") + ("ringAccountKey" . "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUpRUUlCQURBTk\ +Jna3Foa2lHOXcwQkFRRUZBQVNDQ1Nzd2dna25BZ0VBQW9JQ0FRRDNCdDRnOUVUdk9EVnYKM3hWV0ZlS\ +1Nnbk5uVEF3S2dYa3IrQ1FhdU8vTGZWM01RenNSNHliL3hoaWhhb1Z2c2FtZ3ZRU1haL0M1R3I5QQpE\ +YlAxbHNHclRCK1pXMC9uMXVEb3hmVWdZRWY3SGtVanJtdVFjUGlFWGlUVkNiY002U0NzdVNrMnRxdE1\ +BNjBNClRacVo2LzAvbkEvblR5TnFNaUJNSmFRN0NUb2xOTTh2Z2tvd0tBRW14OGpJUG9YZEttMnd6MD\ +Z6SnhwU1d4WjUKc0FBTkdSSHU0b2ZXNWJiMERvamtnRzFBYUJ6Nm9uSmdKK1JFSWV1UkpNQWFHYmtzW\ +TQ4Z1Z1b21BVTU0UFNvUApFb3psVGdHd1k0cnhJTGE0V1Y0RVZMVExrR240ZTYrRUYrSjIvRURBUFdN\ +bXREdFpUeURCc2t5bStLaDRUT2xPCjdnN3JlUUhLdDd1R21YU0RnbzVKZ2hOOHNVRFUxL1Z2YmFFcUZ\ +tTDJrZWNGOVlVZmNsUWRGY1ZncmIrMkh5R3AKRVc0b3RkZjlYYzhOMWxrbGk1dFBqRGZuQ1U3OHB6QT\ +BxQmV1SWhZTnF1VjhGSm1NemhXeVFDbE1MUEFYbXFVdwpWYlY0MWduM0NkektuUlVhZXFONXlzOG5KQ\ +mRJNDBleWlYRUlvU0VKcFpyT1Z5ck1icnNCQzltaVFpZUFhSnlBClFvcjFaRGlwMkpZNUFza2phUGQ0\ +NGk0MGNkeFpob1RhNnpzako5UjZScllFYjhWTmZQemFLaElwSXJSd2NpbCsKWjFrbUUwSE1kY21ITXR\ +hbWZhK0l6WUR4dDV6OGVOZys1RGpVVG1MRkdyQUVWRW5hNDcxdllxYnk5UCs1T1cvNQpSdkxtUER2Tj\ +dlUURpTlZENlBYRFk5OTY4bTZaaHdJREFRQUJBb0lDQURjOEkrTCtlNE41OEFqcHV0MmEyeVNqCllxY\ +VFUSWowMW1GTWhOWXMwQUdTTUswQncyMkdleXZwNFl3R1EzdnNIOSsvSkEydXdoYkJzazNpUW9FQmlx\ +Q0EKenZmOWdPcDRFNlk0elV6RitwSmQvRnUwSG4wWHBab0Rhdnp2eFN4djNFeUN3b0puYWZuL1FHeGw\ +xZEhoQUtsKwpmZGZjekRCc3NPZ1Y2cGtBd1MyY2wwOHFOT2g3cVhaQWFkYk1sQ1lWM0owU1hhaVZiNz\ +lHZXNvTzNwUVBMUUZiClNjQjFjT2sxYnNxWkpOU244d0xmMis5QVBEdzMwWEtNNHg5eTdRTE42Q3oxQ\ +WpvcFJLQ0NIS3R1SEc4UmVETTIKcnRTbjJmTnltQ0VqeDZGVTB6MHFldDV3Y01UbU5weEZuYXdEMU5s\ +dFpnZXBsSllwTjVKZXNEUmo2cFlnWXBPKwo5UDU2cEdtZVNTVzVxcHRoWFFLQmFsdy8wWXp2YUlYdHp\ +hTnZyNUJzRFNrWkU3cldzODIvQmFzUE1RckFmOGpLClZFMU9pSzcrVllUVTRFVWVoZ0FZOXdzNjFqYk\ +lSSEpQbW9VQXpkWHcyL0d0Vk9JUTFwUFJnOXNYN0JaMmUyV1YKdmd5aThPUEJxRWtwblBiMkU5Z0d1U\ +25rY01OeWFVbUl0c3pFandadW14dnZrUW5HanlTb3pjY2R3dnNQNnBJagpoN0g5VUNQTHdOM0N5N1lp\ +UmliSlZBWlNjZkF6QzhubXNLODQrTzJUZHBzTXk0SEZDMmM4dlZiclpteTVkWC9qCk1ESnBzS25JWlZ\ +JMmpXSzRpRS82aUdIWVdoY3JvWnYvVEJ0YW1SQUxTWDVOYkhhWTI0bXVRSG5yMldtaytld3EKbHRGbC\ +90bXgyVkpWUitMZ0JCUGhBb0lCQVFENEI0MjQyVTgvbkJ4d2RzelhCdWxBOVFTa0I5Zktud0RlRkV3S\ +wp3Nks0eU14YVdRU04ycjRxRDhLcW93OVZVMzRYdkRWbFh0RUlDaVh3Q0hZdW5IL3g3cXNSdEVzbHJM\ +aWg4UHRPClpDSU8zUml4RmlIQXFlQUh2YXF0NVhXdndaREx6WnV2THRJOTdINkZ1QjYxck5qMnhxdlR\ +IN05pUmp2S0R0WXgKR1VtNURoQ094cm9tR0NkWHRnWHJGaS9WRU1TSmpQMkM3OTNrN1pTNmNZL0Nkc1\ +RqWEsrZ1UzeWM3OC9kN1pYbwpKMGg2WFlSdmhlQW9Bd2dkVTl4MWtYL2ZmY2tZK1hUcFRwV2xXWmtlU\ +ytsejBpQkRnUlJzMm45OFZDeTZDRmRZCldsZXZaZy9SWXZ6dzlKdWFVcXArOHpHbHNXR2xuOEhtZW5X\ +Q1luSHJnNFBxQnRkL0FvSUJBUUQrOXhDL1N5ZGIKVWZxMUJHYy95YktZc3RLb3A1azB0d3M0SlUwTzN\ +aT1U3MlZ5YTdLT2lTemdPSzVnL2QzckhMMXR2dHViTDBWNAo1dEF4a1AzSkVYbmxZZU5IVkpROTc2RH\ +NGS3Ztc0FGL2JJdVBsdFBRT1dyM3g1eW1RU3lCOTBUczV0dFdWMTVQCktYYVNnMTZidDhwNS9MeERkZ\ +ng2c1YzN1o5RDFRd21EQllreVFIcWQ4clljTm9ad1M5ZnI3UTZhN1ZNSDVtT3IKbEF5dzBCYVdZQk9k\ +bjFGd0pVV3NlRlpmTy9vNUVqZk9Hd0xMR1hiOEVmQ1hqdlRYcUNHLzNrT1JvN1NkOWY1eQowVjIxMmt\ +YVVNINHNDbFB0SmwzeHpaMWJxQ2RMVDNITWNLUTk5UGFFVFppQnNXQ1lOcXg1c0Q0RGhoMzdZQ2hKCm\ +hlN3VUM1E0MElINUFvSUJBREN1VXR1b0UweloyQjhld2grbUpKdnlPMEh5cENFSnlrTE1Xd3gxejNkV\ +E9nQzEKbmhZMWk4TjNxbTZSYUk0SHdDVHFkTlI3b3ExZ1NJZnZNVHIrem9IdXBUYnBXeUorM3hJeDJU\ +Rk9wL3lnMnByUApURHFqWE94SUJycnc0WU5vaTRIa3poeTVKTnl3a1RpdnBaOWsySVMvQTdTQmNWVGx\ +raENianVDK0pPRWthSTJOClpiWGFZY1p1WElVQ3FzcTM2c3RRbCtWZUxRQWt2VjlHc0wrclRnT09Dbz\ +UrTkdRZEVZQnVoRkMzZlJzL1JhSVoKOWFBRTBFL3BTTWp1a05tTnQ2Mm1NSk1tTUdydXhnWFRRblBRR\ +npNSW43aXB2Z0hxQjRsUDM4emdsbnMvbmZVcgo1NWRuZXk3ejhMRFFETHVIc0RHd3hINzNKQjgrTVR2\ +WGFVbkNwQU1DZ2dFQVNBSGxBL0dvdXR6TFRvWmcxcDRUClI1YnhjZHBycFh5d3VYbW5hclJmY3VldG9\ +nUVNtTGpiS0xRNVk0RXZSTENJTzA5MDNENGNnOG5FTU10L01XTXoKSnZwZll3emJGU2J4THR1anRQSX\ +VhaHR3eXV2UkJIVEM1aG5FL3h0WEE1bWZLTDBHWXpzbmtubm1WL2lzSnBSZwpwZFVnSW5sWEJodkRyR\ +FlreUsvWEp0N1FZWlhlUzI5NXlUd0krZndoamlzVVBlTWEyUmRUUE9rQ01JbUVaNUhZCjJHSmZjS25H\ +SkxDVHpDKzNPcGtQazdFRE4vTUlMS2F3YVUxaGp1cVlKWVVUVmpXQzFEM2VUL1ViWHptM0VQNHMKVEN\ +uYWpCYVMzN0N2YVd4ek5JektXZS9TSXdGbEFmYWNSTHlneUR4Z3Q3bHp1akVObEtvU2xya3h3ckpEND\ +Z2WAptUUtDQVFBcTVQWWxSQjgvNnFiWWt1OTA0NUZRdXk2QWtlYXBaMW0raS9SQzZtbFRvUXB6NDlPU\ +Gs4ZGx5YjVtCndvSVhpaEo2V05jN1RsWlRYMnpQTTRBS0M5VFNBUWJrWDg5bjEyU2VDSUlHbXVINnk0\ +TjZiY2lxZjVVcSsvc0IKcHJKeFRNYlRSUFpqS0VVd1N0SFg1MUQ1bi9sQnZERGY3Y2VEZytsYlE0RjR\ +KMTlPd09oZ1lGcjFheGQvNXd2VgpURjNoVlQwbFZGN2RyRC9iMHZOcmxnbUNjbEk4UDg1a2dkRUhZbG\ +ZtTFoxeXJIMkNXVy9SS0lsWk9ZdFVuNFNpCkp5a2VlNDROWElXU3ovalRBdFRta3VQTzRvUjF5d3dRc\ +jdhUTF5a3hRVm9rVm5vY2xqU0tyQlk4R294a0I0eDIKUDNrb3F1UnkvcUd3QzBnN1o4ZjBTQjNQZVZt\ +eQotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0tCg==") + ("ringAccountCert" . "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZXakNDQTBLZ\ +0F3SUJBZ0lJRm1tNmZuaWRndEl3RFFZSktvWklodmNOQVFFTUJRQXdUREVRTUE0R0ExVUUKQXhNSFNt\ +RnRhU0JEUVRFNE1EWUdDZ21TSm9tVDhpeGtBUUVUS0RjNVpqSTJaVFZpWTJNeU9EWXlPREppT0dFMwp\ +PRFF6TUdOak1EWXpNakV4T1RFNFkyWm1PVGd3SGhjTk1qRXdOREUyTVRjek1qRXdXaGNOTXpFd05ERT\ +BNVGN6Ck1qRXdXakJKTVEwd0N3WURWUVFERXdSS1lXMXBNVGd3TmdZS0NaSW1pWlB5TEdRQkFSTW9aa\ +k16TkRWbU1qYzMKTldSa1ptVXdOMkUwWWpCa09UVmtZV1ZoTVRFeFpERTFabUpqTVRFNU9UQ0NBaUl3\ +RFFZSktvWklodmNOQVFFQgpCUUFEZ2dJUEFEQ0NBZ29DZ2dJQkFQY0czaUQwUk84NE5XL2ZGVllWNHB\ +LQ2MyZE1EQXFCZVN2NEpCcTQ3OHQ5ClhjeERPeEhqSnYvR0dLRnFoVyt4cWFDOUJKZG44TGthdjBBTn\ +MvV1d3YXRNSDVsYlQrZlc0T2pGOVNCZ1Ivc2UKUlNPdWE1QncrSVJlSk5VSnR3enBJS3k1S1RhMnEwd\ +0RyUXhObXBuci9UK2NEK2RQSTJveUlFd2xwRHNKT2lVMAp6eStDU2pBb0FTYkh5TWcraGQwcWJiRFBU\ +ck1uR2xKYkZubXdBQTBaRWU3aWg5Ymx0dlFPaU9TQWJVQm9IUHFpCmNtQW41RVFoNjVFa3dCb1p1U3h\ +qanlCVzZpWUJUbmc5S2c4U2pPVk9BYkJqaXZFZ3RyaFpYZ1JVdE11UWFmaDcKcjRRWDRuYjhRTUE5WX\ +lhME8xbFBJTUd5VEtiNHFIaE02VTd1RHV0NUFjcTN1NGFaZElPQ2prbUNFM3l4UU5UWAo5Vzl0b1NvV\ +1l2YVI1d1gxaFI5eVZCMFZ4V0N0djdZZklha1JiaWkxMS8xZHp3M1dXU1dMbTArTU4rY0pUdnluCk1E\ +U29GNjRpRmcycTVYd1VtWXpPRmJKQUtVd3M4QmVhcFRCVnRYaldDZmNKM01xZEZScDZvM25Lenlja0Y\ +wamoKUjdLSmNRaWhJUW1sbXM1WEtzeHV1d0VMMmFKQ0o0Qm9uSUJDaXZWa09LbllsamtDeVNObzkzam\ +lMalJ4M0ZtRwpoTnJyT3lNbjFIcEd0Z1J2eFUxOC9Ob3FFaWtpdEhCeUtYNW5XU1lUUWN4MXlZY3kxc\ +Vo5cjRqTmdQRzNuUHg0CjJEN2tPTlJPWXNVYXNBUlVTZHJqdlc5aXB2TDAvN2s1Yi9sRzh1WThPODN0\ +NUFPSTFVUG85Y05qMzNyeWJwbUgKQWdNQkFBR2pRekJCTUIwR0ExVWREZ1FXQkJUek5GOG5kZDMrQjZ\ +TdzJWMnVvUkhSWDd3Um1UQVBCZ05WSFJNQgpBZjhFQlRBREFRSC9NQThHQTFVZER3RUIvd1FGQXdNSE\ +JnQXdEUVlKS29aSWh2Y05BUUVNQlFBRGdnSUJBRDMrCjlscFluMjZyeG5pekY2UkNvZFFFVmgvOVF2R\ +Wp1V3dHUWZqa3gxb3VlVjdDMzUyWnpIT2hWU3VGcG43TUxkVFcKamI2dWhMRkpoMWtlTDlYZ0pHalMy\ +V0UwdDlJUGp2UWx5UHIwRWJoWGRJNDJMYUR0NDk0dWQ2MEE0bWg0bW1zbwovcjY2NERKOWMwUjZBOUd\ +QZUJjWi9zekhMQjJ4VmM5M3hYNjVjcmNoVTFLMDBVK2ZZUWtHS0xidFFXeUZzdnlKCmRHdFRxamVlYV\ +VNeGt5dFFWNWFxVHJ1SG4vV3U2RWRZejYrZ0ViQXJHUURRK1J3QmMxMDNGcWRIU0xReWdEWHoKd0pNV\ +GhDREdBVmR6V2NCemJYL1JIZms5bTZzK01HblNJUFBrNG9FOUdFbGdQc2JUZ2FrTzc3aElUdzRxZXJi\ +TAp2M0tiaTVOeVZZVFNVa3hDb0VRSkIrWlZJT3BVNFhRdVAvNkZkWldBRk1LU0Jkd1JLcnRmT1hZT1E\ +xcVBvYU1uCjZPR1VGMU0rYWZ0dTNCMkNhZ2ZMaE5hbVBoSjdxWTNSMzJhK2VRVllvYWlESXZKMElIUH\ +FnQ0NuWXlIaVBHTjQKS3VvaE9IZFpXTkxOUXdIQTg3SDV5NEwrSGZISmdFZFppdWYrbTNMa3JJcnN5M\ +WFoUjgyaEpZVkhreHVyZVRDcQpIR2NJaUIvTHpkSG91WFVrWGNrNjRvWXVRQnM3ZE9KVEx6bGlibU8x\ +elRzVWRoc0d4dE9zc2lYWkFjUURmZHBnCjNHdFJ3UkRwd0ZtL2k5TC9UWjMzMjBwY0VZd21aN1dBSzJ\ +Ra3Z6eWZlelRGeXdLWmh3c0RQRkwycllzLzhXdWsKRk1sek5BVmRMTytTS1RxeGlkM0l1elFaQ0FwU0\ +5ybTJMY2ZVN2grWAotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tQkVHSU4gQ0VSVElGSUNBV\ +EUtLS0tLQpNSUlGWFRDQ0EwV2dBd0lCQWdJSXV0T3MxUXBsSVRvd0RRWUpLb1pJaHZjTkFRRU1CUUF3\ +VERFUU1BNEdBMVVFCkF4TUhTbUZ0YVNCRFFURTRNRFlHQ2dtU0pvbVQ4aXhrQVFFVEtEYzVaakkyWlR\ +WaVkyTXlPRFl5T0RKaU9HRTMKT0RRek1HTmpNRFl6TWpFeE9URTRZMlptT1Rnd0hoY05NakV3TkRFMk\ +1UY3pNakExV2hjTk16RXdOREUwTVRjegpNakExV2pCTU1SQXdEZ1lEVlFRREV3ZEtZVzFwSUVOQk1UZ\ +3dOZ1lLQ1pJbWlaUHlMR1FCQVJNb056bG1NalpsCk5XSmpZekk0TmpJNE1tSTRZVGM0TkRNd1kyTXdO\ +ak15TVRFNU1UaGpabVk1T0RDQ0FpSXdEUVlKS29aSWh2Y04KQVFFQkJRQURnZ0lQQURDQ0Fnb0NnZ0l\ +CQUxTcGduY2tYODd3OE5vVzdRWE5DbWtHSHphVnN1NE9UbjFSSUhqVQpGR0w5MjNvVlk5RHVoZjFtVn\ +llamgremVCOCszaTR4b1E5MU04WDdTVnk4SHZxejQ4c0I2djAxMk5RTGpwN08xCkd4dmc1a0REeDRqb\ +mVyUWd6d0JzVEFuaC8xNy9SOEFDZkhINzYzUjhMRjY1clJPSStOT21KNUV2cU9rRWJJOXYKb0UySko1\ +czBVSHZZM1hTQ2pid01ROXRkM2c0SG9LNmRYSU44a2d6ejg0Yi83YkF5TnZ4a09rNnVSei9CSjhhZQo\ +zWjZudElza2tmVHhIZFpBbGtpVW5DU21Bb3VwQ01TbGRLS3RMRzNja2FhN0FDMDRxS29Rekc0WTN2Tl\ +RCOUlmCmdvYThOazlKMThoaFN2REVuR0haaURLR1dkQVA5RW5GRHZERnRFRFZyVVZSWUN5WHdyejMya\ +29Na3dYODVUMTgKc1F2TUZSdWRsa1BoOGxmbnFEWTFpZEd1R2IvSGlnU1ZqalJKZlVTbS9lZ3hycStS\ +TnpJWGFBOWNuOXAxQkdFbwpZU0YxeUdtTEgzSkJscUhVRTg4VnFkenZRdXZ3Mkhvakdvd0pkdlhmU2l\ +oZk5WOTh6VkEzQ2o3L0d3V2w0VU9OCkRUVUk2SzUrR3Y0a01iYXdxcWduaFlHbHBlZVp2bHE2UlI3OV\ +pNblRWVjJ1RWo4aERYQzdFTGN2Yk5tekJSSkoKS0gzaUxIbmNlSkxOV2NIMUV0K0pkaEFRR2xEYmk2R\ +VR0dHRuaWVaN3ZkMyt6UE00eXpkb3JEMlJXUjRyWG1acQpGRlI5eTBab2hQVTBjVmdKZHFtdUpyV01Z\ +eTVsOGt5SWdwaExkdTR1WWRpc0RPMG1ianEySE55K3pEakpyYXFHClNWTkZBZ01CQUFHalF6QkJNQjB\ +HQTFVZERnUVdCQlI1OG01YnpDaGlncmluaERETUJqSVJrWXovbURBUEJnTlYKSFJNQkFmOEVCVEFEQV\ +FIL01BOEdBMVVkRHdFQi93UUZBd01IQmdBd0RRWUpLb1pJaHZjTkFRRU1CUUFEZ2dJQgpBRWFDblluY\ +m9yWmRhWWZRUmxSb0dtSE94T1g5VFdNMXY0dWEweCtFcG11RDVWRDVlWEY1QVZkMlZadEdaeHYvCkd2\ +VnFBU2l0UTk3ampKV2p2bURWTUZtb3hQSmRYWDFkYTd5cmJYeFRmYm1mM1pac1RpdmZVdWQxYThxdUN\ +3dTUKTnBrdHFjV0JyMHRnNzFhOXlidHJOdm1GczZGRE1WMXkrY2JxYlp1UWlDWnc0WmZhekFaeTRQRG\ +NocE9SNjRCSwpBWFZIT05HcWFoV2hwcDkwd2E0TFEwUTE1U3FDR25kYVI4SHg5MHJOeEdkRjM1T1BLU\ +jd1TDdrWDU4ZGxaWStDClJJK1pKMndYMzJUZzgrc1RtTmNaUTliWDdvS0t3R3E5Uk94SjZJSUhOSnN2\ +bXhaSnlPcmE2N21hNTd3OWxiQnUKSzJlQ3cwZjRDeHdLNU1LNStkbWx6R3dhZmJlMG00TTBvVjlhWUx\ +ZTzAwR3Iza05heW9PdjRRVGtEM2pCMzVSMQpDMGJnQmk2eU1sTVJ2akZ5eEZkOFJpL003VG1jcXNObT\ +B5aklqbkZaenVtMFZTR1NLMXlRU3Flak40S0Y2R0JMCllpZ2JpM3c4WG5HYm9pZGdBUE9ncVVJeTJ1d\ +EU1MllzVXFsVHVncXhtM2xDOUhzaDM2UFJLNURDUG93eHVUNlgKcXo1M1ZiN2h6TkxLelpiRlJzbUdF\ +OFY2cWM2bXZTbUFXa25nL3QwaStXVmdGVkZuZFQrQ0oyNTJsa0ZacGljdAp6ekdETW44VUNDRUp4TDR\ +KTklTM2lLOUhlRys2MlZuay9QOEM3YVpLSXpVdjFud25rcVdUUUFYWDBKckJGdDdICjI5ZDk1RElmRT\ +RuT0FyS0JFNHc2Z1R4SU1uZzVzWi9ZbDFjcG5wUHlsR3VICi0tLS0tRU5EIENFUlRJRklDQVRFLS0tL\ +S0K") + ("ethKey" . "fN8cOT1lYNziaW0+pjBIgZ8r6+zMMhHsukkWBNPDsFo=") + ("TURN.username" . "ring") + ("TURN.server" . "turn.jami.net") + ("TURN.realm" . "ring") + ("TURN.password" . "ring") + ("TURN.enable" . "true") + ("TLS.verifyServer" . "true") + ("TLS.verifyClient" . "true") + ("TLS.serverName" . "") + ("TLS.requireClientCertificate" . "true") + ("TLS.privateKeyFile" . "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUpRd0lCQU\ +RBTkJna3Foa2lHOXcwQkFRRUZBQVNDQ1Mwd2dna3BBZ0VBQW9JQ0FRQzM5b1Z0cXNtUGdaSUgKcHpTV\ +GtlT3BlWC9CSEx2KzFTYnJPSFpVRHEwNFZCUU5BNmJmSFNSWTJpbHE1WEVheXNVSmwzQmsvM0txZEhS\ +cQpEV01wQ1dpcE1Vc2FwSGxJR0tSWHEwbXhQZ29WODZSUVBub1dCRTdhWVVEZTlJZXlxMmllZXpDK1l\ +YSnBtWTljCk5tblpaMFlHOHJGMEVpWFA0SHpVWGphZklTKzdKTTJ5ZTZyUlpINXBvdHBQNmV4NXhqVU\ +VuNEFFdWhuWGJ6U0EKNnZMaC9wYnhIUVBHMVlmNHFhcU9TY1lXV0ZWaE5ySVBCVFJNZ2RaTWRGRTh0W\ +VFyaUNkNTV4dUhrYncrQmY1VQpmVENqN25tVEcybDJNbGcrSXBHVkFXUFRWNkl0NFNiS3VadW5MZmRD\ +S2FrSU03SnA3V2dJWjZNRHdkaFFQWjJNClJPQzN0b0c1ajh4aFZOU0Q5QlZiOGZBcnRmeHdldlliQk1\ +yOEdRV2JQYlRKYk9tZHZnUGRGcXNTb1F4QnRUcWQKSG1TMG53RjJ4UGZrdm1Cd3BSWWhHNHpuZlVtMl\ +BQcDBIK1g5Sk96d0R1QWI5K0c0cWZtK3Z6bDd1N0o3anJ0TQpHQlJlSlREdGFNd2RHa0lmcUNRZGREZ\ +HFpM1hzVHVVOVNubVN4NkdxT2V6ZU04bk55dmtZWjR2Uk9vODFzU1JjCjQ4L3pZZjJ1OFBJUm4vVy9x\ +Zk9hbjVIbkcxeFNBUDZkcHAzQ1Y0YTgwUFduZyszcXQ4NW5Ha2k2Nk00NUlnRjgKNHZwRGUyM2YyZnp\ +rTk5OOTk1VTBvUStKVGg5eG1HaWJmenlCaXk5d3RUVHVYWDB3Y3gwTm9wNjdYaFFDVVBGSApybVd6Wj\ +V5bXhEdkRhcUN1U3NwcG0vTkRVRWQyc1FJREFRQUJBb0lDQUYyWXo4bzhXdERvMjZPSkx2Ym1BeTcyC\ +jRra2VsWWZTYXpyQ1AzSUZCWnpqS2xCMHl6STVZWVRUZXI4b2ZhTmtCMXdaOE5WeUlxVVhHeVBhSzls\ +MU1BcmwKU1pFRW5iQlEyVXpCbVh6VVU4MVhhUUpxeHpMc2ZqSWR5U09teG1QaFVobFFGRHpJMTYxaXM\ +4MzI0V1A3WjJXaApsU2U1RkFQdjg1TVpYREVhY1c2R0N5SUVTYVMvdkpHQ2loQ2VzL0pCSmpoejdtNT\ +VRU3liSjl0dnJxUE5KSDhJCmhDTm1BUWhEU3NPYVJXNlpBdGV6UEdUb0FQUHNHMXhLMGdwUlVDM1YyR\ +C90bGRRa0VlSjhxaW5TaUN6ZjZIc3cKTnpncjVUbTMzTm96R3Rjc2Z4ZFl0cVB1UzRPRG40bktLSFpE\ +MTBLTng2Qi9HakdQTHIra21jUUVSMUV4U2s2QwpSemtzSTRzRml6VnVLYTFNK2ZqaVNVc3RTdHV1cTJ\ +QQTJJNzFNTTZlMHJmU3ZKM3VESEJYOWY5T1RFaGxZUFBkCnB1VWM2ek1pdEJKRTg2SFo1M08wR0MzdG\ +p1M1BnSnhmWnJyMHBCU20rSzQ3ajVTUFNkaytiVGh3UDZDZDhzWGUKNmljS3YvMXI0SjVqd1pFTmRna\ +2hKeHVuMkE1WVA0c0NBU21JSFFXWmU1cGdFQ0ljUXBHZjNBVllVdVpGeXZ6cApLL3VBRTc0L3NMK0VU\ +UnhDUlhkN0ZJM2cwZFVySTA2WUFIV1FkY1JpdWpIUU12ZXB3V2RrdUdkaE9wV3VRTy9oCjc0MGgvRlZ\ +0dFdOMjc3ajhhc3ptSDA3T2lRWFIwTU1mN2FEaldMaElMYkd2YkV4TzN0TnRBYXZ6cGxmMUNSNk8KUm\ +1nNDdhVTZpR3lON1MwTE85RUJBb0lCQVFEWWMwelh2UmJHTWxkVVZnOFFTT1ZHKzV4NXVocTVyQ2xtQ\ +lp2ZApvYitWS1hkMXBONThraWkxTFBiYUJmZG9JcWVUamdOK0E0YnVpT3RGNjdCWUdISkp0WTk4ZWR5\ +RkpZREtCSzIyCmthRml2eWVuV01UWG5jaHMvUmNXMnVGS1M3cFAyNDRXYkF2clQxZGxmTTF3L1JzbTZ\ +QVjF1dndPSmNJcEdLSzgKWWgyN2hiaThUbEF6bnJCSS9TbGZwNlpxV3Y0MFRLanNNdmVGVFNWRU5yd1\ +F2MVl4TjcxbjA1UWRVUkIrT0lJSQpPenpNMWpNcm43cjNmS3RKTEx0RllEdUhJZzJDL2E0cUZORmpjM\ +296V1laQkhlcDkrWlEyOTgxdy96VTJIZ0oxCkxiajZjMy9qQU5EakI3MnRkcWMxVUkvMWhwVUh2WDJz\ +emY2czM0L3VSY3RlaFgxWEFvSUJBUURaazVaT01RdVMKNzlVQkhCeTRFbFR2cVkvUUxjMUd1aG9LVXE\ +0dzhONEtJZlpMSDk5TTcwek9JWjNoaEdaYWJDREZHN3RqSDl4Vwo3UnhXVWt5cFRDK1h5TTc1YjF0YW\ +9jRVVyZ1hnUnE4R1JFRXg3OUtwWUE3ei95TVc1OHJCTWhHTWlGZ1JTM00zCmNQSzg2dHBvaTFDN2crd\ +lUxcjJwcDEzN0habzZiUHpKTFRPNXA1OG1tTFRPQVpKd1VSZ3pzZWJnc1dsWXZlR0wKZWNBZ3lYbUtt\ +WmVSeGRNUFlCK0NmMno1TWZ5Ujk4MHRVSDJPaitGQjlJNExzRTExREphZ2wwS1lHMWxKRTBFagpodEV\ +1cTFOTG9DcVkzYXVKYjhtRUpqNlA2Ylc2SytCZTBlUi9CS1MraGxxb3VOYWEySnlhSDc4Qis2U1VZWE\ +RuCnd6MityWUhVZEI4M0FvSUJBRFlXc2ZnaloyS0Z4KzdxUm45aVIvRXlCUXNpSjNXSWdSdmVnUEdrY\ +nRTZWRSeXYKNDIwcnRRSjVSd0o2aFRXL216S3pSVW9qSlgvTU5VYld1ODEzNW05bThJRkJqb3F6TVhq\ +S0xJSzM1NlZlY1ZGUApUSGs1RTVHd3VTbGI3dnA2N0FieXJaSUswL3VzYXdHUWEySTF6YWd1aE5BenR\ +yTHVXcE9jZFdZditwQVd2WEJJCi9aKzRvd0xLU0tGL3FvVmZVYkRPQzFSaTlCbWFpcHArTndiVVdYeV\ +pHanFzMDVGejVYUTFPTUZIMUV5M3BqZmIKaFlRODRpeTZBZDQzU3dqY3lKV1lRUUtCQzBZWDRFeWVyW\ +Dd1TTkvaEUxbWRHUGlJdmNwVk8zWCt3Ly9LQndZNQorUGtTd1NKc3lTSDRqTkRsSGE2K2VuNUpSNy81\ +YWVVNENiY0lFcWNDZ2dFQkFLelMvNnhDWnZnalN5V2poK2hxCm4wN3plQW1icUJmTEVZNHJtTFBGVUF\ +ucWFqSElNbDV4SXFnRnFkd05pQ1BCQ2RLbnNaUU9KYjVpZjRUTndKa2wKckJRNzdMUFRVVlJQY2dnVU\ +p4Uzc4S0Rnckl5Vys5V1FPTEIxZEJEb3MzUDhhbFlmb3h5eHV1Wko4SFpCY3BWaQpQQkdHdTFnSDd3V\ +0lyUzBmbVhkWlJQNGp5cGRvM3hFUWNXWEZkK1dCZE9EektmcEcwZkFzZTdDSFdDWnpBdmttCkFYQklH\ +OXQxdGZHNWQvMEZTS05GbTVPb0FPT3h3L0xZNTgrL0RmZXd0U0VBcFdRZkxTL1BmSWxVdUdvQ3FwcEM\ +Kc2pOVXVNSGxxc011Z2JsY29mNHNoZityWjMzQldYOEJSNWdIb21mRE1ibDNDQWp5TXd1dHpybzVxcD\ +BBUTBWWApxOGNDZ2dFQkFLakFXVVRYY1F2TE8yYkxOZmJBTUlSVXk1T2lTZmJCbDNYRThSZnNzaUt2V\ +VBnTFcwSlV1V3FLCjdGdUFxTlJPRHhrS0pMSTdyQlo2YVNqNitFWHpUMnJwY2dFWktnSjFOUEFRdFNs\ +UjNJUkcyU3JJdjBFK3UzbkUKK1laa3pOa2Q0MUJqTkRjRm1HV21lZk5ROUJmaVIrZlZFSkZmcE5oSkl\ +mNUloSWU0RUtZUE5VUXNua0tSVTlxUApzWi9idXBXc2w4bWVFcko3bllJQ05ucHpnSHRpNXdSMlliVF\ +VXT01odmRFUldxMnhTV3BBYmtNMElhZDBUc05kCmUrYVRQVmJOMXFibFZLMm1qUTl2YS9JSkVuSE51V\ +E9TREtJeUpvcVArQkxiRTVjQU5acXQ2OFFadWdOc2RxNHkKV2FoeStydU5LS1F3Mk5MYzQzZUtsNmxv\ +bXdtRlFZOD0KLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLQo=") + ("TLS.password" . "") + ("TLS.negotiationTimeoutSec" . "-1") + ("TLS.method" . "Automatic") + ("TLS.ciphers" . "") + ("TLS.certificateFile" . "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZHVENDQ\ +XdHZ0F3SUJBZ0lJU1pUdlZPQnh3akF3RFFZSktvWklodmNOQVFFTUJRQXdTVEVOTUFzR0ExVUUKQXhN\ +RVNtRnRhVEU0TURZR0NnbVNKb21UOGl4a0FRRVRLR1l6TXpRMVpqSTNOelZrWkdabE1EZGhOR0l3WkR\ +rMQpaR0ZsWVRFeE1XUXhOV1ppWXpFeE9Ua3dIaGNOTWpFd05ERTJNVGN6TWpFd1doY05NekV3TkRFME\ +1UY3pNakV3CldqQlFNUlF3RWdZRFZRUURFd3RLWVcxcElHUmxkbWxqWlRFNE1EWUdDZ21TSm9tVDhpe\ +GtBUUVUS0dFM09XTmwKTURVNE16VmhPRFV5WlRsbU5qWmxOelF3TURjeU5EUXdPVFZpTVdaa1kyVXdO\ +bVV3Z2dJaU1BMEdDU3FHU0liMwpEUUVCQVFVQUE0SUNEd0F3Z2dJS0FvSUNBUUMzOW9WdHFzbVBnWkl\ +IcHpTVGtlT3BlWC9CSEx2KzFTYnJPSFpVCkRxMDRWQlFOQTZiZkhTUlkyaWxxNVhFYXlzVUpsM0JrLz\ +NLcWRIUnFEV01wQ1dpcE1Vc2FwSGxJR0tSWHEwbXgKUGdvVjg2UlFQbm9XQkU3YVlVRGU5SWV5cTJpZ\ +WV6QytZWEpwbVk5Y05tblpaMFlHOHJGMEVpWFA0SHpVWGphZgpJUys3Sk0yeWU2clJaSDVwb3RwUDZl\ +eDV4alVFbjRBRXVoblhielNBNnZMaC9wYnhIUVBHMVlmNHFhcU9TY1lXCldGVmhOcklQQlRSTWdkWk1\ +kRkU4dFlRcmlDZDU1eHVIa2J3K0JmNVVmVENqN25tVEcybDJNbGcrSXBHVkFXUFQKVjZJdDRTYkt1Wn\ +VuTGZkQ0tha0lNN0pwN1dnSVo2TUR3ZGhRUFoyTVJPQzN0b0c1ajh4aFZOU0Q5QlZiOGZBcgp0Znh3Z\ +XZZYkJNcjhHUVdiUGJUSmJPbWR2Z1BkRnFzU29ReEJ0VHFkSG1TMG53RjJ4UGZrdm1Cd3BSWWhHNHpu\ +CmZVbTJQUHAwSCtYOUpPendEdUFiOStHNHFmbSt2emw3dTdKN2pydE1HQlJlSlREdGFNd2RHa0lmcUN\ +RZGREZHEKaTNYc1R1VTlTbm1TeDZHcU9lemVNOG5OeXZrWVo0dlJPbzgxc1NSYzQ4L3pZZjJ1OFBJUm\ +4vVy9xZk9hbjVIbgpHMXhTQVA2ZHBwM0NWNGE4MFBXbmcrM3F0ODVuR2tpNjZNNDVJZ0Y4NHZwRGUyM\ +2YyZnprTk5OOTk1VTBvUStKClRoOXhtR2liZnp5Qml5OXd0VFR1WFgwd2N4ME5vcDY3WGhRQ1VQRkhy\ +bVd6WjV5bXhEdkRhcUN1U3NwcG0vTkQKVUVkMnNRSURBUUFCTUEwR0NTcUdTSWIzRFFFQkRBVUFBNEl\ +DQVFCZmRCc0p4RVVWeFRBeG5vNll1bEFoR1ZvUAplWG8xMHIwOE5DcDZRZlJxeGJlTkZ5aXEvKzEwSE\ +NpL1RoZk41OVdLNlpudFF4NlFNZUxEUVZTb0NjNzlaaWQ4ClE2RUdsWkp1c2RTTmg0VjVteXRCQVZHZ\ +2J2aXJFWU1Wcm5jWWg3bHR2LzVuSGRsbyt2WXV3Vzh0aEhHTk1TUkIKQmhJN2xydmpqY3NkbVl6L1Bp\ +NFNZdkg3c3RaVWpRYmUzNzh3UE90b3lBMTNybEtQUTF4QjJFbUxISDYya21WTQowa25wL1hQQ2o2alR\ +zakpFWko1NzZNMmloYy9DTzkvREVlWWZ4RFlJb004NEF5dTc0UU9UUVN1cnVHRjF5T1RKCmlxRkltTH\ +hMcWRxNk1Zdmx1QjFmTzlKaU9QaS84UEJFTTVpeGYzajlGOFVMQTZUTU1NSklFR1ROeUNzOWpzQloKR\ +UNiWVgweTY4WWtGYnYzQmNWaGk2K1VVWVhBVUd0akhwQ3Z2VThYaHowL0RVZFVaTkpqejRJeWl1ZE5N\ +dnFjQgppSEJDdkxFS1B4VFd1ZlFZaGM5ZkVXTm1valc1WSsvalBVeDcrQWxPM1RXZm1OclBpWDJuSEd\ +5UG5tYjZIR2hWCmp3UU0wT1h4cGxnZ2dQMWpVZ0VYWWRucStjYzdRMy94RFIvdi9Fd244T1d4OVB3eV\ +Y0WFZwZXY5QnpmeWwyQTMKdFhWVkFKMzJtcTlPQmVadXczdWlWU2E2TWdCVG80eXcrYnBjU2VIU2xXO\ +XNRc1NYY3dUNjhvOUdhR2hMNjdXMApwQWx4R2VxSWRtbTZ0MkxIUCtQQnBtL3BweTMvTmh1NFIwMTNv\ +Znp5V2Y0bEcrVnpWVGE0eXVqU1J3UlluYmZyCjlSWER0L0I5VEg5TnNsamdBQT09Ci0tLS0tRU5EIEN\ +FUlRJRklDQVRFLS0tLS0KLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZXakNDQTBLZ0F3SU\ +JBZ0lJRm1tNmZuaWRndEl3RFFZSktvWklodmNOQVFFTUJRQXdUREVRTUE0R0ExVUUKQXhNSFNtRnRhU\ +0JEUVRFNE1EWUdDZ21TSm9tVDhpeGtBUUVUS0RjNVpqSTJaVFZpWTJNeU9EWXlPREppT0dFMwpPRFF6\ +TUdOak1EWXpNakV4T1RFNFkyWm1PVGd3SGhjTk1qRXdOREUyTVRjek1qRXdXaGNOTXpFd05ERTBNVGN\ +6Ck1qRXdXakJKTVEwd0N3WURWUVFERXdSS1lXMXBNVGd3TmdZS0NaSW1pWlB5TEdRQkFSTW9aak16Tk\ +RWbU1qYzMKTldSa1ptVXdOMkUwWWpCa09UVmtZV1ZoTVRFeFpERTFabUpqTVRFNU9UQ0NBaUl3RFFZS\ +ktvWklodmNOQVFFQgpCUUFEZ2dJUEFEQ0NBZ29DZ2dJQkFQY0czaUQwUk84NE5XL2ZGVllWNHBLQ2My\ +ZE1EQXFCZVN2NEpCcTQ3OHQ5ClhjeERPeEhqSnYvR0dLRnFoVyt4cWFDOUJKZG44TGthdjBBTnMvV1d\ +3YXRNSDVsYlQrZlc0T2pGOVNCZ1Ivc2UKUlNPdWE1QncrSVJlSk5VSnR3enBJS3k1S1RhMnEwd0RyUX\ +hObXBuci9UK2NEK2RQSTJveUlFd2xwRHNKT2lVMAp6eStDU2pBb0FTYkh5TWcraGQwcWJiRFBUck1uR\ +2xKYkZubXdBQTBaRWU3aWg5Ymx0dlFPaU9TQWJVQm9IUHFpCmNtQW41RVFoNjVFa3dCb1p1U3hqanlC\ +VzZpWUJUbmc5S2c4U2pPVk9BYkJqaXZFZ3RyaFpYZ1JVdE11UWFmaDcKcjRRWDRuYjhRTUE5WXlhME8\ +xbFBJTUd5VEtiNHFIaE02VTd1RHV0NUFjcTN1NGFaZElPQ2prbUNFM3l4UU5UWAo5Vzl0b1NvV1l2YV\ +I1d1gxaFI5eVZCMFZ4V0N0djdZZklha1JiaWkxMS8xZHp3M1dXU1dMbTArTU4rY0pUdnluCk1EU29GN\ +jRpRmcycTVYd1VtWXpPRmJKQUtVd3M4QmVhcFRCVnRYaldDZmNKM01xZEZScDZvM25Lenlja0YwamoK\ +UjdLSmNRaWhJUW1sbXM1WEtzeHV1d0VMMmFKQ0o0Qm9uSUJDaXZWa09LbllsamtDeVNObzkzamlMalJ\ +4M0ZtRwpoTnJyT3lNbjFIcEd0Z1J2eFUxOC9Ob3FFaWtpdEhCeUtYNW5XU1lUUWN4MXlZY3kxcVo5cj\ +RqTmdQRzNuUHg0CjJEN2tPTlJPWXNVYXNBUlVTZHJqdlc5aXB2TDAvN2s1Yi9sRzh1WThPODN0NUFPS\ +TFVUG85Y05qMzNyeWJwbUgKQWdNQkFBR2pRekJCTUIwR0ExVWREZ1FXQkJUek5GOG5kZDMrQjZTdzJW\ +MnVvUkhSWDd3Um1UQVBCZ05WSFJNQgpBZjhFQlRBREFRSC9NQThHQTFVZER3RUIvd1FGQXdNSEJnQXd\ +EUVlKS29aSWh2Y05BUUVNQlFBRGdnSUJBRDMrCjlscFluMjZyeG5pekY2UkNvZFFFVmgvOVF2RWp1V3\ +dHUWZqa3gxb3VlVjdDMzUyWnpIT2hWU3VGcG43TUxkVFcKamI2dWhMRkpoMWtlTDlYZ0pHalMyV0Uwd\ +DlJUGp2UWx5UHIwRWJoWGRJNDJMYUR0NDk0dWQ2MEE0bWg0bW1zbwovcjY2NERKOWMwUjZBOUdQZUJj\ +Wi9zekhMQjJ4VmM5M3hYNjVjcmNoVTFLMDBVK2ZZUWtHS0xidFFXeUZzdnlKCmRHdFRxamVlYVVNeGt\ +5dFFWNWFxVHJ1SG4vV3U2RWRZejYrZ0ViQXJHUURRK1J3QmMxMDNGcWRIU0xReWdEWHoKd0pNVGhDRE\ +dBVmR6V2NCemJYL1JIZms5bTZzK01HblNJUFBrNG9FOUdFbGdQc2JUZ2FrTzc3aElUdzRxZXJiTAp2M\ +0tiaTVOeVZZVFNVa3hDb0VRSkIrWlZJT3BVNFhRdVAvNkZkWldBRk1LU0Jkd1JLcnRmT1hZT1ExcVBv\ +YU1uCjZPR1VGMU0rYWZ0dTNCMkNhZ2ZMaE5hbVBoSjdxWTNSMzJhK2VRVllvYWlESXZKMElIUHFnQ0N\ +uWXlIaVBHTjQKS3VvaE9IZFpXTkxOUXdIQTg3SDV5NEwrSGZISmdFZFppdWYrbTNMa3JJcnN5MWFoUj\ +gyaEpZVkhreHVyZVRDcQpIR2NJaUIvTHpkSG91WFVrWGNrNjRvWXVRQnM3ZE9KVEx6bGlibU8xelRzV\ +WRoc0d4dE9zc2lYWkFjUURmZHBnCjNHdFJ3UkRwd0ZtL2k5TC9UWjMzMjBwY0VZd21aN1dBSzJRa3Z6\ +eWZlelRGeXdLWmh3c0RQRkwycllzLzhXdWsKRk1sek5BVmRMTytTS1RxeGlkM0l1elFaQ0FwU05ybTJ\ +MY2ZVN2grWAotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS\ +0tLQpNSUlGWFRDQ0EwV2dBd0lCQWdJSXV0T3MxUXBsSVRvd0RRWUpLb1pJaHZjTkFRRU1CUUF3VERFU\ +U1BNEdBMVVFCkF4TUhTbUZ0YVNCRFFURTRNRFlHQ2dtU0pvbVQ4aXhrQVFFVEtEYzVaakkyWlRWaVky\ +TXlPRFl5T0RKaU9HRTMKT0RRek1HTmpNRFl6TWpFeE9URTRZMlptT1Rnd0hoY05NakV3TkRFMk1UY3p\ +NakExV2hjTk16RXdOREUwTVRjegpNakExV2pCTU1SQXdEZ1lEVlFRREV3ZEtZVzFwSUVOQk1UZ3dOZ1\ +lLQ1pJbWlaUHlMR1FCQVJNb056bG1NalpsCk5XSmpZekk0TmpJNE1tSTRZVGM0TkRNd1kyTXdOak15T\ +VRFNU1UaGpabVk1T0RDQ0FpSXdEUVlKS29aSWh2Y04KQVFFQkJRQURnZ0lQQURDQ0Fnb0NnZ0lCQUxT\ +cGduY2tYODd3OE5vVzdRWE5DbWtHSHphVnN1NE9UbjFSSUhqVQpGR0w5MjNvVlk5RHVoZjFtVnllamg\ +remVCOCszaTR4b1E5MU04WDdTVnk4SHZxejQ4c0I2djAxMk5RTGpwN08xCkd4dmc1a0REeDRqbmVyUW\ +d6d0JzVEFuaC8xNy9SOEFDZkhINzYzUjhMRjY1clJPSStOT21KNUV2cU9rRWJJOXYKb0UySko1czBVS\ +HZZM1hTQ2pid01ROXRkM2c0SG9LNmRYSU44a2d6ejg0Yi83YkF5TnZ4a09rNnVSei9CSjhhZQozWjZu\ +dElza2tmVHhIZFpBbGtpVW5DU21Bb3VwQ01TbGRLS3RMRzNja2FhN0FDMDRxS29Rekc0WTN2TlRCOUl\ +mCmdvYThOazlKMThoaFN2REVuR0haaURLR1dkQVA5RW5GRHZERnRFRFZyVVZSWUN5WHdyejMya29Na3\ +dYODVUMTgKc1F2TUZSdWRsa1BoOGxmbnFEWTFpZEd1R2IvSGlnU1ZqalJKZlVTbS9lZ3hycStSTnpJW\ +GFBOWNuOXAxQkdFbwpZU0YxeUdtTEgzSkJscUhVRTg4VnFkenZRdXZ3Mkhvakdvd0pkdlhmU2loZk5W\ +OTh6VkEzQ2o3L0d3V2w0VU9OCkRUVUk2SzUrR3Y0a01iYXdxcWduaFlHbHBlZVp2bHE2UlI3OVpNblR\ +WVjJ1RWo4aERYQzdFTGN2Yk5tekJSSkoKS0gzaUxIbmNlSkxOV2NIMUV0K0pkaEFRR2xEYmk2RVR0dH\ +RuaWVaN3ZkMyt6UE00eXpkb3JEMlJXUjRyWG1acQpGRlI5eTBab2hQVTBjVmdKZHFtdUpyV01ZeTVsO\ +Gt5SWdwaExkdTR1WWRpc0RPMG1ianEySE55K3pEakpyYXFHClNWTkZBZ01CQUFHalF6QkJNQjBHQTFV\ +ZERnUVdCQlI1OG01YnpDaGlncmluaERETUJqSVJrWXovbURBUEJnTlYKSFJNQkFmOEVCVEFEQVFIL01\ +BOEdBMVVkRHdFQi93UUZBd01IQmdBd0RRWUpLb1pJaHZjTkFRRU1CUUFEZ2dJQgpBRWFDblluYm9yWm\ +RhWWZRUmxSb0dtSE94T1g5VFdNMXY0dWEweCtFcG11RDVWRDVlWEY1QVZkMlZadEdaeHYvCkd2VnFBU\ +2l0UTk3ampKV2p2bURWTUZtb3hQSmRYWDFkYTd5cmJYeFRmYm1mM1pac1RpdmZVdWQxYThxdUN3dTUK\ +TnBrdHFjV0JyMHRnNzFhOXlidHJOdm1GczZGRE1WMXkrY2JxYlp1UWlDWnc0WmZhekFaeTRQRGNocE9\ +SNjRCSwpBWFZIT05HcWFoV2hwcDkwd2E0TFEwUTE1U3FDR25kYVI4SHg5MHJOeEdkRjM1T1BLUjd1TD\ +drWDU4ZGxaWStDClJJK1pKMndYMzJUZzgrc1RtTmNaUTliWDdvS0t3R3E5Uk94SjZJSUhOSnN2bXhaS\ +nlPcmE2N21hNTd3OWxiQnUKSzJlQ3cwZjRDeHdLNU1LNStkbWx6R3dhZmJlMG00TTBvVjlhWUxZTzAw\ +R3Iza05heW9PdjRRVGtEM2pCMzVSMQpDMGJnQmk2eU1sTVJ2akZ5eEZkOFJpL003VG1jcXNObTB5akl\ +qbkZaenVtMFZTR1NLMXlRU3Flak40S0Y2R0JMCllpZ2JpM3c4WG5HYm9pZGdBUE9ncVVJeTJ1dEU1Ml\ +lzVXFsVHVncXhtM2xDOUhzaDM2UFJLNURDUG93eHVUNlgKcXo1M1ZiN2h6TkxLelpiRlJzbUdFOFY2c\ +WM2bXZTbUFXa25nL3QwaStXVmdGVkZuZFQrQ0oyNTJsa0ZacGljdAp6ekdETW44VUNDRUp4TDRKTklT\ +M2lLOUhlRys2MlZuay9QOEM3YVpLSXpVdjFud25rcVdUUUFYWDBKckJGdDdICjI5ZDk1RElmRTRuT0F\ +yS0JFNHc2Z1R4SU1uZzVzWi9ZbDFjcG5wUHlsR3VICi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K") + ("STUN.server" . "") + ("STUN.enable" . "false") + ("SRTP.rtpFallback" . "false") + ("SRTP.keyExchange" . "sdes") + ("SRTP.enable" . "true") + ("RingNS.uri" . "") + ("RingNS.account" . "0790738ce15fa05933b49dd77034312787da86c3") + ("DHT.PublicInCalls" . "true") + ("Account.videoPortMin" . "49152") + ("Account.videoPortMax" . "65534") + ("Account.videoEnabled" . "true") + ("Account.username" . "f3345f2775ddfe07a4b0d95daea111d15fbc1199") + ("Account.useragent" . "") + ("Account.upnpEnabled" . "true") + ("Account.type" . "RING") + ("Account.ringtoneEnabled" . "true") + ("Account.rendezVous" . "true") + ("Account.publishedSameAsLocal" . "true") + ("Account.publishedPort" . "5060") + ("Account.publishedAddress" . "") + ("Account.presenceSubscribeSupported" . "true") + ("Account.peerDiscovery" . "false") + ("Account.managerUsername" . "") + ("Account.managerUri" . "") + ("Account.mailbox" . "") + ("Account.localModeratorsEnabled" . "true") + ("Account.localInterface" . "default") + ("Account.hostname" . "bootstrap.jami.net") + ("Account.hasCustomUserAgent" . "false") + ("Account.enable" . "true") + ("Account.dtmfType" . "overrtp") + ("Account.displayName" . "dummy") + ("Account.defaultModerators" . "") + ("Account.audioPortMin" . "16384") + ("Account.audioPortMax" . "32766") + ("Account.archiveHasPassword" . "false") + ("Account.allowCertFromTrusted" . "true") + ("Account.allowCertFromHistory" . "true") + ("Account.allowCertFromContact" . "true") + ("Account.allModeratorEnabled" . "true") + ("Account.alias" . "dummy") + ("Account.activeCallLimit" . "-1") + ("Account.accountPublish" . "false") + ("Account.accountDiscovery" . "false"))) diff --git a/gnu/tests/telephony.scm b/gnu/tests/telephony.scm new file mode 100644 index 0000000000..1155a9dbc2 --- /dev/null +++ b/gnu/tests/telephony.scm @@ -0,0 +1,366 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2021 Maxim Cournoyer . +;;; +;;; This file is part of GNU Guix. +;;; +;;; GNU Guix is free software; you can redistribute it and/or modify it +;;; under the terms of the GNU General Public License as published by +;;; the Free Software Foundation; either version 3 of the License, or (at +;;; your option) any later version. +;;; +;;; GNU Guix is distributed in the hope that it will be useful, but +;;; WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Guix. If not, see . + +(define-module (gnu tests telephony) + #:use-module (gnu) + #:use-module (gnu packages) + #:use-module (gnu packages guile) + #:use-module (gnu tests) + #:use-module (gnu system vm) + #:use-module (gnu services) + #:use-module (gnu services dbus) + #:use-module (gnu services networking) + #:use-module (gnu services ssh) + #:use-module (gnu services telephony) + #:use-module (guix gexp) + #:use-module (guix modules) + #:export (%test-jami + %test-jami-provisioning)) + +;;; +;;; Jami daemon. +;;; + +(include "data/jami-dummy-account.dat") ;defines %jami-account-content-sexp + +(define %dummy-jami-account-archive + ;; A Jami account archive is a gzipped JSON file. + (computed-file + "dummy-jami-account.gz" + (with-extensions (list guile-json-4 guile-zlib) + #~(begin + (use-modules (json) (zlib)) + (let ((port (open-output-file #$output))) + (call-with-gzip-output-port port + (lambda (port) + (scm->json '#$%jami-account-content-sexp port)))))))) + +(define %allowed-contacts '("1dbcb0f5f37324228235564b79f2b9737e9a008f" + "2dbcb0f5f37324228235564b79f2b9737e9a008f")) + +(define %moderators '("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")) + +(define %dummy-jami-account (jami-account + (archive %dummy-jami-account-archive) + (allowed-contacts %allowed-contacts) + (moderators %moderators) + (rendezvous-point? #t) + (peer-discovery? #f) + (bootstrap-hostnames '("bootstrap.me" + "fallback.another.host")) + (name-server-uri "https://my.name.server"))) + +(define* (make-jami-os #:key provisioning?) + (operating-system + (host-name "jami") + (timezone "America/Montreal") + (locale "en_US.UTF-8") + + (bootloader (bootloader-configuration + (bootloader grub-bootloader) + (target "/dev/sdX"))) + (file-systems (cons (file-system + (device (file-system-label "my-root")) + (mount-point "/") + (type "ext4")) + %base-file-systems)) + (firmware '()) + + (services (cons* (service jami-service-type + (if provisioning? + (jami-configuration + (debug? #t) + (accounts (list %dummy-jami-account))) + (jami-configuration + (debug? #t)))) + (service dbus-root-service-type) + ;; The following services/packages are added for + ;; debugging purposes. + (service dhcp-client-service-type) + (service openssh-service-type + (openssh-configuration + (permit-root-login #t) + (allow-empty-passwords? #t))) + %base-services)) + (packages (cons* (specification->package "recutils") + (specification->package "strace") + %base-packages)))) + +(define %jami-os + (make-jami-os)) + +(define %jami-os-provisioning + (make-jami-os #:provisioning? #t)) + +(define* (run-jami-test #:key provisioning?) + "Run tests in %JAMI-OS. When PROVISIONING? is true, test the +accounts provisioning feature of the service." + (define os (marionette-operating-system + (if provisioning? + %jami-os-provisioning + %jami-os) + #:imported-modules '((gnu services herd) + (guix combinators)))) + (define vm (virtual-machine + (operating-system os) + (memory-size 512))) + + (define username (assoc-ref %jami-account-content-sexp + "Account.username")) + + (define test + (with-imported-modules (source-module-closure + '((gnu build marionette) + (gnu build jami-service))) + #~(begin + (use-modules (rnrs base) + (srfi srfi-11) + (srfi srfi-64) + (gnu build marionette) + (gnu build jami-service)) + + (define marionette + (make-marionette (list #$vm))) + + (mkdir #$output) + (chdir #$output) + + (test-begin "jami") + + (test-assert "service is running" + (marionette-eval + '(begin + (use-modules (gnu services herd)) + (match (start-service 'jami) + (#f #f) + (('service response-parts ...) + (match (assq-ref response-parts 'running) + ((pid) (number? pid)))))) + marionette)) + + (test-assert "service can be stopped" + (marionette-eval + '(begin + (use-modules (gnu services herd) + (rnrs base)) + (setenv "PATH" "/run/current-system/profile/bin") + (let ((pid (match (start-service 'jami) + (#f #f) + (('service response-parts ...) + (match (assq-ref response-parts 'running) + ((pid) pid)))))) + + (assert (number? pid)) + + (match (stop-service 'jami) + (services ;a list of service symbols + (member 'jami services))) + ;; Sometimes, the process still appear in pgrep, even + ;; though we are using waitpid after sending it SIGTERM + ;; in the service; use retries. + (with-retries 20 1 + (not (zero? (status:exit-val + (system* "pgrep" "dring"))))))) + marionette)) + + (test-assert "service can be restarted" + (marionette-eval + '(begin + (use-modules (gnu services herd) + (rnrs base)) + ;; Start and retrieve the current PID. + (define pid (match (start-service 'jami) + (#f #f) + (('service response-parts ...) + (match (assq-ref response-parts 'running) + ((pid) pid))))) + (assert (number? pid)) + + ;; Restart the service. + (restart-service 'jami) + + (define new-pid (match (start-service 'jami) + (#f #f) + (('service response-parts ...) + (match (assq-ref response-parts 'running) + ((pid) pid))))) + (assert (number? new-pid)) + + (not (eq? pid new-pid))) + marionette)) + + (unless #$provisioning? (test-skip 1)) + (test-assert "jami accounts provisioning, account present" + (marionette-eval + '(begin + (use-modules (gnu services herd) + (rnrs base)) + ;; Accounts take some time to appear after being added. + (with-retries 20 1 + (with-shepherd-action 'jami ('list-accounts) results + (let ((account (assoc-ref (car results) #$username))) + (assert (string=? #$username + (assoc-ref account + "Account.username"))))))) + marionette)) + + (unless #$provisioning? (test-skip 1)) + (test-assert "jami accounts provisioning, allowed-contacts" + (marionette-eval + '(begin + (use-modules (gnu services herd) + (rnrs base) + (srfi srfi-1)) + + ;; Public mode is disabled. + (with-shepherd-action 'jami ('list-account-details) + results + (let ((account (assoc-ref (car results) #$username))) + (assert (string=? "false" + (assoc-ref account + "DHT.PublicInCalls"))))) + + ;; Allowed contacts match those declared in the configuration. + (with-shepherd-action 'jami ('list-contacts) results + (let ((contacts (assoc-ref (car results) #$username))) + (assert (lset= string-ci=? contacts '#$%allowed-contacts))))) + marionette)) + + (unless #$provisioning? (test-skip 1)) + (test-assert "jami accounts provisioning, moderators" + (marionette-eval + '(begin + (use-modules (gnu services herd) + (rnrs base) + (srfi srfi-1)) + + ;; Moderators match those declared in the configuration. + (with-shepherd-action 'jami ('list-moderators) results + (let ((moderators (assoc-ref (car results) #$username))) + (assert (lset= string-ci=? moderators '#$%moderators)))) + + ;; Moderators can be added via the Shepherd action. + (with-shepherd-action 'jami + ('add-moderator "cccccccccccccccccccccccccccccccccccccccc" + #$username) results + (let ((moderators (car results))) + (assert (lset= string-ci=? moderators + (cons "cccccccccccccccccccccccccccccccccccccccc" + '#$%moderators)))))) + marionette)) + + (unless #$provisioning? (test-skip 1)) + (test-assert "jami service actions, ban/unban contacts" + (marionette-eval + '(begin + (use-modules (gnu services herd) + (rnrs base) + (srfi srfi-1)) + + ;; Globally ban a contact. + (with-shepherd-action 'jami + ('ban-contact "1dbcb0f5f37324228235564b79f2b9737e9a008f") _ + (with-shepherd-action 'jami ('list-banned-contacts) results + (every (match-lambda + ((username . banned-contacts) + (member "1dbcb0f5f37324228235564b79f2b9737e9a008f" + banned-contacts))) + (car results)))) + + ;; Ban a contact for a single account. + (with-shepherd-action 'jami + ('ban-contact "dddddddddddddddddddddddddddddddddddddddd" + #$username) _ + (with-shepherd-action 'jami ('list-banned-contacts) results + (every (match-lambda + ((username . banned-contacts) + (let ((found? (member "dddddddddddddddddddddddddddddddddddddddd" + banned-contacts))) + (if (string=? #$username username) + found? + (not found?))))) + (car results))))) + marionette)) + + (unless #$provisioning? (test-skip 1)) + (test-assert "jami service actions, enable/disable accounts" + (marionette-eval + '(begin + (use-modules (gnu services herd) + (rnrs base)) + + (with-shepherd-action 'jami + ('disable-account #$username) _ + (with-shepherd-action 'jami ('list-accounts) results + (let ((account (assoc-ref (car results) #$username))) + (assert (string= "false" + (assoc-ref account "Account.enable")))))) + + (with-shepherd-action 'jami + ('enable-account #$username) _ + (with-shepherd-action 'jami ('list-accounts) results + (let ((account (assoc-ref (car results) #$username))) + (assert (string= "true" + (assoc-ref account "Account.enable"))))))) + marionette)) + + (unless #$provisioning? (test-skip 1)) + (test-assert "jami account parameters" + (marionette-eval + '(begin + (use-modules (gnu services herd) + (rnrs base) + (srfi srfi-1)) + + (with-shepherd-action 'jami ('list-account-details) results + (let ((account-details (assoc-ref (car results) + #$username))) + (assert (lset<= + equal? + '(("Account.hostname" . + "bootstrap.me;fallback.another.host") + ("Account.peerDiscovery" . "false") + ("Account.rendezVous" . "true") + ("RingNS.uri" . "https://my.name.server")) + account-details))))) + marionette)) + + (test-end) + (exit (= (test-runner-fail-count (test-runner-current)) 0))))) + + (gexp->derivation (if provisioning? + "jami-provisioning-test" + "jami-test") + test)) + +(define %test-jami + (system-test + (name "jami") + (description "Basic tests for the jami service.") + (value (run-jami-test)))) + +(define %test-jami-provisioning + (system-test + (name "jami-provisioning") + (description "Provisioning test for the jami service.") + (value (run-jami-test #:provisioning? #t)))) + +;; Local Variables: +;; eval: (put 'with-retries 'scheme-indent-function 2) +;; End: diff --git a/tests/services/telephony.scm b/tests/services/telephony.scm new file mode 100644 index 0000000000..b4a0f120d4 --- /dev/null +++ b/tests/services/telephony.scm @@ -0,0 +1,446 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2021 Maxim Cournoyer +;;; +;;; This file is part of GNU Guix. +;;; +;;; GNU Guix is free software; you can redistribute it and/or modify it +;;; under the terms of the GNU General Public License as published by +;;; the Free Software Foundation; either version 3 of the License, or (at +;;; your option) any later version. +;;; +;;; GNU Guix is distributed in the hope that it will be useful, but +;;; WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Guix. If not, see . + +(define-module (tests services telephony) + #:use-module (gnu build jami-service) + #:use-module (gnu services telephony) + #:use-module (srfi srfi-64)) + +;;; Tests for the (gnu services telephony) and related modules. + +(test-begin "jami-service") + +(define parse-dbus-reply + (@@ (gnu build jami-service) parse-dbus-reply)) + +(define parse-account-ids + (@@ (gnu build jami-service) parse-account-ids)) + +(define parse-account-details + (@@ (gnu build jami-service) parse-account-details)) + +(define parse-contacts + (@@ (gnu build jami-service) parse-contacts)) + +(define jami-account->alist + (@@ (gnu services telephony) jami-account->alist)) + +;; $ dbus-send --print-reply --dest="cx.ring.Ring" \ +;; "/cx/ring/Ring/ConfigurationManager" \ +;; "cx.ring.Ring.ConfigurationManager.getAccountList" +(define getAccountList-reply "\ +method return time=1622217253.386711 sender=:1.7 -> destination=:1.14 serial=140 reply_serial=2 + array [ + string \"addf37fbb558d6a0\" + string \"d5cbeb7d08c98a65\" + string \"398af0c6b74ce101\" + ] +") + +(test-equal "parse-account-ids" + '("addf37fbb558d6a0" "d5cbeb7d08c98a65" "398af0c6b74ce101") + (parse-account-ids getAccountList-reply)) + +;; $ dbus-send --print-reply --dest="cx.ring.Ring" \ +;; "/cx/ring/Ring/ConfigurationManager" \ +;; "cx.ring.Ring.ConfigurationManager.getAccountDetails" \ +;; 'string:398af0c6b74ce101' +(define getAccountDetails-reply "\ +method return time=1622254991.789588 sender=:1.7 -> destination=:1.19 serial=145 reply_serial=2 + array [ + dict entry( + string \"Account.accountDiscovery\" + string \"false\" + ) + dict entry( + string \"Account.accountPublish\" + string \"false\" + ) + dict entry( + string \"Account.activeCallLimit\" + string \"-1\" + ) + dict entry( + string \"Account.alias\" + string \"some-rendezvous-point-name\" + ) + dict entry( + string \"Account.allModeratorEnabled\" + string \"true\" + ) + dict entry( + string \"Account.allowCertFromContact\" + string \"true\" + ) + dict entry( + string \"Account.allowCertFromHistory\" + string \"true\" + ) + dict entry( + string \"Account.allowCertFromTrusted\" + string \"true\" + ) + dict entry( + string \"Account.archiveHasPassword\" + string \"false\" + ) + dict entry( + string \"Account.audioPortMax\" + string \"32766\" + ) + dict entry( + string \"Account.audioPortMin\" + string \"16384\" + ) + dict entry( + string \"Account.autoAnswer\" + string \"false\" + ) + dict entry( + string \"Account.defaultModerators\" + string \"\" + ) + dict entry( + string \"Account.deviceID\" + string \"94b4070fc7a8afa8482c777a9822c52e6af2e1bd\" + ) + dict entry( + string \"Account.deviceName\" + string \"some-device\" + ) + dict entry( + string \"Account.dhtProxyListUrl\" + string \"https://config.jami.net/proxyList\" + ) + dict entry( + string \"Account.displayName\" + string \"some-rendezvous-point-name\" + ) + dict entry( + string \"Account.dtmfType\" + string \"overrtp\" + ) + dict entry( + string \"Account.enable\" + string \"true\" + ) + dict entry( + string \"Account.hasCustomUserAgent\" + string \"false\" + ) + dict entry( + string \"Account.hostname\" + string \"bootstrap.jami.net\" + ) + dict entry( + string \"Account.localInterface\" + string \"default\" + ) + dict entry( + string \"Account.localModeratorsEnabled\" + string \"true\" + ) + dict entry( + string \"Account.mailbox\" + string \"\" + ) + dict entry( + string \"Account.managerUri\" + string \"\" + ) + dict entry( + string \"Account.managerUsername\" + string \"\" + ) + dict entry( + string \"Account.peerDiscovery\" + string \"false\" + ) + dict entry( + string \"Account.presenceSubscribeSupported\" + string \"true\" + ) + dict entry( + string \"Account.proxyEnabled\" + string \"false\" + ) + dict entry( + string \"Account.proxyPushToken\" + string \"\" + ) + dict entry( + string \"Account.proxyServer\" + string \"dhtproxy.jami.net:[80-95]\" + ) + dict entry( + string \"Account.publishedAddress\" + string \"\" + ) + dict entry( + string \"Account.publishedPort\" + string \"5060\" + ) + dict entry( + string \"Account.publishedSameAsLocal\" + string \"true\" + ) + dict entry( + string \"Account.rendezVous\" + string \"true\" + ) + dict entry( + string \"Account.ringtoneEnabled\" + string \"true\" + ) + dict entry( + string \"Account.ringtonePath\" + string \"/usr/share/ring/ringtones/default.opus\" + ) + dict entry( + string \"Account.type\" + string \"RING\" + ) + dict entry( + string \"Account.upnpEnabled\" + string \"true\" + ) + dict entry( + string \"Account.useragent\" + string \"\" + ) + dict entry( + string \"Account.username\" + string \"ccb8bbe2382343f7feb140710ab48aaf1b55634e\" + ) + dict entry( + string \"Account.videoEnabled\" + string \"true\" + ) + dict entry( + string \"Account.videoPortMax\" + string \"65534\" + ) + dict entry( + string \"Account.videoPortMin\" + string \"49152\" + ) + dict entry( + string \"DHT.PublicInCalls\" + string \"true\" + ) + dict entry( + string \"DHT.port\" + string \"7766\" + ) + dict entry( + string \"RingNS.account\" + string \"3989b55313a911b6f0c004748b49b254f35c9ef6\" + ) + dict entry( + string \"RingNS.uri\" + string \"\" + ) + dict entry( + string \"SRTP.enable\" + string \"true\" + ) + dict entry( + string \"SRTP.keyExchange\" + string \"sdes\" + ) + dict entry( + string \"SRTP.rtpFallback\" + string \"false\" + ) + dict entry( + string \"STUN.enable\" + string \"false\" + ) + dict entry( + string \"STUN.server\" + string \"\" + ) + dict entry( + string \"TLS.certificateFile\" + string \"/var/lib/jami/.local/share/jami/398af0c6b74ce101/ring_device.crt\" + ) + dict entry( + string \"TLS.certificateListFile\" + string \"\" + ) + dict entry( + string \"TLS.ciphers\" + string \"\" + ) + dict entry( + string \"TLS.method\" + string \"Automatic\" + ) + dict entry( + string \"TLS.negotiationTimeoutSec\" + string \"-1\" + ) + dict entry( + string \"TLS.password\" + string \"\" + ) + dict entry( + string \"TLS.privateKeyFile\" + string \"/var/lib/jami/.local/share/jami/398af0c6b74ce101/ring_device.key\" + ) + dict entry( + string \"TLS.requireClientCertificate\" + string \"true\" + ) + dict entry( + string \"TLS.serverName\" + string \"\" + ) + dict entry( + string \"TLS.verifyClient\" + string \"true\" + ) + dict entry( + string \"TLS.verifyServer\" + string \"true\" + ) + dict entry( + string \"TURN.enable\" + string \"true\" + ) + dict entry( + string \"TURN.password\" + string \"ring\" + ) + dict entry( + string \"TURN.realm\" + string \"ring\" + ) + dict entry( + string \"TURN.server\" + string \"turn.jami.net\" + ) + dict entry( + string \"TURN.username\" + string \"ring\" + ) + ] +") + +(test-equal "parse-account-details; username, alias and display name" + '("ccb8bbe2382343f7feb140710ab48aaf1b55634e" ;username + "some-rendezvous-point-name" ;alias + "some-rendezvous-point-name") ;displayName + (let ((account-details (parse-account-details getAccountDetails-reply))) + (list (assoc-ref account-details "Account.username") + (assoc-ref account-details "Account.alias") + (assoc-ref account-details "Account.displayName")))) + +(define getContacts-reply "\ +method return time=1627014042.752673 sender=:1.113 -> destination=:1.186 serial=220 reply_serial=2 + array [ + array [ + dict entry( + string \"added\" + string \"1578883327\" + ) + dict entry( + string \"confirmed\" + string \"true\" + ) + dict entry( + string \"id\" + string \"1c7d5a09464223442549fef172a3cf6f4de9b01c\" + ) + ] + array [ + dict entry( + string \"added\" + string \"1623107941\" + ) + dict entry( + string \"confirmed\" + string \"true\" + ) + dict entry( + string \"id\" + string \"5903c6c9ac5cb863c64e559add3d5d1c8c563449\" + ) + ] + array [ + dict entry( + string \"added\" + string \"1595996256\" + ) + dict entry( + string \"confirmed\" + string \"true\" + ) + dict entry( + string \"id\" + string \"ff2d72a548693214fb3a0f0f7a943b5e2bb9be03\" + ) + ] + ]") + +(test-equal "parse-account-contacts" + '((("added" . "1578883327") + ("confirmed" . "true") + ("id" . "1c7d5a09464223442549fef172a3cf6f4de9b01c")) + (("added" . "1623107941") + ("confirmed" . "true") + ("id" . "5903c6c9ac5cb863c64e559add3d5d1c8c563449")) + (("added" . "1595996256") + ("confirmed" . "true") + ("id" . "ff2d72a548693214fb3a0f0f7a943b5e2bb9be03"))) + (parse-contacts getContacts-reply)) + +(define getContacts-empty-reply "\ +method return time=1627400787.873988 sender=:1.1197 -> destination=:1.1463 serial=2127 reply_serial=2 + array [ + ]") + +(test-equal "parse-account-contacts, empty array" + '() + (parse-contacts getContacts-empty-reply)) + +(define %dummy-jami-account (jami-account + (archive "/tmp/dummy.gz"))) + +(define %dummy-jami-account-2 (jami-account + (archive "/tmp/dummy.gz") + (rendezvous-point? #t) + (peer-discovery? #f) + (bootstrap-hostnames '("bootstrap.me" + "fallback.another.host")) + (name-server-uri "https://my.name.server"))) + +(test-equal "jami-account->alist, no account detail value set" + '() + (jami-account->alist %dummy-jami-account)) + +(test-equal "jami-account->alist, with account detail values" + '(("Account.hostname" . "bootstrap.me;fallback.another.host") + ("Account.peerDiscovery" . "false") + ("Account.rendezVous" . "true") + ("RingNS.uri" . "https://my.name.server")) + (sort (jami-account->alist %dummy-jami-account-2) + (lambda (x y) + (string<=? (car x) (car y))))) + +(test-end) -- cgit v1.2.3 From 4673f817938d9d2b1b40a072ab2e0c44a32ccc97 Mon Sep 17 00:00:00 2001 From: Maxim Cournoyer Date: Mon, 2 Aug 2021 15:48:59 -0400 Subject: Revert "services: Add a service for Jami." This reverts commit 69dcc24c9f0cdfea674eb690e7755d26a25ced2b. It broke 'guix pull'. --- Makefile.am | 1 - doc/guix.texi | 228 ------------ gnu/build/jami-service.scm | 587 ----------------------------- gnu/local.mk | 5 +- gnu/services/telephony.scm | 684 +--------------------------------- gnu/tests/data/jami-dummy-account.dat | 392 ------------------- gnu/tests/telephony.scm | 366 ------------------ tests/services/telephony.scm | 446 ---------------------- 8 files changed, 3 insertions(+), 2706 deletions(-) delete mode 100644 gnu/build/jami-service.scm delete mode 100644 gnu/tests/data/jami-dummy-account.dat delete mode 100644 gnu/tests/telephony.scm delete mode 100644 tests/services/telephony.scm (limited to 'gnu/local.mk') diff --git a/Makefile.am b/Makefile.am index 5542aa1c56..d5ec909213 100644 --- a/Makefile.am +++ b/Makefile.am @@ -489,7 +489,6 @@ SCM_TESTS = \ tests/services/file-sharing.scm \ tests/services/configuration.scm \ tests/services/linux.scm \ - tests/services/telephony.scm \ tests/sets.scm \ tests/size.scm \ tests/status.scm \ diff --git a/doc/guix.texi b/doc/guix.texi index 9a9c85678c..2298d512a1 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -22526,234 +22526,6 @@ and Error. @node Telephony Services @subsection Telephony Services -@cindex telephony, services -The @code{(gnu services telephony)} module contains Guix service -definitions for telephony services. Currently it provides the following -services: - -@subsubheading Jami - -@cindex jami, service - -This section describes how to configure a Jami server that can be used -to host video (or audio) conferences, among other uses. The following -example demonstrates how to specify Jami account archives (backups) to -be provisioned automatically: - -@lisp -(service jami-service-type - (jami-configuration - (accounts - (list (jami-account - (archive "/etc/jami/unencrypted-account-1.gz")) - (jami-account - (archive "/etc/jami/unencrypted-account-2.gz")))))) -@end lisp - -When the accounts field is specified, the Jami account files of the -service found under @file{/var/lib/jami} are recreated every time the -service starts. - -Jami accounts and their corresponding backup archives can be generated -using either the @code{jami-qt} or @code{jami-gnome} Jami clients. The -accounts should not be password-protected, but it is wise to ensure -their files are only readable by @samp{root}. - -The next example shows how to declare that only some contacts should be -allowed to communicate with a given account: - -@lisp -(service jami-service-type - (jami-configuration - (accounts - (list (jami-account - (archive "/etc/jami/unencrypted-account-1.gz") - (peer-discovery? #t) - (rendezvous-point? #t) - (allowed-contacts - '("1dbcb0f5f37324228235564b79f2b9737e9a008f" - "2dbcb0f5f37324228235564b79f2b9737e9a008f"))))))) -@end lisp - -In this mode, only the declared @code{allowed-contacts} can initiate -communication with the Jami account. This can be used, for example, -with rendezvous point accounts to create a private video conferencing -space. - -To put the system administrator in full control of the conferences -hosted on their system, the Jami service supports the following actions: - -@example sh -# herd doc jami list-actions jami -(list-accounts - list-account-details - list-banned-contacts - list-contacts - list-moderators - add-moderator - ban-contact - enable-account - disable-account) -@end example - -The above actions aim to provide the most valuable actions for -moderation purposes, not to cover the whole Jami API. Users wanting to -interact with the Jami daemon from Guile may be interested in -experimenting with the @code{(gnu build jami-service)} module, which -powers the above Shepherd actions. - -@c TODO: This should be auto-generated from the doc already defined on -@c the shepherd-actions themselves in (gnu services telephony). -The @code{add-moderator} and @code{ban-contact} actions accept a contact -@emph{fingerprint} (40 characters long hash) as first argument and an -account fingerprint or username as second argument: - -@example sh -# herd add-moderator jami 1dbcb0f5f37324228235564b79f2b9737e9a008f \ - f3345f2775ddfe07a4b0d95daea111d15fbc1199 - -# herd list-moderators jami -Moderators for account f3345f2775ddfe07a4b0d95daea111d15fbc1199: - - 1dbcb0f5f37324228235564b79f2b9737e9a008f - -@end example - -In the case of @code{ban-contact}, the second username argument is -optional; when omitted, the account is banned from all Jami accounts: - -@example sh -# herd ban-contact jami 1dbcb0f5f37324228235564b79f2b9737e9a008f - -# herd list-banned-contacts jami -Banned contacts for account f3345f2775ddfe07a4b0d95daea111d15fbc1199: - - 1dbcb0f5f37324228235564b79f2b9737e9a008f - -@end example - -Banned contacts are also stripped from their moderation privileges. - -The @code{disable-account} action allows to completely disconnect an -account from the network, making it unreachable, while -@code{enable-account} does the inverse. They accept a single account -username or fingerprint as first argument: - -@example sh -# herd disable-account jami f3345f2775ddfe07a4b0d95daea111d15fbc1199 - -# herd list-accounts jami -The following Jami accounts are available: - - f3345f2775ddfe07a4b0d95daea111d15fbc1199 (dummy) [disabled] - -@end example - -The @code{list-account-details} action prints the detailed parameters of -each accounts in the Recutils format, which means the @command{recsel} -command can be used to select accounts of interest (@pxref{Selection -Expressions,,,recutils, GNU recutils manual}). Note that period -characters (@samp{.}) found in the account parameter keys are mapped to -underscores (@samp{_}) in the output, to meet the requirements of the -Recutils format. The following example shows how to print the account -fingerprints for all accounts operating in the rendezvous point mode: - -@example sh -# herd list-account-details jami | \ - recsel -p Account.username -e 'Account.rendezVous ~ "true"' -Account_username: f3345f2775ddfe07a4b0d95daea111d15fbc1199 -@end example - -The remaining actions should be self-explanatory. - -The complete set of available configuration options is detailed below. - -@c TODO: Ideally, the following fragments would be auto-generated at -@c build time, so that they needn't be manually duplicated. -@c Auto-generated via (configuration->documentation 'jami-configuration) -@deftp {Data Type} jami-configuration -Available @code{jami-configuration} fields are: - -@table @asis -@item @code{jamid} (default: @code{libring}) (type: package) -The Jami daemon package to use. - -@item @code{dbus} (default: @code{dbus}) (type: package) -The D-Bus package to use to start the required D-Bus session. - -@item @code{nss-certs} (default: @code{nss-certs}) (type: package) -The nss-certs package to use to provide TLS certificates. - -@item @code{enable-logging?} (default: @code{#t}) (type: boolean) -Whether to enable logging to syslog. - -@item @code{debug?} (default: @code{#f}) (type: boolean) -Whether to enable debug level messages. - -@item @code{auto-answer?} (default: @code{#f}) (type: boolean) -Whether to force automatic answer to incoming calls. - -@item @code{accounts} (default: @code{disabled}) (type: maybe-jami-account-list) -A list of Jami accounts to be (re-)provisioned every time the Jami -daemon service starts. When providing this field, the account -directories under @file{/var/lib/jami/} are recreated every time the -service starts, ensuring a consistent state. - -@end table - -@end deftp - -@c Auto-generated via (configuration->documentation 'jami-account) -@deftp {Data Type} jami-account -Available @code{jami-account} fields are: - -@table @asis -@item @code{archive} (type: string-or-computed-file) -The account archive (backup) file name of the account. This is used to -provision the account when the service starts. The account archive -should @emph{not} be encrypted. It is highly recommended to make it -readable only to the @samp{root} user (i.e., not in the store), to guard -against leaking the secret key material of the Jami account it contains. - -@item @code{allowed-contacts} (default: @code{disabled}) (type: maybe-account-fingerprint-list) -The list of allowed contacts for the account, entered as their 40 -characters long fingerprint. Messages or calls from accounts not in -that list will be rejected. When unspecified, the configuration of the -account archive is used as-is with respect to contacts and public -inbound calls/messaging allowance, which typically defaults to allow any -contact to communicate with the account. - -@item @code{moderators} (default: @code{disabled}) (type: maybe-account-fingerprint-list) -The list of contacts that should have moderation privileges (to ban, -mute, etc. other users) in rendezvous conferences, entered as their 40 -characters long fingerprint. When unspecified, the configuration of the -account archive is used as-is with respect to moderation, which -typically defaults to allow anyone to moderate. - -@item @code{rendezvous-point?} (default: @code{disabled}) (type: maybe-boolean) -Whether the account should operate in the rendezvous mode. In this -mode, all the incoming audio/video calls are mixed into a conference. -When left unspecified, the value from the account archive prevails. - -@item @code{peer-discovery?} (default: @code{disabled}) (type: maybe-boolean) -Whether peer discovery should be enabled. Peer discovery is used to -discover other OpenDHT nodes on the local network, which can be useful -to maintain communication between devices on such network even when the -connection to the the Internet has been lost. When left unspecified, -the value from the account archive prevails. - -@item @code{bootstrap-hostnames} (default: @code{disabled}) (type: maybe-string-list) -A list of hostnames or IPs pointing to OpenDHT nodes, that should be -used to initially join the OpenDHT network. When left unspecified, the -value from the account archive prevails. - -@item @code{name-server-uri} (default: @code{disabled}) (type: maybe-string) -The URI of the name server to use, that can be used to retrieve the -account fingerprint for a registered username. - -@end table - -@end deftp - -@subsubheading Murmur (VoIP server) - @cindex Murmur (VoIP server) @cindex VoIP server This section describes how to set up and run a Murmur server. Murmur is diff --git a/gnu/build/jami-service.scm b/gnu/build/jami-service.scm deleted file mode 100644 index d44e87387d..0000000000 --- a/gnu/build/jami-service.scm +++ /dev/null @@ -1,587 +0,0 @@ -;;; GNU Guix --- Functional package management for GNU -;;; Copyright © 2021 Maxim Cournoyer -;;; -;;; This file is part of GNU Guix. -;;; -;;; GNU Guix is free software; you can redistribute it and/or modify it -;;; under the terms of the GNU General Public License as published by -;;; the Free Software Foundation; either version 3 of the License, or (at -;;; your option) any later version. -;;; -;;; GNU Guix is distributed in the hope that it will be useful, but -;;; WITHOUT ANY WARRANTY; without even the implied warranty of -;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -;;; GNU General Public License for more details. -;;; -;;; You should have received a copy of the GNU General Public License -;;; along with GNU Guix. If not, see . - -;;; Commentary: -;;; -;;; This module contains helpers used as part of the jami-service-type -;;; definition. -;;; -;;; Code: - -(define-module (gnu build jami-service) - #:use-module (ice-9 format) - #:use-module (ice-9 match) - #:use-module (ice-9 peg) - #:use-module (ice-9 rdelim) - #:use-module (ice-9 regex) - #:use-module (rnrs io ports) - #:autoload (shepherd service) (fork+exec-command) - #:use-module (srfi srfi-1) - #:use-module (srfi srfi-26) - #:export (account-fingerprint? - account-details->recutil - get-accounts - get-usernames - set-account-details - add-account - account->username - username->account - username->contacts - enable-account - disable-account - - add-contact - remove-contact - - set-all-moderators - set-moderator - username->all-moderators? - username->moderators - - dbus-available-services - dbus-service-available? - - %send-dbus-binary - %send-dbus-bus - %send-dbus-user - %send-dbus-group - %send-dbus-debug - send-dbus - - with-retries)) - -;;; -;;; Utilities. -;;; - -(define-syntax-rule (with-retries n delay body ...) - "Retry the code in BODY up to N times until it doesn't raise an exception -nor return #f, else raise an error. A delay of DELAY seconds is inserted -before each retry." - (let loop ((attempts 0)) - (catch #t - (lambda () - (let ((result (begin body ...))) - (if (not result) - (error "failed attempt" attempts) - result))) - (lambda args - (if (< attempts n) - (begin - (sleep delay) ;else wait and retry - (loop (+ 1 attempts))) - (error "maximum number of retry attempts reached" - body ... args)))))) - -(define (alist->list alist) - "Flatten ALIST into a list." - (append-map (match-lambda - (() '()) - ((key . value) - (list key value))) - alist)) - -(define account-fingerprint-rx (make-regexp "[0-9A-f]{40}")) - -(define (account-fingerprint? val) - "A Jami account fingerprint is 40 characters long and only contains -hexadecimal characters." - (and (string? val) - (regexp-exec account-fingerprint-rx val))) - - -;;; -;;; D-Bus reply parser. -;;; - -(define (parse-dbus-reply reply) - "Return the parse tree of REPLY, a string returned by the 'dbus-send' -command." - ;; Refer to 'man 1 dbus-send' for the grammar reference. Note that the - ;; format of the replies doesn't match the format of the input, which is the - ;; one documented, but it gives an idea. For an even better reference, see - ;; the `print_iter' procedure of the 'dbus-print-message.c' file from the - ;; 'dbus' package sources. - (define-peg-string-patterns - "contents <- header (item / container (item / container*)?) - item <-- WS type WS value NL - container <- array / dict / variant - array <-- array-start (item / container)* array-end - dict <-- array-start dict-entry* array-end - dict-entry <-- dict-entry-start item item dict-entry-end - variant <-- variant-start item - type <-- 'string' / 'int16' / 'uint16' / 'int32' / 'uint32' / 'int64' / - 'uint64' / 'double' / 'byte' / 'boolean' / 'objpath' - value <-- (!NL .)* NL - header < (!NL .)* NL - variant-start < WS 'variant' - array-start < WS 'array [' NL - array-end < WS ']' NL - dict-entry-start < WS 'dict entry(' NL - dict-entry-end < WS ')' NL - DQ < '\"' - WS < ' '* - NL < '\n'*") - - (peg:tree (match-pattern contents reply))) - -(define (strip-quotes text) - "Strip the leading and trailing double quotes (\") characters from TEXT." - (let* ((text* (if (string-prefix? "\"" text) - (string-drop text 1) - text)) - (text** (if (string-suffix? "\"" text*) - (string-drop-right text* 1) - text*))) - text**)) - -(define (deserialize-item item) - "Return the value described by the ITEM parse tree as a Guile object." - ;; Strings are printed wrapped in double quotes (see the print_iter - ;; procedure in dbus-print-message.c). - (match item - (('item ('type "string") ('value value)) - (strip-quotes value)) - (('item ('type "boolean") ('value value)) - (if (string=? "true" value) - #t - #f)) - (('item _ ('value value)) - value))) - -(define (serialize-boolean bool) - "Return the serialized format expected by dbus-send for BOOL." - (format #f "boolean:~:[false~;true~]" bool)) - -(define (dict->alist dict-parse-tree) - "Translate a dict parse tree to an alist." - (define (tuples->alist tuples) - (map (lambda (x) (apply cons x)) tuples)) - - (match dict-parse-tree - ('dict - '()) - (('dict ('dict-entry keys values) ...) - (let ((keys* (map deserialize-item keys)) - (values* (map deserialize-item values))) - (tuples->alist (zip keys* values*)))))) - -(define (array->list array-parse-tree) - "Translate an array parse tree to a list." - (match array-parse-tree - ('array - '()) - (('array items ...) - (map deserialize-item items)))) - - -;;; -;;; Low-level, D-Bus-related procedures. -;;; - -;;; The following parameters are used in the jami-service-type service -;;; definition to conveniently customize the behavior of the send-dbus helper, -;;; even when called indirectly. -(define %send-dbus-binary (make-parameter "dbus-send")) -(define %send-dbus-bus (make-parameter #f)) -(define %send-dbus-user (make-parameter #f)) -(define %send-dbus-group (make-parameter #f)) -(define %send-dbus-debug (make-parameter #f)) - -(define* (send-dbus #:key service path interface method - bus - dbus-send - user group - timeout - arguments) - "Return the response of DBUS-SEND, else raise an error. Unless explicitly -provided, DBUS-SEND takes the value of the %SEND-DBUS-BINARY parameter. BUS -can be used to specify the bus address, such as 'unix:path=/var/run/jami/bus'. -Alternatively, the %SEND-DBUS-BUS parameter can be used. ARGUMENTS can be -used to pass input values to a D-Bus method call. TIMEOUT is the amount of -time to wait for a reply in milliseconds before giving up with an error. USER -and GROUP allow choosing under which user/group the DBUS-SEND command is -executed. Alternatively, the %SEND-DBUS-USER and %SEND-DBUS-GROUP parameters -can be used instead." - (let* ((command `(,(if dbus-send - dbus-send - (%send-dbus-binary)) - ,@(if (or bus (%send-dbus-bus)) - (list (string-append "--bus=" - (or bus (%send-dbus-bus)))) - '()) - "--print-reply" - ,@(if timeout - (list (format #f "--reply-timeout=~d" timeout)) - '()) - ,(string-append "--dest=" service) ;e.g., cx.ring.Ring - ,path ;e.g., /cx/ring/Ring/ConfigurationManager - ,(string-append interface "." method) - ,@(or arguments '()))) - (temp-port (mkstemp! (string-copy "/tmp/dbus-send-output-XXXXXXX"))) - (temp-file (port-filename temp-port))) - (dynamic-wind - (lambda () - (let* ((uid (or (and=> (or user (%send-dbus-user)) - (compose passwd:uid getpwnam)) -1)) - (gid (or (and=> (or group (%send-dbus-group)) - (compose group:gid getgrnam)) -1))) - (chown temp-port uid gid))) - (lambda () - (let ((pid (fork+exec-command command - #:user (or user (%send-dbus-user)) - #:group (or group (%send-dbus-group)) - #:log-file temp-file))) - (match (waitpid pid) - ((_ . status) - (let ((exit-status (status:exit-val status)) - (output (call-with-port temp-port get-string-all))) - (if (= 0 exit-status) - output - (error "the send-dbus command exited with: " - command exit-status output))))))) - (lambda () - (false-if-exception (delete-file temp-file)))))) - -(define (parse-account-ids reply) - "Return the Jami account IDs from REPLY, which is assumed to be the output -of the Jami D-Bus `getAccountList' method." - (array->list (parse-dbus-reply reply))) - -(define (parse-account-details reply) - "Parse REPLY, which is assumed to be the output of the Jami D-Bus -`getAccountDetails' method, and return its content as an alist." - (dict->alist (parse-dbus-reply reply))) - -(define (parse-contacts reply) - "Parse REPLY, which is assumed to be the output of the Jamid D-Bus -`getContacts' method, and return its content as an alist." - (match (parse-dbus-reply reply) - ('array - '()) - (('array dicts ...) - (map dict->alist dicts)))) - - -;;; -;;; Higher-level, D-Bus-related procedures. -;;; - -(define (validate-fingerprint fingerprint) - "Validate that fingerprint is 40 characters long." - (unless (account-fingerprint? fingerprint) - (error "Account fingerprint is not valid:" fingerprint))) - -(define (dbus-available-services) - "Return the list of available (acquired) D-Bus services." - (let ((reply (parse-dbus-reply - (send-dbus #:service "org.freedesktop.DBus" - #:path "/org/freedesktop/DBus" - #:interface "org.freedesktop.DBus" - #:method "ListNames")))) - ;; Remove entries such as ":1.7". - (remove (cut string-prefix? ":" <>) - (array->list reply)))) - -(define (dbus-service-available? service) - "Predicate to check for the D-Bus SERVICE availability." - (member service (dbus-available-services))) - -(define* (send-dbus/configuration-manager #:key method arguments timeout) - "Query the Jami D-Bus ConfigurationManager service." - (send-dbus #:service "cx.ring.Ring" - #:path "/cx/ring/Ring/ConfigurationManager" - #:interface "cx.ring.Ring.ConfigurationManager" - #:method method - #:arguments arguments - #:timeout timeout)) - -;;; The following methods are for internal use; they make use of the account -;;; ID, an implementation detail of Jami the user should not need to be -;;; concerned with. -(define (get-account-ids) - "Return the available Jami account identifiers (IDs). Account IDs are an -implementation detail used to identify the accounts in Jami." - (parse-account-ids - (send-dbus/configuration-manager #:method "getAccountList"))) - -(define (id->account-details id) - "Retrieve the account data associated with the given account ID." - (parse-account-details - (send-dbus/configuration-manager - #:method "getAccountDetails" - #:arguments (list (string-append "string:" id))))) - -(define (id->volatile-account-details id) - "Retrieve the account data associated with the given account ID." - (parse-account-details - (send-dbus/configuration-manager - #:method "getVolatileAccountDetails" - #:arguments (list (string-append "string:" id))))) - -(define (id->account id) - "Retrieve the complete account data associated with the given account ID." - (append (id->volatile-account-details id) - (id->account-details id))) - -(define %username-to-id-cache #f) - -(define (invalidate-username-to-id-cache!) - (set! %username-to-id-cache #f)) - -(define (username->id username) - "Return the first account ID corresponding to USERNAME." - (unless (assoc-ref %username-to-id-cache username) - (set! %username-to-id-cache - (append-map - (lambda (id) - (let* ((account (id->account id)) - (username (assoc-ref account "Account.username")) - (registered-name (assoc-ref account - "Account.registeredName"))) - `(,@(if username - (list (cons username id)) - '()) - ,@(if registered-name - (list (cons registered-name id)) - '())))) - (get-account-ids)))) - (or (assoc-ref %username-to-id-cache username) - (let ((message (format #f "Could not retrieve a local account ID\ - for ~:[username~;fingerprint~]" (account-fingerprint? username)))) - (error message username)))) - -(define (account->username account) - "Return USERNAME, the registered username associated with ACCOUNT, else its -public key fingerprint." - (or (assoc-ref account "Account.registeredName") - (assoc-ref account "Account.username"))) - -(define (id->username id) - "Return USERNAME, the registered username associated with ID, else its -public key fingerprint, else #f." - (account->username (id->account id))) - -(define (get-accounts) - "Return the list of all accounts, as a list of alists." - (map id->account (get-account-ids))) - -(define (get-usernames) - "Return the list of the usernames associated with the present accounts." - (map account->username (get-accounts))) - -(define (username->account username) - "Return the first account associated with USERNAME, else #f. -USERNAME can be either the account 40 characters public key fingerprint or a -registered username." - (find (lambda (account) - (member username - (list (assoc-ref account "Account.username") - (assoc-ref account "Account.registeredName")))) - (get-accounts))) - -(define (add-account archive) - "Import the Jami account ARCHIVE and return its account ID. The archive -should *not* be encrypted with a password. Return the username associated -with the account." - (invalidate-username-to-id-cache!) - (let ((reply (send-dbus/configuration-manager - #:method "addAccount" - #:arguments (list (string-append - "dict:string:string:Account.archivePath," - archive - ",Account.type,RING"))))) - ;; The account information takes some time to be populated. - (let ((id (deserialize-item (parse-dbus-reply reply)))) - (with-retries 20 1 - (let ((username (id->username id))) - (if (string-null? username) - #f - username)))))) - -(define (remove-account username) - "Delete the Jami account associated with USERNAME, the account 40 characters -fingerprint or a registered username." - (let ((id (username->id username))) - (send-dbus/configuration-manager - #:method "removeAccount" - #:arguments (list (string-append "string:" id)))) - (invalidate-username-to-id-cache!)) - -(define* (username->contacts username) - "Return the contacts associated with the account of USERNAME as two values; -the first one being the regular contacts and the second one the banned -contacts. USERNAME can be either the account 40 characters public key -fingerprint or a registered username. The contacts returned are represented -using their 40 characters fingerprint." - (let* ((id (username->id username)) - (reply (send-dbus/configuration-manager - #:method "getContacts" - #:arguments (list (string-append "string:" id)))) - (all-contacts (parse-contacts reply)) - (banned? (lambda (contact) - (and=> (assoc-ref contact "banned") - (cut string=? "true" <>)))) - (banned (filter banned? all-contacts)) - (not-banned (filter (negate banned?) all-contacts)) - (fingerprint (cut assoc-ref <> "id"))) - (values (map fingerprint not-banned) - (map fingerprint banned)))) - -(define* (remove-contact contact username #:key ban?) - "Remove CONTACT, the 40 characters public key fingerprint of a contact, from -the account associated with USERNAME (either a fingerprint or a registered -username). When BAN? is true, also mark the contact as banned." - (validate-fingerprint contact) - (let ((id (username->id username))) - (send-dbus/configuration-manager - #:method "removeContact" - #:arguments (list (string-append "string:" id) - (string-append "string:" contact) - (serialize-boolean ban?))))) - -(define (add-contact contact username) - "Add CONTACT, the 40 characters public key fingerprint of a contact, to the -account of USERNAME (either a fingerprint or a registered username)." - (validate-fingerprint contact) - (let ((id (username->id username))) - (send-dbus/configuration-manager - #:method "addContact" - #:arguments (list (string-append "string:" id) - (string-append "string:" contact))))) - -(define* (set-account-details details username #:key timeout) - "Set DETAILS, an alist containing the key value pairs to set for the account -of USERNAME, a registered username or account fingerprint. The value of the -parameters not provided are unchanged. TIMEOUT is a value in milliseconds to -pass to the `send-dbus/configuration-manager' procedure." - (let* ((id (username->id username)) - (current-details (id->account-details id)) - (updated-details (map (match-lambda - ((key . value) - (or (and=> (assoc-ref details key) - (cut cons key <>)) - (cons key value)))) - current-details)) - ;; dbus-send does not permit sending null strings (it throws a - ;; "malformed dictionary" error). Luckily they seem to have the - ;; semantic of "default account value" in Jami; so simply drop them. - (updated-details* (remove (match-lambda - ((_ . value) - (string-null? value))) - updated-details))) - (send-dbus/configuration-manager - #:timeout timeout - #:method "setAccountDetails" - #:arguments - (list (string-append "string:" id) - (string-append "dict:string:string:" - (string-join (alist->list updated-details*) - ",")))))) - -(define (set-all-moderators enabled? username) - "Set the 'AllModerators' property to enabled? for the account of USERNAME, a -registered username or account fingerprint." - (let ((id (username->id username))) - (send-dbus/configuration-manager - #:method "setAllModerators" - #:arguments - (list (string-append "string:" id) - (serialize-boolean enabled?))))) - -(define (username->all-moderators? username) - "Return the 'AllModerators' property for the account of USERNAME, a -registered username or account fingerprint." - (let* ((id (username->id username)) - (reply (send-dbus/configuration-manager - #:method "isAllModerators" - #:arguments - (list (string-append "string:" id))))) - (deserialize-item (parse-dbus-reply reply)))) - -(define (username->moderators username) - "Return the moderators for the account of USERNAME, a registered username or -account fingerprint." - (let* ((id (username->id username)) - (reply (send-dbus/configuration-manager - #:method "getDefaultModerators" - #:arguments - (list (string-append "string:" id))))) - (array->list (parse-dbus-reply reply)))) - -(define (set-moderator contact enabled? username) - "Set the moderator flag to ENABLED? for CONTACT, the 40 characters public -key fingerprint of a contact for the account of USERNAME, a registered -username or account fingerprint." - (validate-fingerprint contact) - (let* ((id (username->id username))) - (send-dbus/configuration-manager #:method "setDefaultModerator" - #:arguments - (list (string-append "string:" id) - (string-append "string:" contact) - (serialize-boolean enabled?))))) - -(define (disable-account username) - "Disable the account known by USERNAME, a registered username or account -fingerprint." - (set-account-details '(("Account.enable" . "false")) username - ;; Waiting for the reply on this command takes a very - ;; long time that trips the default D-Bus timeout value - ;; (25 s), for some reason. - #:timeout 60000)) - -(define (enable-account username) - "Enable the account known by USERNAME, a registered username or account -fingerprint." - (set-account-details '(("Account.enable" . "true")) username)) - - -;;; -;;; Presentation procedures. -;;; - -(define (.->_ text) - "Map each period character to underscore characters." - (string-map (match-lambda - (#\. #\_) - (c c)) - text)) - -(define (account-details->recutil account-details) - "Serialize the account-details alist into a recutil string. Period -characters in the keys are normalized to underscore to meet Recutils' format -requirements." - (define (pair->recutil-property pair) - (match pair - ((key . value) - (string-append (.->_ key) ": " value)))) - - (define sorted-account-details - ;; Have the account username, display name and alias appear first, for - ;; convenience. - (let ((first-items '("Account.username" - "Account.displayName" - "Account.alias"))) - (append (map (cut assoc <> account-details) first-items) - (fold alist-delete account-details first-items)))) - - (string-join (map pair->recutil-property sorted-account-details) "\n")) - -;; Local Variables: -;; eval: (put 'with-retries 'scheme-indent-function 2) -;; End: diff --git a/gnu/local.mk b/gnu/local.mk index 66a2ba52bc..9e8f2d702d 100644 --- a/gnu/local.mk +++ b/gnu/local.mk @@ -685,7 +685,6 @@ GNU_SYSTEM_MODULES = \ %D%/build/chromium-extension.scm \ %D%/build/cross-toolchain.scm \ %D%/build/image.scm \ - %D%/build/jami-service.scm \ %D%/build/file-systems.scm \ %D%/build/hurd-boot.scm \ %D%/build/install.scm \ @@ -723,7 +722,6 @@ GNU_SYSTEM_MODULES = \ %D%/tests/security-token.scm \ %D%/tests/singularity.scm \ %D%/tests/ssh.scm \ - %D%/tests/telephony.scm \ %D%/tests/version-control.scm \ %D%/tests/virtualization.scm \ %D%/tests/web.scm @@ -1883,8 +1881,7 @@ dist_patch_DATA = \ %D%/packages/patches/ytnef-CVE-2021-3403.patch \ %D%/packages/patches/ytnef-CVE-2021-3404.patch \ %D%/packages/patches/zstd-CVE-2021-24031_CVE-2021-24032.patch \ - %D%/packages/patches/zziplib-CVE-2018-16548.patch \ - %D%/tests/data/jami-dummy-account.dat + %D%/packages/patches/zziplib-CVE-2018-16548.patch MISC_DISTRO_FILES = \ %D%/packages/ld-wrapper.in diff --git a/gnu/services/telephony.scm b/gnu/services/telephony.scm index fd90840324..e1259cc2df 100644 --- a/gnu/services/telephony.scm +++ b/gnu/services/telephony.scm @@ -1,6 +1,5 @@ ;;; GNU Guix --- Functional package management for GNU ;;; Copyright © 2017 nee -;;; Copyright © 2021 Maxim Cournoyer ;;; ;;; This file is part of GNU Guix. ;;; @@ -18,45 +17,16 @@ ;;; along with GNU Guix. If not, see . (define-module (gnu services telephony) - #:use-module ((gnu build jami-service) #:select (account-fingerprint?)) - #:use-module ((gnu services) #:hide (delete)) - #:use-module (gnu services configuration) + #:use-module (gnu services) #:use-module (gnu services shepherd) #:use-module (gnu system shadow) #:use-module (gnu packages admin) - #:use-module (gnu packages certs) - #:use-module (gnu packages glib) - #:use-module (gnu packages jami) #:use-module (gnu packages telephony) #:use-module (guix records) - #:use-module (guix modules) - #:use-module (guix packages) #:use-module (guix gexp) #:use-module (srfi srfi-1) - #:use-module (srfi srfi-2) - #:use-module (srfi srfi-26) - #:use-module (ice-9 format) #:use-module (ice-9 match) - #:export (jami-account - jami-account-archive - jami-account-allowed-contacts - jami-account-moderators - jami-account-rendezvous-point? - jami-account-discovery? - jami-account-bootstrap-uri - jami-account-name-server-uri - - jami-configuration - jami-configuration-jamid - jami-configuration-dbus - jami-configuration-enable-logging? - jami-configuration-debug? - jami-configuration-auto-answer? - jami-configuration-accounts - - jami-service-type - - murmur-configuration + #:export (murmur-configuration make-murmur-configuration murmur-configuration? murmur-configuration-package @@ -104,652 +74,6 @@ murmur-service-type)) - -;;; -;;; Jami daemon. -;;; - -;;; XXX: Passing a computed-file object as the account is used for tests. -(define (string-or-computed-file? val) - (or (string? val) - (computed-file? val))) - -(define (string-list? val) - (and (list? val) - (and-map string? val))) - -(define (account-fingerprint-list? val) - (and (list? val) - (and-map account-fingerprint? val))) - -(define-maybe string-list) - -(define-maybe/no-serialization account-fingerprint-list) - -(define-maybe boolean) - -(define-maybe string) - -;;; The following serializers are used to derive an account details alist from -;;; a record. -(define (serialize-string-list _ val) - (string-join val ";")) - -(define (serialize-boolean _ val) - (format #f "~:[false~;true~]" val)) - -(define (serialize-string _ val) - val) - -;;; Note: Serialization is used to produce an account details alist that can -;;; be passed to the SET-ACCOUNT-DETAILS procedure. Fields that do not map to -;;; a Jami account 'detail' should have their serialization disabled via the -;;; 'empty-serializer' procedure. -(define-configuration jami-account - (archive - (string-or-computed-file) - "The account archive (backup) file name of the account. This is used to -provision the account when the service starts. The account archive should -@emph{not} be encrypted. It is highly recommended to make it readable only to -the @samp{root} user (i.e., not in the store), to guard against leaking the -secret key material of the Jami account it contains." - empty-serializer) - (allowed-contacts - (maybe-account-fingerprint-list 'disabled) - "The list of allowed contacts for the account, entered as their 40 -characters long fingerprint. Messages or calls from accounts not in that list -will be rejected. When unspecified, the configuration of the account archive -is used as-is with respect to contacts and public inbound calls/messaging -allowance, which typically defaults to allow any contact to communicate with -the account." - empty-serializer) - (moderators - (maybe-account-fingerprint-list 'disabled) - "The list of contacts that should have moderation privileges (to ban, mute, -etc. other users) in rendezvous conferences, entered as their 40 characters -long fingerprint. When unspecified, the configuration of the account archive -is used as-is with respect to moderation, which typically defaults to allow -anyone to moderate." - empty-serializer) - ;; The serializable fields below are to be set with set-account-details. - (rendezvous-point? - (maybe-boolean 'disabled) - "Whether the account should operate in the rendezvous mode. In this mode, -all the incoming audio/video calls are mixed into a conference. When left -unspecified, the value from the account archive prevails.") - (peer-discovery? - (maybe-boolean 'disabled) - "Whether peer discovery should be enabled. Peer discovery is used to -discover other OpenDHT nodes on the local network, which can be useful to -maintain communication between devices on such network even when the -connection to the the Internet has been lost. When left unspecified, the -value from the account archive prevails.") - (bootstrap-hostnames - (maybe-string-list 'disabled) - "A list of hostnames or IPs pointing to OpenDHT nodes, that should be used -to initially join the OpenDHT network. When left unspecified, the value from -the account archive prevails.") - (name-server-uri - (maybe-string 'disabled) - "The URI of the name server to use, that can be used to retrieve the -account fingerprint for a registered username.")) - -(define (jami-account->alist jami-account-object) - "Serialize the JAMI-ACCOUNT object as an alist suitable to be passed to -SET-ACCOUNT-DETAILS." - (define (field-name->account-detail name) - (match name - ('rendezvous-point? "Account.rendezVous") - ('peer-discovery? "Account.peerDiscovery") - ('bootstrap-hostnames "Account.hostname") - ('name-server-uri "RingNS.uri") - (_ #f))) - - (filter-map (lambda (field) - (and-let* ((name (field-name->account-detail - (configuration-field-name field))) - (value ((configuration-field-serializer field) - name ((configuration-field-getter field) - jami-account-object))) - ;; The define-maybe default serializer produces an - ;; empty string for the 'disabled value. - (value* (if (string-null? value) - #f - value))) - (cons name value*))) - jami-account-fields)) - -(define (jami-account-list? val) - (and (list? val) - (and-map jami-account? val))) - -(define-maybe/no-serialization jami-account-list) - -(define-configuration/no-serialization jami-configuration - (jamid - (package libring) - "The Jami daemon package to use.") - (dbus - (package dbus) - "The D-Bus package to use to start the required D-Bus session.") - (nss-certs - (package nss-certs) - "The nss-certs package to use to provide TLS certificates.") - (enable-logging? - (boolean #t) - "Whether to enable logging to syslog.") - (debug? - (boolean #f) - "Whether to enable debug level messages.") - (auto-answer? - (boolean #f) - "Whether to force automatic answer to incoming calls.") - (accounts - (maybe-jami-account-list 'disabled) - "A list of Jami accounts to be (re-)provisioned every time the Jami daemon -service starts. When providing this field, the account directories under -@file{/var/lib/jami/} are recreated every time the service starts, ensuring a -consistent state.")) - -(define %jami-accounts - (list (user-group (name "jami") (system? #t)) - (user-account - (name "jami") - (group "jami") - (system? #t) - (comment "Jami daemon user") - (home-directory "/var/lib/jami")))) - -(define (jami-configuration->command-line-arguments config) - "Derive the command line arguments to used to launch the Jami daemon from -CONFIG, a object." - (match-record config - (jamid dbus enable-logging? debug? auto-answer?) - `(,(file-append jamid "/lib/ring/dring") - "--persistent" ;stay alive after client quits - ,@(if enable-logging? - '() ;logs go to syslog by default - (list "--console")) ;else stdout/stderr - ,@(if debug? - (list "--debug") - '()) - ,@(if auto-answer? - (list "--auto-answer") - '())))) - -(define (jami-dbus-session-activation config) - "Create a directory to hold the Jami D-Bus session socket." - (with-imported-modules (source-module-closure '((gnu build activation))) - #~(begin - (use-modules (gnu build activation)) - (let ((user (getpwnam "jami"))) - (mkdir-p/perms "/var/run/jami" user #o700))))) - -(define (jami-shepherd-services config) - "Return a running the Jami daemon." - (let* ((jamid (jami-configuration-jamid config)) - (nss-certs (jami-configuration-nss-certs config)) - (dbus (jami-configuration-dbus config)) - (dbus-daemon (file-append dbus "/bin/dbus-daemon")) - (dbus-send (file-append dbus "/bin/dbus-send")) - (accounts (jami-configuration-accounts config)) - (declarative-mode? (not (eq? 'disabled accounts)))) - - (with-imported-modules (source-module-closure - '((gnu build jami-service) - (gnu build shepherd) - (gnu system file-systems))) - - (define list-accounts-action - (shepherd-action - (name 'list-accounts) - (documentation "List the available Jami accounts. Return the account -details alists keyed by their account username.") - (procedure - #~(lambda _ - (parameterize ((%send-dbus-binary #$dbus-send) - (%send-dbus-bus "unix:path=/var/run/jami/bus") - (%send-dbus-user "jami") - (%send-dbus-group "jami")) - ;; Print the accounts summary or long listing, according to - ;; user-provided option. - (let* ((usernames (get-usernames)) - (accounts (map-in-order username->account usernames))) - (match accounts - (() ;empty list - (format #t "There is no Jami account available.~%")) - ((one two ...) - (format #t "The following Jami accounts are available:~%") - (for-each - (lambda (account) - (define fingerprint (assoc-ref account - "Account.username")) - (define human-friendly-name - (or (assoc-ref account - "Account.registeredName") - (assoc-ref account - "Account.displayName") - (assoc-ref account - "Account.alias"))) - (define disabled? - (and=> (assoc-ref account "Account.enable") - (cut string=? "false" <>))) - - (format #t " - ~a~@[ (~a)~] ~:[~;[disabled]~]~%" - fingerprint human-friendly-name disabled?)) - accounts) - (display "\n"))) - ;; Return the account-details-list alist. - (map cons usernames accounts))))))) - - (define list-account-details-action - (shepherd-action - (name 'list-account-details) - (documentation "Display the account details of the available Jami -accounts in the @code{recutils} format. Return the account details alists -keyed by their account username.") - (procedure - #~(lambda _ - (parameterize ((%send-dbus-binary #$dbus-send) - (%send-dbus-bus "unix:path=/var/run/jami/bus") - (%send-dbus-user "jami") - (%send-dbus-group "jami")) - (let* ((usernames (get-usernames)) - (accounts (map-in-order username->account usernames))) - (for-each (lambda (account) - (display (account-details->recutil account)) - (display "\n\n")) - accounts) - (map cons usernames accounts))))))) - - (define list-contacts-action - (shepherd-action - (name 'list-contacts) - (documentation "Display the contacts for each Jami account. Return -an alist containing the contacts keyed by the account usernames.") - (procedure - #~(lambda _ - (parameterize ((%send-dbus-binary #$dbus-send) - (%send-dbus-bus "unix:path=/var/run/jami/bus") - (%send-dbus-user "jami") - (%send-dbus-group "jami")) - (let* ((usernames (get-usernames)) - (contacts (map-in-order username->contacts usernames))) - (for-each (lambda (username contacts) - (format #t "Contacts for account ~a:~%" - username) - (format #t "~{ - ~a~%~}~%" contacts)) - usernames contacts) - (map cons usernames contacts))))))) - - (define list-moderators-action - (shepherd-action - (name 'list-moderators) - (documentation "Display the moderators for each Jami account. Return -an alist containing the moderators keyed by the account usernames.") - (procedure - #~(lambda _ - (parameterize ((%send-dbus-binary #$dbus-send) - (%send-dbus-bus "unix:path=/var/run/jami/bus") - (%send-dbus-user "jami") - (%send-dbus-group "jami")) - (let* ((usernames (get-usernames)) - (moderators (map-in-order username->moderators - usernames))) - (for-each - (lambda (username moderators) - (if (username->all-moderators? username) - (format #t "Anyone can moderate for account ~a~%" - username) - (begin - (format #t "Moderators for account ~a:~%" username) - (format #t "~{ - ~a~%~}~%" moderators)))) - usernames moderators) - (map cons usernames moderators))))))) - - (define add-moderator-action - (shepherd-action - (name 'add-moderator) - (documentation "Add a moderator for a given Jami account. The -MODERATOR contact must be given as its 40 characters fingerprint, while the -Jami account can be provided as its registered USERNAME or fingerprint. - -@example -herd add-moderator jami 1dbcb0f5f37324228235564b79f2b9737e9a008f username -@end example - -Return the moderators for the account known by USERNAME.") - (procedure - #~(lambda (_ moderator username) - (parameterize ((%send-dbus-binary #$dbus-send) - (%send-dbus-bus "unix:path=/var/run/jami/bus") - (%send-dbus-user "jami") - (%send-dbus-group "jami")) - (set-all-moderators #f username) - (add-contact moderator username) - (set-moderator moderator #t username) - (username->moderators username)))))) - - (define ban-contact-action - (shepherd-action - (name 'ban-contact) - (documentation "Ban a contact for a given or all Jami accounts, and -clear their moderator flag. The CONTACT must be given as its 40 characters -fingerprint, while the Jami account can be provided as its registered USERNAME -or fingerprint, or omitted. When the account is omitted, CONTACT is banned -from all accounts. - -@example -herd ban-contact jami 1dbcb0f5f37324228235564b79f2b9737e9a008f [username] -@end example") - (procedure - #~(lambda* (_ contact #:optional username) - (parameterize ((%send-dbus-binary #$dbus-send) - (%send-dbus-bus "unix:path=/var/run/jami/bus") - (%send-dbus-user "jami") - (%send-dbus-group "jami")) - (let ((usernames (or (and=> username list) - (get-usernames)))) - (for-each (lambda (username) - (set-moderator contact #f username) - (remove-contact contact username #:ban? #t)) - usernames))))))) - - (define list-banned-contacts-action - (shepherd-action - (name 'list-banned-contacts) - (documentation "List the banned contacts for each accounts. Return -an alist of the banned contacts, keyed by the account usernames.") - (procedure - #~(lambda _ - (parameterize ((%send-dbus-binary #$dbus-send) - (%send-dbus-bus "unix:path=/var/run/jami/bus") - (%send-dbus-user "jami") - (%send-dbus-group "jami")) - - (define banned-contacts - (let ((usernames (get-usernames))) - (map cons usernames - (map-in-order (lambda (x) - (receive (_ banned) - (username->contacts x) - banned)) - usernames)))) - - (for-each (match-lambda - ((username . banned) - (unless (null? banned) - (format #t "Banned contacts for account ~a:~%" - username) - (format #t "~{ - ~a~%~}~%" banned)))) - banned-contacts) - banned-contacts))))) - - (define enable-account-action - (shepherd-action - (name 'enable-account) - (documentation "Enable an account. It takes USERNAME as an argument, -either a registered username or the fingerprint of the account.") - (procedure - #~(lambda (_ username) - (parameterize ((%send-dbus-binary #$dbus-send) - (%send-dbus-bus "unix:path=/var/run/jami/bus") - (%send-dbus-user "jami") - (%send-dbus-group "jami")) - (enable-account username)))))) - - (define disable-account-action - (shepherd-action - (name 'disable-account) - (documentation "Disable an account. It takes USERNAME as an -argument, either a registered username or the fingerprint of the account.") - (procedure - #~(lambda (_ username) - (parameterize ((%send-dbus-binary #$dbus-send) - (%send-dbus-bus "unix:path=/var/run/jami/bus") - (%send-dbus-user "jami") - (%send-dbus-group "jami")) - (disable-account username)))))) - - (list (shepherd-service - (documentation "Run a D-Bus session for the Jami daemon.") - (provision '(jami-dbus-session)) - (modules `((gnu build shepherd) - (gnu build jami-service) - (gnu system file-systems) - ,@%default-modules)) - ;; The requirement on dbus-system is to ensure other required - ;; activation for D-Bus, such as a /etc/machine-id file. - (requirement '(dbus-system syslogd)) - (start - #~(lambda args - (define pid - ((make-forkexec-constructor/container - (list #$dbus-daemon "--session" - "--address=unix:path=/var/run/jami/bus" - "--nofork" "--syslog-only" "--nopidfile") - #:mappings (list (file-system-mapping - (source "/dev/log") ;for syslog - (target source)) - (file-system-mapping - (source "/var/run/jami") - (target source) - (writable? #t))) - #:user "jami" - #:group "jami" - #:environment-variables - ;; This is so that the cx.ring.Ring service D-Bus - ;; definition is found by dbus-send. - (list (string-append "XDG_DATA_DIRS=" - #$jamid "/share"))))) - - ;; XXX: This manual synchronization probably wouldn't be - ;; needed if we were using a PID file, but providing it via a - ;; customized config file with would not override - ;; the one inherited from the base config of D-Bus. - (let ((sock (socket PF_UNIX SOCK_STREAM 0))) - (with-retries 20 1 (catch 'system-error - (lambda () - (connect sock AF_UNIX - "/var/run/jami/bus") - (close-port sock) - #t) - (lambda args - #f)))) - - pid)) - (stop #~(make-kill-destructor))) - - (shepherd-service - (documentation "Run the Jami daemon.") - (provision '(jami)) - (actions (list list-accounts-action - list-account-details-action - list-contacts-action - list-moderators-action - add-moderator-action - ban-contact-action - list-banned-contacts-action - enable-account-action - disable-account-action)) - (requirement '(jami-dbus-session)) - (modules `((ice-9 format) - (ice-9 ftw) - (ice-9 match) - (ice-9 receive) - (srfi srfi-1) - (srfi srfi-26) - (gnu build jami-service) - (gnu build shepherd) - (gnu system file-systems) - ,@%default-modules)) - (start - #~(lambda args - (define (delete-file-recursively/safe file) - ;; Ensure we're not deleting things outside of - ;; /var/lib/jami. This prevents a possible attack in case - ;; the daemon is compromised and an attacker gains write - ;; access to /var/lib/jami. - (let ((parent-directory (dirname file))) - (if (eq? 'symlink (stat:type (stat parent-directory))) - (error "abnormality detected; unexpected symlink found at" - parent-directory) - (delete-file-recursively file)))) - - (when #$declarative-mode? - ;; Clear the Jami configuration and accounts, to enforce the - ;; declared state. - (catch #t - (lambda () - (for-each (cut delete-file-recursively/safe <>) - '("/var/lib/jami/.cache/jami" - "/var/lib/jami/.config/jami" - "/var/lib/jami/.local/share/jami" - "/var/lib/jami/accounts"))) - (lambda args - #t)) - ;; Copy the Jami account archives from somewhere readable - ;; by root to a place only the jami user can read. - (let* ((accounts-dir "/var/lib/jami/accounts/") - (pwd (getpwnam "jami")) - (user (passwd:uid pwd)) - (group (passwd:gid pwd))) - (mkdir-p accounts-dir) - (chown accounts-dir user group) - (for-each (lambda (f) - (let ((dest (string-append accounts-dir - (basename f)))) - (copy-file f dest) - (chown dest user group))) - '#$(and declarative-mode? - (map jami-account-archive accounts))))) - - ;; Start the daemon. - (define daemon-pid - ((make-forkexec-constructor/container - '#$(jami-configuration->command-line-arguments config) - #:mappings - (list (file-system-mapping - (source "/dev/log") ;for syslog - (target source)) - (file-system-mapping - (source "/var/lib/jami") - (target source) - (writable? #t)) - (file-system-mapping - (source "/var/run/jami") - (target source) - (writable? #t)) - ;; Expose TLS certificates for GnuTLS. - (file-system-mapping - (source #$(file-append nss-certs "/etc/ssl/certs")) - (target "/etc/ssl/certs"))) - #:user "jami" - #:group "jami" - #:environment-variables - (list (string-append "DBUS_SESSION_BUS_ADDRESS=" - "unix:path=/var/run/jami/bus") - ;; Expose TLS certificates for OpenSSL. - "SSL_CERT_DIR=/etc/ssl/certs")))) - - (parameterize ((%send-dbus-binary #$dbus-send) - (%send-dbus-bus "unix:path=/var/run/jami/bus") - (%send-dbus-user "jami") - (%send-dbus-group "jami")) - - ;; Wait until the service name has been acquired by D-Bus. - (with-retries 20 1 - (dbus-service-available? "cx.ring.Ring")) - - (when #$declarative-mode? - ;; Provision the accounts via the D-Bus API of the daemon. - (let* ((jami-account-archives - (map (cut string-append - "/var/lib/jami/accounts/" <>) - (scandir "/var/lib/jami/accounts/" - (lambda (f) - (not (member f '("." ".."))))))) - (usernames (map-in-order (cut add-account <>) - jami-account-archives))) - - (define (archive-name->username archive) - (list-ref - usernames - (list-index (lambda (f) - (string-suffix? (basename archive) f)) - jami-account-archives))) - - (for-each - (lambda (archive allowed-contacts moderators - account-details) - (let ((username (archive-name->username - archive))) - (when (not (eq? 'disabled allowed-contacts)) - ;; Reject calls from unknown contacts. - (set-account-details - '(("DHT.PublicInCalls" . "false")) username) - ;; Remove all contacts. - (for-each (cut remove-contact <> username) - (username->contacts username)) - ;; Add allowed ones. - (for-each (cut add-contact <> username) - allowed-contacts)) - (when (not (eq? 'disabled moderators)) - ;; Disable the 'AllModerators' property. - (set-all-moderators #f username) - ;; Remove all moderators. - (for-each (cut set-moderator <> #f username) - (username->moderators username)) - ;; Add declared moderators. - (for-each (cut set-moderator <> #t username) - moderators)) - ;; Set the various account parameters. - (set-account-details account-details username))) - '#$(and declarative-mode? - (map-in-order (cut jami-account-archive <>) - accounts)) - '#$(and declarative-mode? - (map-in-order - (cut jami-account-allowed-contacts <>) - accounts)) - '#$(and declarative-mode? - (map-in-order (cut jami-account-moderators <>) - accounts)) - '#$(and declarative-mode? - (map-in-order jami-account->alist accounts)))))) - - ;; Finally, return the PID of the daemon process. - daemon-pid)) - (stop - #~(lambda (pid . args) - (kill pid SIGKILL) - ;; Wait for the process to exit; this prevents overlapping - ;; processes when issuing 'herd restart'. - (waitpid pid) - #f))))))) - -(define jami-service-type - (service-type - (name 'jami) - (default-value (jami-configuration)) - (extensions - (list (service-extension shepherd-root-service-type - jami-shepherd-services) - (service-extension account-service-type - (const %jami-accounts)) - (service-extension activation-service-type - jami-dbus-session-activation))) - (description "Run the Jami daemon (@command{dring}). This service is -geared toward the use case of hosting Jami rendezvous points over a headless -server. If you use Jami on your local machine, you may prefer to setup a user -Shepherd service for it instead; this way, the daemon will be shared via your -normal user D-Bus session bus."))) - - -;;; -;;; Murmur. -;;; - ;; https://github.com/mumble-voip/mumble/blob/master/scripts/murmur.ini (define-record-type* murmur-configuration @@ -981,7 +305,3 @@ suite.") (service-extension account-service-type murmur-accounts))) (default-value (murmur-configuration)))) - -;; Local Variables: -;; eval: (put 'with-retries 'scheme-indent-function 2) -;; End: diff --git a/gnu/tests/data/jami-dummy-account.dat b/gnu/tests/data/jami-dummy-account.dat deleted file mode 100644 index 0e908396ca..0000000000 --- a/gnu/tests/data/jami-dummy-account.dat +++ /dev/null @@ -1,392 +0,0 @@ -;;; -*- mode: scheme; -*- -;;; JSON extracted from an actual Jami account and processed with -;;; Emacs/guile-json. -(define %jami-account-content-sexp - '(("RINGCAKEY" . "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUpRd0lCQURBTkJna3F\ -oa2lHOXcwQkFRRUZBQVNDQ1Mwd2dna3BBZ0VBQW9JQ0FRQzBxWUozSkYvTzhQRGEKRnUwRnpRcHBCaD\ -gybGJMdURrNTlVU0I0MUJSaS9kdDZGV1BRN29YOVpsY25vNGZzM2dmUHQ0dU1hRVBkVFBGKwowbGN2Q\ -jc2cytQTEFlcjlOZGpVQzQ2ZXp0UnNiNE9aQXc4ZUk1M3EwSU04QWJFd0o0ZjllLzBmQUFueHgrK3Qw\ -CmZDeGV1YTBUaVBqVHBpZVJMNmpwQkd5UGI2Qk5pU2ViTkZCNzJOMTBnbzI4REVQYlhkNE9CNkN1blZ\ -5RGZKSU0KOC9PRy8rMndNamI4WkRwT3JrYy93U2ZHbnQyZXA3U0xKSkgwOFIzV1FKWklsSndrcGdLTH\ -FRakVwWFNpclN4dAozSkdtdXdBdE9LaXFFTXh1R043elV3ZlNINEtHdkRaUFNkZklZVXJ3eEp4aDJZZ\ -3lobG5RRC9SSnhRN3d4YlJBCjFhMUZVV0FzbDhLODk5cEtESk1GL09VOWZMRUx6QlViblpaRDRmSlg1\ -NmcyTlluUnJobS94NG9FbFk0MFNYMUUKcHYzb01hNnZrVGN5RjJnUFhKL2FkUVJoS0dFaGRjaHBpeDl\ -5UVphaDFCUFBGYW5jNzBMcjhOaDZJeHFNQ1hiMQozMG9vWHpWZmZNMVFOd28rL3hzRnBlRkRqUTAxQ0\ -9pdWZocitKREcyc0txb0o0V0JwYVhubWI1YXVrVWUvV1RKCjAxVmRyaEkvSVExd3V4QzNMMnpac3dVU\ -1NTaDk0aXg1M0hpU3pWbkI5UkxmaVhZUUVCcFEyNHVoRTdiYlo0bm0KZTczZC9zenpPTXMzYUt3OWtW\ -a2VLMTVtYWhSVWZjdEdhSVQxTkhGWUNYYXByaWExakdNdVpmSk1pSUtZUzNidQpMbUhZckF6dEptNDZ\ -0aHpjdnN3NHlhMnFoa2xUUlFJREFRQUJBb0lDQVFDaHZaUm85KzZ5aFhFTHZ6U3FXZHcxCkZGOERibG\ -hIMmhVWkNuV0kxMDM5SmdyRkxMczFSU1krSzg1aFZYMk9hV1VTNk44TmNCYzUyL1hrdFltS09HUFQKM\ -WZqMnE2M3pPcDNSSFdGNWVPMXhNeExRN3JZSDhqMGZZTFFTUytKemdwb3ZRVnJLSXkrb21JSSt3aUN6\ -R1laRApGQUM0ODJzL0J5MHdtRjVjdC9JTEdIeVY3ZXNVUlo1Vi9iL0ltQzUwQ1lDUWpQR2xBb3JkeUx\ -1MHp2NjZZUXc2CkQycTg0VHAyVUg3SExEVmhFNytUbDg4Q04xWll0VGtpSkthbkNpMFVmbStPKzJFM0\ -5HM01hajk1aDl2NktqYkoKUlkxeTNDRTVmQmkyUFNLbVVzRjN3SzdhbzJDRks5MTgybmlxL2FaNm5WO\ -Xc3NmVrRjhEOWUvS1pqUE5ZT0xkaApFczBSL2laV3RpbUx2RHdXQWNWNFNnSFFjNXJvNU9yOEFUS1ZK\ -VmlzZGFuWXkvdUhmVXZWN3U5cDVLK2E4SHU2CllabW13ZTh4bnF5M3V2M0VabE9LY20zTnZvWllVMnJ\ -HUUFQQW1sWWQ4WlRsZGxPa1JCSGxxYzllMmJuSnNTQW8KNUhhS0N3aDJsWmZpalVGNXFrMXNQcm1kN3\ -BlMld2VVV3QmVuSjJnS1ZoTE5VVGtHWmtTWGhzNlV3WWRRMjVtRQppQzl6WjhXNkQ2OXBvb0lsTTVXT\ -01ySEs0Rng1ck9vT05kUHQ4NEk1bTI3cnpnbXM4QnJXVUlGLytZZjJ0bGdmClBIR0V4c3ZCK3JRQk52\ -WHU3dXoxcVdFTlJTL2YwR2E3dVF4ZW5sZ2dubHc5M1pNOW1GWXpXb1RpdWFmdnphTnAKWEsrTEVrV2F\ -RYUs1Q0VaNEhmUlhBUUtDQVFFQTUyK240OUxQODlyQkR2bFdsTkxNanJqTDdSb0xyQ3FVZGpQWQpyT1\ -hZS3ZkTkxyS2NTc1hNdkY4YW5HQng1UG5oVDZGY05ic2dzQ3BUUXowMThZYmcrbUUxQno5ZTdFNTJGZ\ -i9NCk9BbWZqSllXUnZueUtiNnB3SGlvOHlXUXlVVk1zZU1CcmpvWk1kNkpPZEZ0K2JITHBWOS9iSkdR\ -a3NTRE04WTkKbWxGQUlUL0gyNTh1K1ZKTWsrT0prU28zZmJQSk5Ja1Q2WVBKVmNaSnZTRGI5QU83WDF\ -lendCOXVRL0FEblZ6YwpSQkJOUVZaTStZS2ZNWFJBdmFuWnlmWFFwaUxCQW8rRVRPSHJCR1dDRUhtSF\ -RCaEZIMTkyamtxNlcrTStvS0R1Cm1xMitMc2hZWTVFc2NpL2hPOVZjK2FCM0hhaGliME03M092MHFNc\ -WZoTncyU1BncHdRS0NBUUVBeDlaR1gxQnQKL3MwdGtNcGV1QWhlWjFqTklnZmFEY0RRTWlmU0k4QjRx\ -WUhiL1hOREQ5NjFQME9zMDdCN2wzNE1iZ3U2QlNwcwpXdSt0Y1hjSFlqQVJUc0Qzd0pSaVRIb0RSQzR\ -YYkxEa2pHRUVCVzRKbFVqZTA1QWZrU0QrdkZSMkJwZStxQlBLCm5yb3Mwd1BWL3RXa3MzY2VFOUlBTV\ -pWWDhQeFA3RVNXbitVZDJEWkZhcVFLb0JybHZXRXhxelpYUEJSVjhoS3QKcFBqWnFkZXFQLzhUZTBtS\ -zh5MEVreXVXOWhFdGZ2Sm5HWXhMNStrSG9xd2hQVk1tODZ5YlZNVHRQYWJTdCtPUwp0WHhJTE9RMWRN\ -QkFabzRxSnNkUUZJcTJnSHA5WFYwa2ZNUms1ajdJT1Q4c2Z6TlpKVkRNK1k5VHVlSGJXSnduCnZsWld\ -VZ2NVZTlBaWhRS0NBUUVBbFJaK2h1ckUvNGdLR2dWUld5bTRrTEJHM2dTTFJHdGhuQXVtSnlzaFoveE\ -wKZ2l1Wk55bll5L2hRQWpDMjdoUnlxb04rRFRid3hjdGVPOUJ3c1poNzBZOVJROHYwOERGVExMVE43O\ -E56UG5OcApBbXY5TGhzZTYxaFBMZU1qTkNVcVZPV3hyWFRMeWk1YkpCM2Z4SnhlWGJmNU5BMUpudUpz\ -eXF1SC82TWJ0cytKCmhkY3p3WFRjMCtBZVBKOS9nOENQZXdKYkMzRFVBQ2R1VlNHWHo4ZWZxcm1xbDd\ -jbnB5ZzBpK2pJRkNpVU8rVEcKVFcxeDg3KzUvUFF2MGtSQ0Z1UUloZ2ZCNkcwWW9vcHBrUWRZdXhKZl\ -pPaHdUUldpbTVMMlF5K294WWZySGVQOQozSlltbGFCMmJiN3kxL1FoQjcvek9VMk1nTEtYdHl4Z09ve\ -EpoQlFwZ1FLQ0FRQkIwSUE4dy9CMkNuMEhRcDhQClhUSTZOelRZRUYzd1NhQkg1SFdBOE5MTWdNaERJ\ -TUxsWnlPcVFrK1pLSGFMM2llWjFxTGRNS3VmQjNESC9idWcKeXRQb2JBVXNsN0lJSGVjVmZWaVpvMml\ -pRXhHUCtEMlB2UUFtRFVGWU90V3FrT2FPSlV2VmJ5ODhOM1NyeW9lZgo5aHpZUGxMWmxFQWNGR055S3\ -FibjJXOENHaU5LSWhXYW1Zd21UclY3T1pkeUcrTi9GZk40Vms1NkZyc1pCTDQ5CmRYU2xGZ045TTBaZ\ -WNleTEvZEpPRE9lSHNuME5VK0gvNFZEUk1hR1NmelpwSkxJOXE4T2FiSWpVM0ttb24wQTcKdzFWeWNU\ -L1FwYlBxRUFVckt5dytvMzV3MlAyaUZ1czZiMlBvUUxFTGFTRVl6K3R6UEw5UTM1ejNRdGdMQytuagp\ -IUmxCQW9JQkFES0Q4NGhrYkphczlIQ00zanNNSU9kb213ZEMvcktxVUxKNHFWZU5QR0xDY2VpNEdocn\ -dlQnNICnNoN0hibFlZSDN2U2Y3RW1iZEVCb2xlWVJsaUx5ZzJDQU1ZOGpNK0lpUEwydk1yM2Y3SzZPT\ -mU0UkVPUFJSZkcKWlJDcTh4a0ZPQlg1SUJUbkVCV3QzdVdyb1NGY2x4RTdUa2I4VkUyVzExTG9ZNlkw\ -TUNPaHdxN21xYXRUVnNrawpTRDNySmkrTFR6a2Y4OEx1bjZZNjdiaFNOTWpKZkFaUXNQc0FTRkJBUTJ\ -rQnE5alRLZGVuaU4yYTJIbm0xNCtrCnJDeU9ZVE14Q2hQbWNpS25pVy9MWnFUL0U1dlNRUGdBVzc0dT\ -VLazJoSjRBajNjRW9NVEwxSytZbStWYWh2U0cKTi8xOFdYQ1JRQkg1d0p2eXJYczBtT29GQlRnTWg4d\ -z0KLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLQo=") - ("ringAccountKey" . "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUpRUUlCQURBTk\ -Jna3Foa2lHOXcwQkFRRUZBQVNDQ1Nzd2dna25BZ0VBQW9JQ0FRRDNCdDRnOUVUdk9EVnYKM3hWV0ZlS\ -1Nnbk5uVEF3S2dYa3IrQ1FhdU8vTGZWM01RenNSNHliL3hoaWhhb1Z2c2FtZ3ZRU1haL0M1R3I5QQpE\ -YlAxbHNHclRCK1pXMC9uMXVEb3hmVWdZRWY3SGtVanJtdVFjUGlFWGlUVkNiY002U0NzdVNrMnRxdE1\ -BNjBNClRacVo2LzAvbkEvblR5TnFNaUJNSmFRN0NUb2xOTTh2Z2tvd0tBRW14OGpJUG9YZEttMnd6MD\ -Z6SnhwU1d4WjUKc0FBTkdSSHU0b2ZXNWJiMERvamtnRzFBYUJ6Nm9uSmdKK1JFSWV1UkpNQWFHYmtzW\ -TQ4Z1Z1b21BVTU0UFNvUApFb3psVGdHd1k0cnhJTGE0V1Y0RVZMVExrR240ZTYrRUYrSjIvRURBUFdN\ -bXREdFpUeURCc2t5bStLaDRUT2xPCjdnN3JlUUhLdDd1R21YU0RnbzVKZ2hOOHNVRFUxL1Z2YmFFcUZ\ -tTDJrZWNGOVlVZmNsUWRGY1ZncmIrMkh5R3AKRVc0b3RkZjlYYzhOMWxrbGk1dFBqRGZuQ1U3OHB6QT\ -BxQmV1SWhZTnF1VjhGSm1NemhXeVFDbE1MUEFYbXFVdwpWYlY0MWduM0NkektuUlVhZXFONXlzOG5KQ\ -mRJNDBleWlYRUlvU0VKcFpyT1Z5ck1icnNCQzltaVFpZUFhSnlBClFvcjFaRGlwMkpZNUFza2phUGQ0\ -NGk0MGNkeFpob1RhNnpzako5UjZScllFYjhWTmZQemFLaElwSXJSd2NpbCsKWjFrbUUwSE1kY21ITXR\ -hbWZhK0l6WUR4dDV6OGVOZys1RGpVVG1MRkdyQUVWRW5hNDcxdllxYnk5UCs1T1cvNQpSdkxtUER2Tj\ -dlUURpTlZENlBYRFk5OTY4bTZaaHdJREFRQUJBb0lDQURjOEkrTCtlNE41OEFqcHV0MmEyeVNqCllxY\ -VFUSWowMW1GTWhOWXMwQUdTTUswQncyMkdleXZwNFl3R1EzdnNIOSsvSkEydXdoYkJzazNpUW9FQmlx\ -Q0EKenZmOWdPcDRFNlk0elV6RitwSmQvRnUwSG4wWHBab0Rhdnp2eFN4djNFeUN3b0puYWZuL1FHeGw\ -xZEhoQUtsKwpmZGZjekRCc3NPZ1Y2cGtBd1MyY2wwOHFOT2g3cVhaQWFkYk1sQ1lWM0owU1hhaVZiNz\ -lHZXNvTzNwUVBMUUZiClNjQjFjT2sxYnNxWkpOU244d0xmMis5QVBEdzMwWEtNNHg5eTdRTE42Q3oxQ\ -WpvcFJLQ0NIS3R1SEc4UmVETTIKcnRTbjJmTnltQ0VqeDZGVTB6MHFldDV3Y01UbU5weEZuYXdEMU5s\ -dFpnZXBsSllwTjVKZXNEUmo2cFlnWXBPKwo5UDU2cEdtZVNTVzVxcHRoWFFLQmFsdy8wWXp2YUlYdHp\ -hTnZyNUJzRFNrWkU3cldzODIvQmFzUE1RckFmOGpLClZFMU9pSzcrVllUVTRFVWVoZ0FZOXdzNjFqYk\ -lSSEpQbW9VQXpkWHcyL0d0Vk9JUTFwUFJnOXNYN0JaMmUyV1YKdmd5aThPUEJxRWtwblBiMkU5Z0d1U\ -25rY01OeWFVbUl0c3pFandadW14dnZrUW5HanlTb3pjY2R3dnNQNnBJagpoN0g5VUNQTHdOM0N5N1lp\ -UmliSlZBWlNjZkF6QzhubXNLODQrTzJUZHBzTXk0SEZDMmM4dlZiclpteTVkWC9qCk1ESnBzS25JWlZ\ -JMmpXSzRpRS82aUdIWVdoY3JvWnYvVEJ0YW1SQUxTWDVOYkhhWTI0bXVRSG5yMldtaytld3EKbHRGbC\ -90bXgyVkpWUitMZ0JCUGhBb0lCQVFENEI0MjQyVTgvbkJ4d2RzelhCdWxBOVFTa0I5Zktud0RlRkV3S\ -wp3Nks0eU14YVdRU04ycjRxRDhLcW93OVZVMzRYdkRWbFh0RUlDaVh3Q0hZdW5IL3g3cXNSdEVzbHJM\ -aWg4UHRPClpDSU8zUml4RmlIQXFlQUh2YXF0NVhXdndaREx6WnV2THRJOTdINkZ1QjYxck5qMnhxdlR\ -IN05pUmp2S0R0WXgKR1VtNURoQ094cm9tR0NkWHRnWHJGaS9WRU1TSmpQMkM3OTNrN1pTNmNZL0Nkc1\ -RqWEsrZ1UzeWM3OC9kN1pYbwpKMGg2WFlSdmhlQW9Bd2dkVTl4MWtYL2ZmY2tZK1hUcFRwV2xXWmtlU\ -ytsejBpQkRnUlJzMm45OFZDeTZDRmRZCldsZXZaZy9SWXZ6dzlKdWFVcXArOHpHbHNXR2xuOEhtZW5X\ -Q1luSHJnNFBxQnRkL0FvSUJBUUQrOXhDL1N5ZGIKVWZxMUJHYy95YktZc3RLb3A1azB0d3M0SlUwTzN\ -aT1U3MlZ5YTdLT2lTemdPSzVnL2QzckhMMXR2dHViTDBWNAo1dEF4a1AzSkVYbmxZZU5IVkpROTc2RH\ -NGS3Ztc0FGL2JJdVBsdFBRT1dyM3g1eW1RU3lCOTBUczV0dFdWMTVQCktYYVNnMTZidDhwNS9MeERkZ\ -ng2c1YzN1o5RDFRd21EQllreVFIcWQ4clljTm9ad1M5ZnI3UTZhN1ZNSDVtT3IKbEF5dzBCYVdZQk9k\ -bjFGd0pVV3NlRlpmTy9vNUVqZk9Hd0xMR1hiOEVmQ1hqdlRYcUNHLzNrT1JvN1NkOWY1eQowVjIxMmt\ -YVVNINHNDbFB0SmwzeHpaMWJxQ2RMVDNITWNLUTk5UGFFVFppQnNXQ1lOcXg1c0Q0RGhoMzdZQ2hKCm\ -hlN3VUM1E0MElINUFvSUJBREN1VXR1b0UweloyQjhld2grbUpKdnlPMEh5cENFSnlrTE1Xd3gxejNkV\ -E9nQzEKbmhZMWk4TjNxbTZSYUk0SHdDVHFkTlI3b3ExZ1NJZnZNVHIrem9IdXBUYnBXeUorM3hJeDJU\ -Rk9wL3lnMnByUApURHFqWE94SUJycnc0WU5vaTRIa3poeTVKTnl3a1RpdnBaOWsySVMvQTdTQmNWVGx\ -raENianVDK0pPRWthSTJOClpiWGFZY1p1WElVQ3FzcTM2c3RRbCtWZUxRQWt2VjlHc0wrclRnT09Dbz\ -UrTkdRZEVZQnVoRkMzZlJzL1JhSVoKOWFBRTBFL3BTTWp1a05tTnQ2Mm1NSk1tTUdydXhnWFRRblBRR\ -npNSW43aXB2Z0hxQjRsUDM4emdsbnMvbmZVcgo1NWRuZXk3ejhMRFFETHVIc0RHd3hINzNKQjgrTVR2\ -WGFVbkNwQU1DZ2dFQVNBSGxBL0dvdXR6TFRvWmcxcDRUClI1YnhjZHBycFh5d3VYbW5hclJmY3VldG9\ -nUVNtTGpiS0xRNVk0RXZSTENJTzA5MDNENGNnOG5FTU10L01XTXoKSnZwZll3emJGU2J4THR1anRQSX\ -VhaHR3eXV2UkJIVEM1aG5FL3h0WEE1bWZLTDBHWXpzbmtubm1WL2lzSnBSZwpwZFVnSW5sWEJodkRyR\ -FlreUsvWEp0N1FZWlhlUzI5NXlUd0krZndoamlzVVBlTWEyUmRUUE9rQ01JbUVaNUhZCjJHSmZjS25H\ -SkxDVHpDKzNPcGtQazdFRE4vTUlMS2F3YVUxaGp1cVlKWVVUVmpXQzFEM2VUL1ViWHptM0VQNHMKVEN\ -uYWpCYVMzN0N2YVd4ek5JektXZS9TSXdGbEFmYWNSTHlneUR4Z3Q3bHp1akVObEtvU2xya3h3ckpEND\ -Z2WAptUUtDQVFBcTVQWWxSQjgvNnFiWWt1OTA0NUZRdXk2QWtlYXBaMW0raS9SQzZtbFRvUXB6NDlPU\ -Gs4ZGx5YjVtCndvSVhpaEo2V05jN1RsWlRYMnpQTTRBS0M5VFNBUWJrWDg5bjEyU2VDSUlHbXVINnk0\ -TjZiY2lxZjVVcSsvc0IKcHJKeFRNYlRSUFpqS0VVd1N0SFg1MUQ1bi9sQnZERGY3Y2VEZytsYlE0RjR\ -KMTlPd09oZ1lGcjFheGQvNXd2VgpURjNoVlQwbFZGN2RyRC9iMHZOcmxnbUNjbEk4UDg1a2dkRUhZbG\ -ZtTFoxeXJIMkNXVy9SS0lsWk9ZdFVuNFNpCkp5a2VlNDROWElXU3ovalRBdFRta3VQTzRvUjF5d3dRc\ -jdhUTF5a3hRVm9rVm5vY2xqU0tyQlk4R294a0I0eDIKUDNrb3F1UnkvcUd3QzBnN1o4ZjBTQjNQZVZt\ -eQotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0tCg==") - ("ringAccountCert" . "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZXakNDQTBLZ\ -0F3SUJBZ0lJRm1tNmZuaWRndEl3RFFZSktvWklodmNOQVFFTUJRQXdUREVRTUE0R0ExVUUKQXhNSFNt\ -RnRhU0JEUVRFNE1EWUdDZ21TSm9tVDhpeGtBUUVUS0RjNVpqSTJaVFZpWTJNeU9EWXlPREppT0dFMwp\ -PRFF6TUdOak1EWXpNakV4T1RFNFkyWm1PVGd3SGhjTk1qRXdOREUyTVRjek1qRXdXaGNOTXpFd05ERT\ -BNVGN6Ck1qRXdXakJKTVEwd0N3WURWUVFERXdSS1lXMXBNVGd3TmdZS0NaSW1pWlB5TEdRQkFSTW9aa\ -k16TkRWbU1qYzMKTldSa1ptVXdOMkUwWWpCa09UVmtZV1ZoTVRFeFpERTFabUpqTVRFNU9UQ0NBaUl3\ -RFFZSktvWklodmNOQVFFQgpCUUFEZ2dJUEFEQ0NBZ29DZ2dJQkFQY0czaUQwUk84NE5XL2ZGVllWNHB\ -LQ2MyZE1EQXFCZVN2NEpCcTQ3OHQ5ClhjeERPeEhqSnYvR0dLRnFoVyt4cWFDOUJKZG44TGthdjBBTn\ -MvV1d3YXRNSDVsYlQrZlc0T2pGOVNCZ1Ivc2UKUlNPdWE1QncrSVJlSk5VSnR3enBJS3k1S1RhMnEwd\ -0RyUXhObXBuci9UK2NEK2RQSTJveUlFd2xwRHNKT2lVMAp6eStDU2pBb0FTYkh5TWcraGQwcWJiRFBU\ -ck1uR2xKYkZubXdBQTBaRWU3aWg5Ymx0dlFPaU9TQWJVQm9IUHFpCmNtQW41RVFoNjVFa3dCb1p1U3h\ -qanlCVzZpWUJUbmc5S2c4U2pPVk9BYkJqaXZFZ3RyaFpYZ1JVdE11UWFmaDcKcjRRWDRuYjhRTUE5WX\ -lhME8xbFBJTUd5VEtiNHFIaE02VTd1RHV0NUFjcTN1NGFaZElPQ2prbUNFM3l4UU5UWAo5Vzl0b1NvV\ -1l2YVI1d1gxaFI5eVZCMFZ4V0N0djdZZklha1JiaWkxMS8xZHp3M1dXU1dMbTArTU4rY0pUdnluCk1E\ -U29GNjRpRmcycTVYd1VtWXpPRmJKQUtVd3M4QmVhcFRCVnRYaldDZmNKM01xZEZScDZvM25Lenlja0Y\ -wamoKUjdLSmNRaWhJUW1sbXM1WEtzeHV1d0VMMmFKQ0o0Qm9uSUJDaXZWa09LbllsamtDeVNObzkzam\ -lMalJ4M0ZtRwpoTnJyT3lNbjFIcEd0Z1J2eFUxOC9Ob3FFaWtpdEhCeUtYNW5XU1lUUWN4MXlZY3kxc\ -Vo5cjRqTmdQRzNuUHg0CjJEN2tPTlJPWXNVYXNBUlVTZHJqdlc5aXB2TDAvN2s1Yi9sRzh1WThPODN0\ -NUFPSTFVUG85Y05qMzNyeWJwbUgKQWdNQkFBR2pRekJCTUIwR0ExVWREZ1FXQkJUek5GOG5kZDMrQjZ\ -TdzJWMnVvUkhSWDd3Um1UQVBCZ05WSFJNQgpBZjhFQlRBREFRSC9NQThHQTFVZER3RUIvd1FGQXdNSE\ -JnQXdEUVlKS29aSWh2Y05BUUVNQlFBRGdnSUJBRDMrCjlscFluMjZyeG5pekY2UkNvZFFFVmgvOVF2R\ -Wp1V3dHUWZqa3gxb3VlVjdDMzUyWnpIT2hWU3VGcG43TUxkVFcKamI2dWhMRkpoMWtlTDlYZ0pHalMy\ -V0UwdDlJUGp2UWx5UHIwRWJoWGRJNDJMYUR0NDk0dWQ2MEE0bWg0bW1zbwovcjY2NERKOWMwUjZBOUd\ -QZUJjWi9zekhMQjJ4VmM5M3hYNjVjcmNoVTFLMDBVK2ZZUWtHS0xidFFXeUZzdnlKCmRHdFRxamVlYV\ -VNeGt5dFFWNWFxVHJ1SG4vV3U2RWRZejYrZ0ViQXJHUURRK1J3QmMxMDNGcWRIU0xReWdEWHoKd0pNV\ -GhDREdBVmR6V2NCemJYL1JIZms5bTZzK01HblNJUFBrNG9FOUdFbGdQc2JUZ2FrTzc3aElUdzRxZXJi\ -TAp2M0tiaTVOeVZZVFNVa3hDb0VRSkIrWlZJT3BVNFhRdVAvNkZkWldBRk1LU0Jkd1JLcnRmT1hZT1E\ -xcVBvYU1uCjZPR1VGMU0rYWZ0dTNCMkNhZ2ZMaE5hbVBoSjdxWTNSMzJhK2VRVllvYWlESXZKMElIUH\ -FnQ0NuWXlIaVBHTjQKS3VvaE9IZFpXTkxOUXdIQTg3SDV5NEwrSGZISmdFZFppdWYrbTNMa3JJcnN5M\ -WFoUjgyaEpZVkhreHVyZVRDcQpIR2NJaUIvTHpkSG91WFVrWGNrNjRvWXVRQnM3ZE9KVEx6bGlibU8x\ -elRzVWRoc0d4dE9zc2lYWkFjUURmZHBnCjNHdFJ3UkRwd0ZtL2k5TC9UWjMzMjBwY0VZd21aN1dBSzJ\ -Ra3Z6eWZlelRGeXdLWmh3c0RQRkwycllzLzhXdWsKRk1sek5BVmRMTytTS1RxeGlkM0l1elFaQ0FwU0\ -5ybTJMY2ZVN2grWAotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tQkVHSU4gQ0VSVElGSUNBV\ -EUtLS0tLQpNSUlGWFRDQ0EwV2dBd0lCQWdJSXV0T3MxUXBsSVRvd0RRWUpLb1pJaHZjTkFRRU1CUUF3\ -VERFUU1BNEdBMVVFCkF4TUhTbUZ0YVNCRFFURTRNRFlHQ2dtU0pvbVQ4aXhrQVFFVEtEYzVaakkyWlR\ -WaVkyTXlPRFl5T0RKaU9HRTMKT0RRek1HTmpNRFl6TWpFeE9URTRZMlptT1Rnd0hoY05NakV3TkRFMk\ -1UY3pNakExV2hjTk16RXdOREUwTVRjegpNakExV2pCTU1SQXdEZ1lEVlFRREV3ZEtZVzFwSUVOQk1UZ\ -3dOZ1lLQ1pJbWlaUHlMR1FCQVJNb056bG1NalpsCk5XSmpZekk0TmpJNE1tSTRZVGM0TkRNd1kyTXdO\ -ak15TVRFNU1UaGpabVk1T0RDQ0FpSXdEUVlKS29aSWh2Y04KQVFFQkJRQURnZ0lQQURDQ0Fnb0NnZ0l\ -CQUxTcGduY2tYODd3OE5vVzdRWE5DbWtHSHphVnN1NE9UbjFSSUhqVQpGR0w5MjNvVlk5RHVoZjFtVn\ -llamgremVCOCszaTR4b1E5MU04WDdTVnk4SHZxejQ4c0I2djAxMk5RTGpwN08xCkd4dmc1a0REeDRqb\ -mVyUWd6d0JzVEFuaC8xNy9SOEFDZkhINzYzUjhMRjY1clJPSStOT21KNUV2cU9rRWJJOXYKb0UySko1\ -czBVSHZZM1hTQ2pid01ROXRkM2c0SG9LNmRYSU44a2d6ejg0Yi83YkF5TnZ4a09rNnVSei9CSjhhZQo\ -zWjZudElza2tmVHhIZFpBbGtpVW5DU21Bb3VwQ01TbGRLS3RMRzNja2FhN0FDMDRxS29Rekc0WTN2Tl\ -RCOUlmCmdvYThOazlKMThoaFN2REVuR0haaURLR1dkQVA5RW5GRHZERnRFRFZyVVZSWUN5WHdyejMya\ -29Na3dYODVUMTgKc1F2TUZSdWRsa1BoOGxmbnFEWTFpZEd1R2IvSGlnU1ZqalJKZlVTbS9lZ3hycStS\ -TnpJWGFBOWNuOXAxQkdFbwpZU0YxeUdtTEgzSkJscUhVRTg4VnFkenZRdXZ3Mkhvakdvd0pkdlhmU2l\ -oZk5WOTh6VkEzQ2o3L0d3V2w0VU9OCkRUVUk2SzUrR3Y0a01iYXdxcWduaFlHbHBlZVp2bHE2UlI3OV\ -pNblRWVjJ1RWo4aERYQzdFTGN2Yk5tekJSSkoKS0gzaUxIbmNlSkxOV2NIMUV0K0pkaEFRR2xEYmk2R\ -VR0dHRuaWVaN3ZkMyt6UE00eXpkb3JEMlJXUjRyWG1acQpGRlI5eTBab2hQVTBjVmdKZHFtdUpyV01Z\ -eTVsOGt5SWdwaExkdTR1WWRpc0RPMG1ianEySE55K3pEakpyYXFHClNWTkZBZ01CQUFHalF6QkJNQjB\ -HQTFVZERnUVdCQlI1OG01YnpDaGlncmluaERETUJqSVJrWXovbURBUEJnTlYKSFJNQkFmOEVCVEFEQV\ -FIL01BOEdBMVVkRHdFQi93UUZBd01IQmdBd0RRWUpLb1pJaHZjTkFRRU1CUUFEZ2dJQgpBRWFDblluY\ -m9yWmRhWWZRUmxSb0dtSE94T1g5VFdNMXY0dWEweCtFcG11RDVWRDVlWEY1QVZkMlZadEdaeHYvCkd2\ -VnFBU2l0UTk3ampKV2p2bURWTUZtb3hQSmRYWDFkYTd5cmJYeFRmYm1mM1pac1RpdmZVdWQxYThxdUN\ -3dTUKTnBrdHFjV0JyMHRnNzFhOXlidHJOdm1GczZGRE1WMXkrY2JxYlp1UWlDWnc0WmZhekFaeTRQRG\ -NocE9SNjRCSwpBWFZIT05HcWFoV2hwcDkwd2E0TFEwUTE1U3FDR25kYVI4SHg5MHJOeEdkRjM1T1BLU\ -jd1TDdrWDU4ZGxaWStDClJJK1pKMndYMzJUZzgrc1RtTmNaUTliWDdvS0t3R3E5Uk94SjZJSUhOSnN2\ -bXhaSnlPcmE2N21hNTd3OWxiQnUKSzJlQ3cwZjRDeHdLNU1LNStkbWx6R3dhZmJlMG00TTBvVjlhWUx\ -ZTzAwR3Iza05heW9PdjRRVGtEM2pCMzVSMQpDMGJnQmk2eU1sTVJ2akZ5eEZkOFJpL003VG1jcXNObT\ -B5aklqbkZaenVtMFZTR1NLMXlRU3Flak40S0Y2R0JMCllpZ2JpM3c4WG5HYm9pZGdBUE9ncVVJeTJ1d\ -EU1MllzVXFsVHVncXhtM2xDOUhzaDM2UFJLNURDUG93eHVUNlgKcXo1M1ZiN2h6TkxLelpiRlJzbUdF\ -OFY2cWM2bXZTbUFXa25nL3QwaStXVmdGVkZuZFQrQ0oyNTJsa0ZacGljdAp6ekdETW44VUNDRUp4TDR\ -KTklTM2lLOUhlRys2MlZuay9QOEM3YVpLSXpVdjFud25rcVdUUUFYWDBKckJGdDdICjI5ZDk1RElmRT\ -RuT0FyS0JFNHc2Z1R4SU1uZzVzWi9ZbDFjcG5wUHlsR3VICi0tLS0tRU5EIENFUlRJRklDQVRFLS0tL\ -S0K") - ("ethKey" . "fN8cOT1lYNziaW0+pjBIgZ8r6+zMMhHsukkWBNPDsFo=") - ("TURN.username" . "ring") - ("TURN.server" . "turn.jami.net") - ("TURN.realm" . "ring") - ("TURN.password" . "ring") - ("TURN.enable" . "true") - ("TLS.verifyServer" . "true") - ("TLS.verifyClient" . "true") - ("TLS.serverName" . "") - ("TLS.requireClientCertificate" . "true") - ("TLS.privateKeyFile" . "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUpRd0lCQU\ -RBTkJna3Foa2lHOXcwQkFRRUZBQVNDQ1Mwd2dna3BBZ0VBQW9JQ0FRQzM5b1Z0cXNtUGdaSUgKcHpTV\ -GtlT3BlWC9CSEx2KzFTYnJPSFpVRHEwNFZCUU5BNmJmSFNSWTJpbHE1WEVheXNVSmwzQmsvM0txZEhS\ -cQpEV01wQ1dpcE1Vc2FwSGxJR0tSWHEwbXhQZ29WODZSUVBub1dCRTdhWVVEZTlJZXlxMmllZXpDK1l\ -YSnBtWTljCk5tblpaMFlHOHJGMEVpWFA0SHpVWGphZklTKzdKTTJ5ZTZyUlpINXBvdHBQNmV4NXhqVU\ -VuNEFFdWhuWGJ6U0EKNnZMaC9wYnhIUVBHMVlmNHFhcU9TY1lXV0ZWaE5ySVBCVFJNZ2RaTWRGRTh0W\ -VFyaUNkNTV4dUhrYncrQmY1VQpmVENqN25tVEcybDJNbGcrSXBHVkFXUFRWNkl0NFNiS3VadW5MZmRD\ -S2FrSU03SnA3V2dJWjZNRHdkaFFQWjJNClJPQzN0b0c1ajh4aFZOU0Q5QlZiOGZBcnRmeHdldlliQk1\ -yOEdRV2JQYlRKYk9tZHZnUGRGcXNTb1F4QnRUcWQKSG1TMG53RjJ4UGZrdm1Cd3BSWWhHNHpuZlVtMl\ -BQcDBIK1g5Sk96d0R1QWI5K0c0cWZtK3Z6bDd1N0o3anJ0TQpHQlJlSlREdGFNd2RHa0lmcUNRZGREZ\ -HFpM1hzVHVVOVNubVN4NkdxT2V6ZU04bk55dmtZWjR2Uk9vODFzU1JjCjQ4L3pZZjJ1OFBJUm4vVy9x\ -Zk9hbjVIbkcxeFNBUDZkcHAzQ1Y0YTgwUFduZyszcXQ4NW5Ha2k2Nk00NUlnRjgKNHZwRGUyM2YyZnp\ -rTk5OOTk1VTBvUStKVGg5eG1HaWJmenlCaXk5d3RUVHVYWDB3Y3gwTm9wNjdYaFFDVVBGSApybVd6Wj\ -V5bXhEdkRhcUN1U3NwcG0vTkRVRWQyc1FJREFRQUJBb0lDQUYyWXo4bzhXdERvMjZPSkx2Ym1BeTcyC\ -jRra2VsWWZTYXpyQ1AzSUZCWnpqS2xCMHl6STVZWVRUZXI4b2ZhTmtCMXdaOE5WeUlxVVhHeVBhSzls\ -MU1BcmwKU1pFRW5iQlEyVXpCbVh6VVU4MVhhUUpxeHpMc2ZqSWR5U09teG1QaFVobFFGRHpJMTYxaXM\ -4MzI0V1A3WjJXaApsU2U1RkFQdjg1TVpYREVhY1c2R0N5SUVTYVMvdkpHQ2loQ2VzL0pCSmpoejdtNT\ -VRU3liSjl0dnJxUE5KSDhJCmhDTm1BUWhEU3NPYVJXNlpBdGV6UEdUb0FQUHNHMXhLMGdwUlVDM1YyR\ -C90bGRRa0VlSjhxaW5TaUN6ZjZIc3cKTnpncjVUbTMzTm96R3Rjc2Z4ZFl0cVB1UzRPRG40bktLSFpE\ -MTBLTng2Qi9HakdQTHIra21jUUVSMUV4U2s2QwpSemtzSTRzRml6VnVLYTFNK2ZqaVNVc3RTdHV1cTJ\ -QQTJJNzFNTTZlMHJmU3ZKM3VESEJYOWY5T1RFaGxZUFBkCnB1VWM2ek1pdEJKRTg2SFo1M08wR0MzdG\ -p1M1BnSnhmWnJyMHBCU20rSzQ3ajVTUFNkaytiVGh3UDZDZDhzWGUKNmljS3YvMXI0SjVqd1pFTmRna\ -2hKeHVuMkE1WVA0c0NBU21JSFFXWmU1cGdFQ0ljUXBHZjNBVllVdVpGeXZ6cApLL3VBRTc0L3NMK0VU\ -UnhDUlhkN0ZJM2cwZFVySTA2WUFIV1FkY1JpdWpIUU12ZXB3V2RrdUdkaE9wV3VRTy9oCjc0MGgvRlZ\ -0dFdOMjc3ajhhc3ptSDA3T2lRWFIwTU1mN2FEaldMaElMYkd2YkV4TzN0TnRBYXZ6cGxmMUNSNk8KUm\ -1nNDdhVTZpR3lON1MwTE85RUJBb0lCQVFEWWMwelh2UmJHTWxkVVZnOFFTT1ZHKzV4NXVocTVyQ2xtQ\ -lp2ZApvYitWS1hkMXBONThraWkxTFBiYUJmZG9JcWVUamdOK0E0YnVpT3RGNjdCWUdISkp0WTk4ZWR5\ -RkpZREtCSzIyCmthRml2eWVuV01UWG5jaHMvUmNXMnVGS1M3cFAyNDRXYkF2clQxZGxmTTF3L1JzbTZ\ -QVjF1dndPSmNJcEdLSzgKWWgyN2hiaThUbEF6bnJCSS9TbGZwNlpxV3Y0MFRLanNNdmVGVFNWRU5yd1\ -F2MVl4TjcxbjA1UWRVUkIrT0lJSQpPenpNMWpNcm43cjNmS3RKTEx0RllEdUhJZzJDL2E0cUZORmpjM\ -296V1laQkhlcDkrWlEyOTgxdy96VTJIZ0oxCkxiajZjMy9qQU5EakI3MnRkcWMxVUkvMWhwVUh2WDJz\ -emY2czM0L3VSY3RlaFgxWEFvSUJBUURaazVaT01RdVMKNzlVQkhCeTRFbFR2cVkvUUxjMUd1aG9LVXE\ -0dzhONEtJZlpMSDk5TTcwek9JWjNoaEdaYWJDREZHN3RqSDl4Vwo3UnhXVWt5cFRDK1h5TTc1YjF0YW\ -9jRVVyZ1hnUnE4R1JFRXg3OUtwWUE3ei95TVc1OHJCTWhHTWlGZ1JTM00zCmNQSzg2dHBvaTFDN2crd\ -lUxcjJwcDEzN0habzZiUHpKTFRPNXA1OG1tTFRPQVpKd1VSZ3pzZWJnc1dsWXZlR0wKZWNBZ3lYbUtt\ -WmVSeGRNUFlCK0NmMno1TWZ5Ujk4MHRVSDJPaitGQjlJNExzRTExREphZ2wwS1lHMWxKRTBFagpodEV\ -1cTFOTG9DcVkzYXVKYjhtRUpqNlA2Ylc2SytCZTBlUi9CS1MraGxxb3VOYWEySnlhSDc4Qis2U1VZWE\ -RuCnd6MityWUhVZEI4M0FvSUJBRFlXc2ZnaloyS0Z4KzdxUm45aVIvRXlCUXNpSjNXSWdSdmVnUEdrY\ -nRTZWRSeXYKNDIwcnRRSjVSd0o2aFRXL216S3pSVW9qSlgvTU5VYld1ODEzNW05bThJRkJqb3F6TVhq\ -S0xJSzM1NlZlY1ZGUApUSGs1RTVHd3VTbGI3dnA2N0FieXJaSUswL3VzYXdHUWEySTF6YWd1aE5BenR\ -yTHVXcE9jZFdZditwQVd2WEJJCi9aKzRvd0xLU0tGL3FvVmZVYkRPQzFSaTlCbWFpcHArTndiVVdYeV\ -pHanFzMDVGejVYUTFPTUZIMUV5M3BqZmIKaFlRODRpeTZBZDQzU3dqY3lKV1lRUUtCQzBZWDRFeWVyW\ -Dd1TTkvaEUxbWRHUGlJdmNwVk8zWCt3Ly9LQndZNQorUGtTd1NKc3lTSDRqTkRsSGE2K2VuNUpSNy81\ -YWVVNENiY0lFcWNDZ2dFQkFLelMvNnhDWnZnalN5V2poK2hxCm4wN3plQW1icUJmTEVZNHJtTFBGVUF\ -ucWFqSElNbDV4SXFnRnFkd05pQ1BCQ2RLbnNaUU9KYjVpZjRUTndKa2wKckJRNzdMUFRVVlJQY2dnVU\ -p4Uzc4S0Rnckl5Vys5V1FPTEIxZEJEb3MzUDhhbFlmb3h5eHV1Wko4SFpCY3BWaQpQQkdHdTFnSDd3V\ -0lyUzBmbVhkWlJQNGp5cGRvM3hFUWNXWEZkK1dCZE9EektmcEcwZkFzZTdDSFdDWnpBdmttCkFYQklH\ -OXQxdGZHNWQvMEZTS05GbTVPb0FPT3h3L0xZNTgrL0RmZXd0U0VBcFdRZkxTL1BmSWxVdUdvQ3FwcEM\ -Kc2pOVXVNSGxxc011Z2JsY29mNHNoZityWjMzQldYOEJSNWdIb21mRE1ibDNDQWp5TXd1dHpybzVxcD\ -BBUTBWWApxOGNDZ2dFQkFLakFXVVRYY1F2TE8yYkxOZmJBTUlSVXk1T2lTZmJCbDNYRThSZnNzaUt2V\ -VBnTFcwSlV1V3FLCjdGdUFxTlJPRHhrS0pMSTdyQlo2YVNqNitFWHpUMnJwY2dFWktnSjFOUEFRdFNs\ -UjNJUkcyU3JJdjBFK3UzbkUKK1laa3pOa2Q0MUJqTkRjRm1HV21lZk5ROUJmaVIrZlZFSkZmcE5oSkl\ -mNUloSWU0RUtZUE5VUXNua0tSVTlxUApzWi9idXBXc2w4bWVFcko3bllJQ05ucHpnSHRpNXdSMlliVF\ -VXT01odmRFUldxMnhTV3BBYmtNMElhZDBUc05kCmUrYVRQVmJOMXFibFZLMm1qUTl2YS9JSkVuSE51V\ -E9TREtJeUpvcVArQkxiRTVjQU5acXQ2OFFadWdOc2RxNHkKV2FoeStydU5LS1F3Mk5MYzQzZUtsNmxv\ -bXdtRlFZOD0KLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLQo=") - ("TLS.password" . "") - ("TLS.negotiationTimeoutSec" . "-1") - ("TLS.method" . "Automatic") - ("TLS.ciphers" . "") - ("TLS.certificateFile" . "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZHVENDQ\ -XdHZ0F3SUJBZ0lJU1pUdlZPQnh3akF3RFFZSktvWklodmNOQVFFTUJRQXdTVEVOTUFzR0ExVUUKQXhN\ -RVNtRnRhVEU0TURZR0NnbVNKb21UOGl4a0FRRVRLR1l6TXpRMVpqSTNOelZrWkdabE1EZGhOR0l3WkR\ -rMQpaR0ZsWVRFeE1XUXhOV1ppWXpFeE9Ua3dIaGNOTWpFd05ERTJNVGN6TWpFd1doY05NekV3TkRFME\ -1UY3pNakV3CldqQlFNUlF3RWdZRFZRUURFd3RLWVcxcElHUmxkbWxqWlRFNE1EWUdDZ21TSm9tVDhpe\ -GtBUUVUS0dFM09XTmwKTURVNE16VmhPRFV5WlRsbU5qWmxOelF3TURjeU5EUXdPVFZpTVdaa1kyVXdO\ -bVV3Z2dJaU1BMEdDU3FHU0liMwpEUUVCQVFVQUE0SUNEd0F3Z2dJS0FvSUNBUUMzOW9WdHFzbVBnWkl\ -IcHpTVGtlT3BlWC9CSEx2KzFTYnJPSFpVCkRxMDRWQlFOQTZiZkhTUlkyaWxxNVhFYXlzVUpsM0JrLz\ -NLcWRIUnFEV01wQ1dpcE1Vc2FwSGxJR0tSWHEwbXgKUGdvVjg2UlFQbm9XQkU3YVlVRGU5SWV5cTJpZ\ -WV6QytZWEpwbVk5Y05tblpaMFlHOHJGMEVpWFA0SHpVWGphZgpJUys3Sk0yeWU2clJaSDVwb3RwUDZl\ -eDV4alVFbjRBRXVoblhielNBNnZMaC9wYnhIUVBHMVlmNHFhcU9TY1lXCldGVmhOcklQQlRSTWdkWk1\ -kRkU4dFlRcmlDZDU1eHVIa2J3K0JmNVVmVENqN25tVEcybDJNbGcrSXBHVkFXUFQKVjZJdDRTYkt1Wn\ -VuTGZkQ0tha0lNN0pwN1dnSVo2TUR3ZGhRUFoyTVJPQzN0b0c1ajh4aFZOU0Q5QlZiOGZBcgp0Znh3Z\ -XZZYkJNcjhHUVdiUGJUSmJPbWR2Z1BkRnFzU29ReEJ0VHFkSG1TMG53RjJ4UGZrdm1Cd3BSWWhHNHpu\ -CmZVbTJQUHAwSCtYOUpPendEdUFiOStHNHFmbSt2emw3dTdKN2pydE1HQlJlSlREdGFNd2RHa0lmcUN\ -RZGREZHEKaTNYc1R1VTlTbm1TeDZHcU9lemVNOG5OeXZrWVo0dlJPbzgxc1NSYzQ4L3pZZjJ1OFBJUm\ -4vVy9xZk9hbjVIbgpHMXhTQVA2ZHBwM0NWNGE4MFBXbmcrM3F0ODVuR2tpNjZNNDVJZ0Y4NHZwRGUyM\ -2YyZnprTk5OOTk1VTBvUStKClRoOXhtR2liZnp5Qml5OXd0VFR1WFgwd2N4ME5vcDY3WGhRQ1VQRkhy\ -bVd6WjV5bXhEdkRhcUN1U3NwcG0vTkQKVUVkMnNRSURBUUFCTUEwR0NTcUdTSWIzRFFFQkRBVUFBNEl\ -DQVFCZmRCc0p4RVVWeFRBeG5vNll1bEFoR1ZvUAplWG8xMHIwOE5DcDZRZlJxeGJlTkZ5aXEvKzEwSE\ -NpL1RoZk41OVdLNlpudFF4NlFNZUxEUVZTb0NjNzlaaWQ4ClE2RUdsWkp1c2RTTmg0VjVteXRCQVZHZ\ -2J2aXJFWU1Wcm5jWWg3bHR2LzVuSGRsbyt2WXV3Vzh0aEhHTk1TUkIKQmhJN2xydmpqY3NkbVl6L1Bp\ -NFNZdkg3c3RaVWpRYmUzNzh3UE90b3lBMTNybEtQUTF4QjJFbUxISDYya21WTQowa25wL1hQQ2o2alR\ -zakpFWko1NzZNMmloYy9DTzkvREVlWWZ4RFlJb004NEF5dTc0UU9UUVN1cnVHRjF5T1RKCmlxRkltTH\ -hMcWRxNk1Zdmx1QjFmTzlKaU9QaS84UEJFTTVpeGYzajlGOFVMQTZUTU1NSklFR1ROeUNzOWpzQloKR\ -UNiWVgweTY4WWtGYnYzQmNWaGk2K1VVWVhBVUd0akhwQ3Z2VThYaHowL0RVZFVaTkpqejRJeWl1ZE5N\ -dnFjQgppSEJDdkxFS1B4VFd1ZlFZaGM5ZkVXTm1valc1WSsvalBVeDcrQWxPM1RXZm1OclBpWDJuSEd\ -5UG5tYjZIR2hWCmp3UU0wT1h4cGxnZ2dQMWpVZ0VYWWRucStjYzdRMy94RFIvdi9Fd244T1d4OVB3eV\ -Y0WFZwZXY5QnpmeWwyQTMKdFhWVkFKMzJtcTlPQmVadXczdWlWU2E2TWdCVG80eXcrYnBjU2VIU2xXO\ -XNRc1NYY3dUNjhvOUdhR2hMNjdXMApwQWx4R2VxSWRtbTZ0MkxIUCtQQnBtL3BweTMvTmh1NFIwMTNv\ -Znp5V2Y0bEcrVnpWVGE0eXVqU1J3UlluYmZyCjlSWER0L0I5VEg5TnNsamdBQT09Ci0tLS0tRU5EIEN\ -FUlRJRklDQVRFLS0tLS0KLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZXakNDQTBLZ0F3SU\ -JBZ0lJRm1tNmZuaWRndEl3RFFZSktvWklodmNOQVFFTUJRQXdUREVRTUE0R0ExVUUKQXhNSFNtRnRhU\ -0JEUVRFNE1EWUdDZ21TSm9tVDhpeGtBUUVUS0RjNVpqSTJaVFZpWTJNeU9EWXlPREppT0dFMwpPRFF6\ -TUdOak1EWXpNakV4T1RFNFkyWm1PVGd3SGhjTk1qRXdOREUyTVRjek1qRXdXaGNOTXpFd05ERTBNVGN\ -6Ck1qRXdXakJKTVEwd0N3WURWUVFERXdSS1lXMXBNVGd3TmdZS0NaSW1pWlB5TEdRQkFSTW9aak16Tk\ -RWbU1qYzMKTldSa1ptVXdOMkUwWWpCa09UVmtZV1ZoTVRFeFpERTFabUpqTVRFNU9UQ0NBaUl3RFFZS\ -ktvWklodmNOQVFFQgpCUUFEZ2dJUEFEQ0NBZ29DZ2dJQkFQY0czaUQwUk84NE5XL2ZGVllWNHBLQ2My\ -ZE1EQXFCZVN2NEpCcTQ3OHQ5ClhjeERPeEhqSnYvR0dLRnFoVyt4cWFDOUJKZG44TGthdjBBTnMvV1d\ -3YXRNSDVsYlQrZlc0T2pGOVNCZ1Ivc2UKUlNPdWE1QncrSVJlSk5VSnR3enBJS3k1S1RhMnEwd0RyUX\ -hObXBuci9UK2NEK2RQSTJveUlFd2xwRHNKT2lVMAp6eStDU2pBb0FTYkh5TWcraGQwcWJiRFBUck1uR\ -2xKYkZubXdBQTBaRWU3aWg5Ymx0dlFPaU9TQWJVQm9IUHFpCmNtQW41RVFoNjVFa3dCb1p1U3hqanlC\ -VzZpWUJUbmc5S2c4U2pPVk9BYkJqaXZFZ3RyaFpYZ1JVdE11UWFmaDcKcjRRWDRuYjhRTUE5WXlhME8\ -xbFBJTUd5VEtiNHFIaE02VTd1RHV0NUFjcTN1NGFaZElPQ2prbUNFM3l4UU5UWAo5Vzl0b1NvV1l2YV\ -I1d1gxaFI5eVZCMFZ4V0N0djdZZklha1JiaWkxMS8xZHp3M1dXU1dMbTArTU4rY0pUdnluCk1EU29GN\ -jRpRmcycTVYd1VtWXpPRmJKQUtVd3M4QmVhcFRCVnRYaldDZmNKM01xZEZScDZvM25Lenlja0YwamoK\ -UjdLSmNRaWhJUW1sbXM1WEtzeHV1d0VMMmFKQ0o0Qm9uSUJDaXZWa09LbllsamtDeVNObzkzamlMalJ\ -4M0ZtRwpoTnJyT3lNbjFIcEd0Z1J2eFUxOC9Ob3FFaWtpdEhCeUtYNW5XU1lUUWN4MXlZY3kxcVo5cj\ -RqTmdQRzNuUHg0CjJEN2tPTlJPWXNVYXNBUlVTZHJqdlc5aXB2TDAvN2s1Yi9sRzh1WThPODN0NUFPS\ -TFVUG85Y05qMzNyeWJwbUgKQWdNQkFBR2pRekJCTUIwR0ExVWREZ1FXQkJUek5GOG5kZDMrQjZTdzJW\ -MnVvUkhSWDd3Um1UQVBCZ05WSFJNQgpBZjhFQlRBREFRSC9NQThHQTFVZER3RUIvd1FGQXdNSEJnQXd\ -EUVlKS29aSWh2Y05BUUVNQlFBRGdnSUJBRDMrCjlscFluMjZyeG5pekY2UkNvZFFFVmgvOVF2RWp1V3\ -dHUWZqa3gxb3VlVjdDMzUyWnpIT2hWU3VGcG43TUxkVFcKamI2dWhMRkpoMWtlTDlYZ0pHalMyV0Uwd\ -DlJUGp2UWx5UHIwRWJoWGRJNDJMYUR0NDk0dWQ2MEE0bWg0bW1zbwovcjY2NERKOWMwUjZBOUdQZUJj\ -Wi9zekhMQjJ4VmM5M3hYNjVjcmNoVTFLMDBVK2ZZUWtHS0xidFFXeUZzdnlKCmRHdFRxamVlYVVNeGt\ -5dFFWNWFxVHJ1SG4vV3U2RWRZejYrZ0ViQXJHUURRK1J3QmMxMDNGcWRIU0xReWdEWHoKd0pNVGhDRE\ -dBVmR6V2NCemJYL1JIZms5bTZzK01HblNJUFBrNG9FOUdFbGdQc2JUZ2FrTzc3aElUdzRxZXJiTAp2M\ -0tiaTVOeVZZVFNVa3hDb0VRSkIrWlZJT3BVNFhRdVAvNkZkWldBRk1LU0Jkd1JLcnRmT1hZT1ExcVBv\ -YU1uCjZPR1VGMU0rYWZ0dTNCMkNhZ2ZMaE5hbVBoSjdxWTNSMzJhK2VRVllvYWlESXZKMElIUHFnQ0N\ -uWXlIaVBHTjQKS3VvaE9IZFpXTkxOUXdIQTg3SDV5NEwrSGZISmdFZFppdWYrbTNMa3JJcnN5MWFoUj\ -gyaEpZVkhreHVyZVRDcQpIR2NJaUIvTHpkSG91WFVrWGNrNjRvWXVRQnM3ZE9KVEx6bGlibU8xelRzV\ -WRoc0d4dE9zc2lYWkFjUURmZHBnCjNHdFJ3UkRwd0ZtL2k5TC9UWjMzMjBwY0VZd21aN1dBSzJRa3Z6\ -eWZlelRGeXdLWmh3c0RQRkwycllzLzhXdWsKRk1sek5BVmRMTytTS1RxeGlkM0l1elFaQ0FwU05ybTJ\ -MY2ZVN2grWAotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS\ -0tLQpNSUlGWFRDQ0EwV2dBd0lCQWdJSXV0T3MxUXBsSVRvd0RRWUpLb1pJaHZjTkFRRU1CUUF3VERFU\ -U1BNEdBMVVFCkF4TUhTbUZ0YVNCRFFURTRNRFlHQ2dtU0pvbVQ4aXhrQVFFVEtEYzVaakkyWlRWaVky\ -TXlPRFl5T0RKaU9HRTMKT0RRek1HTmpNRFl6TWpFeE9URTRZMlptT1Rnd0hoY05NakV3TkRFMk1UY3p\ -NakExV2hjTk16RXdOREUwTVRjegpNakExV2pCTU1SQXdEZ1lEVlFRREV3ZEtZVzFwSUVOQk1UZ3dOZ1\ -lLQ1pJbWlaUHlMR1FCQVJNb056bG1NalpsCk5XSmpZekk0TmpJNE1tSTRZVGM0TkRNd1kyTXdOak15T\ -VRFNU1UaGpabVk1T0RDQ0FpSXdEUVlKS29aSWh2Y04KQVFFQkJRQURnZ0lQQURDQ0Fnb0NnZ0lCQUxT\ -cGduY2tYODd3OE5vVzdRWE5DbWtHSHphVnN1NE9UbjFSSUhqVQpGR0w5MjNvVlk5RHVoZjFtVnllamg\ -remVCOCszaTR4b1E5MU04WDdTVnk4SHZxejQ4c0I2djAxMk5RTGpwN08xCkd4dmc1a0REeDRqbmVyUW\ -d6d0JzVEFuaC8xNy9SOEFDZkhINzYzUjhMRjY1clJPSStOT21KNUV2cU9rRWJJOXYKb0UySko1czBVS\ -HZZM1hTQ2pid01ROXRkM2c0SG9LNmRYSU44a2d6ejg0Yi83YkF5TnZ4a09rNnVSei9CSjhhZQozWjZu\ -dElza2tmVHhIZFpBbGtpVW5DU21Bb3VwQ01TbGRLS3RMRzNja2FhN0FDMDRxS29Rekc0WTN2TlRCOUl\ -mCmdvYThOazlKMThoaFN2REVuR0haaURLR1dkQVA5RW5GRHZERnRFRFZyVVZSWUN5WHdyejMya29Na3\ -dYODVUMTgKc1F2TUZSdWRsa1BoOGxmbnFEWTFpZEd1R2IvSGlnU1ZqalJKZlVTbS9lZ3hycStSTnpJW\ -GFBOWNuOXAxQkdFbwpZU0YxeUdtTEgzSkJscUhVRTg4VnFkenZRdXZ3Mkhvakdvd0pkdlhmU2loZk5W\ -OTh6VkEzQ2o3L0d3V2w0VU9OCkRUVUk2SzUrR3Y0a01iYXdxcWduaFlHbHBlZVp2bHE2UlI3OVpNblR\ -WVjJ1RWo4aERYQzdFTGN2Yk5tekJSSkoKS0gzaUxIbmNlSkxOV2NIMUV0K0pkaEFRR2xEYmk2RVR0dH\ -RuaWVaN3ZkMyt6UE00eXpkb3JEMlJXUjRyWG1acQpGRlI5eTBab2hQVTBjVmdKZHFtdUpyV01ZeTVsO\ -Gt5SWdwaExkdTR1WWRpc0RPMG1ianEySE55K3pEakpyYXFHClNWTkZBZ01CQUFHalF6QkJNQjBHQTFV\ -ZERnUVdCQlI1OG01YnpDaGlncmluaERETUJqSVJrWXovbURBUEJnTlYKSFJNQkFmOEVCVEFEQVFIL01\ -BOEdBMVVkRHdFQi93UUZBd01IQmdBd0RRWUpLb1pJaHZjTkFRRU1CUUFEZ2dJQgpBRWFDblluYm9yWm\ -RhWWZRUmxSb0dtSE94T1g5VFdNMXY0dWEweCtFcG11RDVWRDVlWEY1QVZkMlZadEdaeHYvCkd2VnFBU\ -2l0UTk3ampKV2p2bURWTUZtb3hQSmRYWDFkYTd5cmJYeFRmYm1mM1pac1RpdmZVdWQxYThxdUN3dTUK\ -TnBrdHFjV0JyMHRnNzFhOXlidHJOdm1GczZGRE1WMXkrY2JxYlp1UWlDWnc0WmZhekFaeTRQRGNocE9\ -SNjRCSwpBWFZIT05HcWFoV2hwcDkwd2E0TFEwUTE1U3FDR25kYVI4SHg5MHJOeEdkRjM1T1BLUjd1TD\ -drWDU4ZGxaWStDClJJK1pKMndYMzJUZzgrc1RtTmNaUTliWDdvS0t3R3E5Uk94SjZJSUhOSnN2bXhaS\ -nlPcmE2N21hNTd3OWxiQnUKSzJlQ3cwZjRDeHdLNU1LNStkbWx6R3dhZmJlMG00TTBvVjlhWUxZTzAw\ -R3Iza05heW9PdjRRVGtEM2pCMzVSMQpDMGJnQmk2eU1sTVJ2akZ5eEZkOFJpL003VG1jcXNObTB5akl\ -qbkZaenVtMFZTR1NLMXlRU3Flak40S0Y2R0JMCllpZ2JpM3c4WG5HYm9pZGdBUE9ncVVJeTJ1dEU1Ml\ -lzVXFsVHVncXhtM2xDOUhzaDM2UFJLNURDUG93eHVUNlgKcXo1M1ZiN2h6TkxLelpiRlJzbUdFOFY2c\ -WM2bXZTbUFXa25nL3QwaStXVmdGVkZuZFQrQ0oyNTJsa0ZacGljdAp6ekdETW44VUNDRUp4TDRKTklT\ -M2lLOUhlRys2MlZuay9QOEM3YVpLSXpVdjFud25rcVdUUUFYWDBKckJGdDdICjI5ZDk1RElmRTRuT0F\ -yS0JFNHc2Z1R4SU1uZzVzWi9ZbDFjcG5wUHlsR3VICi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K") - ("STUN.server" . "") - ("STUN.enable" . "false") - ("SRTP.rtpFallback" . "false") - ("SRTP.keyExchange" . "sdes") - ("SRTP.enable" . "true") - ("RingNS.uri" . "") - ("RingNS.account" . "0790738ce15fa05933b49dd77034312787da86c3") - ("DHT.PublicInCalls" . "true") - ("Account.videoPortMin" . "49152") - ("Account.videoPortMax" . "65534") - ("Account.videoEnabled" . "true") - ("Account.username" . "f3345f2775ddfe07a4b0d95daea111d15fbc1199") - ("Account.useragent" . "") - ("Account.upnpEnabled" . "true") - ("Account.type" . "RING") - ("Account.ringtoneEnabled" . "true") - ("Account.rendezVous" . "true") - ("Account.publishedSameAsLocal" . "true") - ("Account.publishedPort" . "5060") - ("Account.publishedAddress" . "") - ("Account.presenceSubscribeSupported" . "true") - ("Account.peerDiscovery" . "false") - ("Account.managerUsername" . "") - ("Account.managerUri" . "") - ("Account.mailbox" . "") - ("Account.localModeratorsEnabled" . "true") - ("Account.localInterface" . "default") - ("Account.hostname" . "bootstrap.jami.net") - ("Account.hasCustomUserAgent" . "false") - ("Account.enable" . "true") - ("Account.dtmfType" . "overrtp") - ("Account.displayName" . "dummy") - ("Account.defaultModerators" . "") - ("Account.audioPortMin" . "16384") - ("Account.audioPortMax" . "32766") - ("Account.archiveHasPassword" . "false") - ("Account.allowCertFromTrusted" . "true") - ("Account.allowCertFromHistory" . "true") - ("Account.allowCertFromContact" . "true") - ("Account.allModeratorEnabled" . "true") - ("Account.alias" . "dummy") - ("Account.activeCallLimit" . "-1") - ("Account.accountPublish" . "false") - ("Account.accountDiscovery" . "false"))) diff --git a/gnu/tests/telephony.scm b/gnu/tests/telephony.scm deleted file mode 100644 index 1155a9dbc2..0000000000 --- a/gnu/tests/telephony.scm +++ /dev/null @@ -1,366 +0,0 @@ -;;; GNU Guix --- Functional package management for GNU -;;; Copyright © 2021 Maxim Cournoyer . -;;; -;;; This file is part of GNU Guix. -;;; -;;; GNU Guix is free software; you can redistribute it and/or modify it -;;; under the terms of the GNU General Public License as published by -;;; the Free Software Foundation; either version 3 of the License, or (at -;;; your option) any later version. -;;; -;;; GNU Guix is distributed in the hope that it will be useful, but -;;; WITHOUT ANY WARRANTY; without even the implied warranty of -;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -;;; GNU General Public License for more details. -;;; -;;; You should have received a copy of the GNU General Public License -;;; along with GNU Guix. If not, see . - -(define-module (gnu tests telephony) - #:use-module (gnu) - #:use-module (gnu packages) - #:use-module (gnu packages guile) - #:use-module (gnu tests) - #:use-module (gnu system vm) - #:use-module (gnu services) - #:use-module (gnu services dbus) - #:use-module (gnu services networking) - #:use-module (gnu services ssh) - #:use-module (gnu services telephony) - #:use-module (guix gexp) - #:use-module (guix modules) - #:export (%test-jami - %test-jami-provisioning)) - -;;; -;;; Jami daemon. -;;; - -(include "data/jami-dummy-account.dat") ;defines %jami-account-content-sexp - -(define %dummy-jami-account-archive - ;; A Jami account archive is a gzipped JSON file. - (computed-file - "dummy-jami-account.gz" - (with-extensions (list guile-json-4 guile-zlib) - #~(begin - (use-modules (json) (zlib)) - (let ((port (open-output-file #$output))) - (call-with-gzip-output-port port - (lambda (port) - (scm->json '#$%jami-account-content-sexp port)))))))) - -(define %allowed-contacts '("1dbcb0f5f37324228235564b79f2b9737e9a008f" - "2dbcb0f5f37324228235564b79f2b9737e9a008f")) - -(define %moderators '("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" - "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")) - -(define %dummy-jami-account (jami-account - (archive %dummy-jami-account-archive) - (allowed-contacts %allowed-contacts) - (moderators %moderators) - (rendezvous-point? #t) - (peer-discovery? #f) - (bootstrap-hostnames '("bootstrap.me" - "fallback.another.host")) - (name-server-uri "https://my.name.server"))) - -(define* (make-jami-os #:key provisioning?) - (operating-system - (host-name "jami") - (timezone "America/Montreal") - (locale "en_US.UTF-8") - - (bootloader (bootloader-configuration - (bootloader grub-bootloader) - (target "/dev/sdX"))) - (file-systems (cons (file-system - (device (file-system-label "my-root")) - (mount-point "/") - (type "ext4")) - %base-file-systems)) - (firmware '()) - - (services (cons* (service jami-service-type - (if provisioning? - (jami-configuration - (debug? #t) - (accounts (list %dummy-jami-account))) - (jami-configuration - (debug? #t)))) - (service dbus-root-service-type) - ;; The following services/packages are added for - ;; debugging purposes. - (service dhcp-client-service-type) - (service openssh-service-type - (openssh-configuration - (permit-root-login #t) - (allow-empty-passwords? #t))) - %base-services)) - (packages (cons* (specification->package "recutils") - (specification->package "strace") - %base-packages)))) - -(define %jami-os - (make-jami-os)) - -(define %jami-os-provisioning - (make-jami-os #:provisioning? #t)) - -(define* (run-jami-test #:key provisioning?) - "Run tests in %JAMI-OS. When PROVISIONING? is true, test the -accounts provisioning feature of the service." - (define os (marionette-operating-system - (if provisioning? - %jami-os-provisioning - %jami-os) - #:imported-modules '((gnu services herd) - (guix combinators)))) - (define vm (virtual-machine - (operating-system os) - (memory-size 512))) - - (define username (assoc-ref %jami-account-content-sexp - "Account.username")) - - (define test - (with-imported-modules (source-module-closure - '((gnu build marionette) - (gnu build jami-service))) - #~(begin - (use-modules (rnrs base) - (srfi srfi-11) - (srfi srfi-64) - (gnu build marionette) - (gnu build jami-service)) - - (define marionette - (make-marionette (list #$vm))) - - (mkdir #$output) - (chdir #$output) - - (test-begin "jami") - - (test-assert "service is running" - (marionette-eval - '(begin - (use-modules (gnu services herd)) - (match (start-service 'jami) - (#f #f) - (('service response-parts ...) - (match (assq-ref response-parts 'running) - ((pid) (number? pid)))))) - marionette)) - - (test-assert "service can be stopped" - (marionette-eval - '(begin - (use-modules (gnu services herd) - (rnrs base)) - (setenv "PATH" "/run/current-system/profile/bin") - (let ((pid (match (start-service 'jami) - (#f #f) - (('service response-parts ...) - (match (assq-ref response-parts 'running) - ((pid) pid)))))) - - (assert (number? pid)) - - (match (stop-service 'jami) - (services ;a list of service symbols - (member 'jami services))) - ;; Sometimes, the process still appear in pgrep, even - ;; though we are using waitpid after sending it SIGTERM - ;; in the service; use retries. - (with-retries 20 1 - (not (zero? (status:exit-val - (system* "pgrep" "dring"))))))) - marionette)) - - (test-assert "service can be restarted" - (marionette-eval - '(begin - (use-modules (gnu services herd) - (rnrs base)) - ;; Start and retrieve the current PID. - (define pid (match (start-service 'jami) - (#f #f) - (('service response-parts ...) - (match (assq-ref response-parts 'running) - ((pid) pid))))) - (assert (number? pid)) - - ;; Restart the service. - (restart-service 'jami) - - (define new-pid (match (start-service 'jami) - (#f #f) - (('service response-parts ...) - (match (assq-ref response-parts 'running) - ((pid) pid))))) - (assert (number? new-pid)) - - (not (eq? pid new-pid))) - marionette)) - - (unless #$provisioning? (test-skip 1)) - (test-assert "jami accounts provisioning, account present" - (marionette-eval - '(begin - (use-modules (gnu services herd) - (rnrs base)) - ;; Accounts take some time to appear after being added. - (with-retries 20 1 - (with-shepherd-action 'jami ('list-accounts) results - (let ((account (assoc-ref (car results) #$username))) - (assert (string=? #$username - (assoc-ref account - "Account.username"))))))) - marionette)) - - (unless #$provisioning? (test-skip 1)) - (test-assert "jami accounts provisioning, allowed-contacts" - (marionette-eval - '(begin - (use-modules (gnu services herd) - (rnrs base) - (srfi srfi-1)) - - ;; Public mode is disabled. - (with-shepherd-action 'jami ('list-account-details) - results - (let ((account (assoc-ref (car results) #$username))) - (assert (string=? "false" - (assoc-ref account - "DHT.PublicInCalls"))))) - - ;; Allowed contacts match those declared in the configuration. - (with-shepherd-action 'jami ('list-contacts) results - (let ((contacts (assoc-ref (car results) #$username))) - (assert (lset= string-ci=? contacts '#$%allowed-contacts))))) - marionette)) - - (unless #$provisioning? (test-skip 1)) - (test-assert "jami accounts provisioning, moderators" - (marionette-eval - '(begin - (use-modules (gnu services herd) - (rnrs base) - (srfi srfi-1)) - - ;; Moderators match those declared in the configuration. - (with-shepherd-action 'jami ('list-moderators) results - (let ((moderators (assoc-ref (car results) #$username))) - (assert (lset= string-ci=? moderators '#$%moderators)))) - - ;; Moderators can be added via the Shepherd action. - (with-shepherd-action 'jami - ('add-moderator "cccccccccccccccccccccccccccccccccccccccc" - #$username) results - (let ((moderators (car results))) - (assert (lset= string-ci=? moderators - (cons "cccccccccccccccccccccccccccccccccccccccc" - '#$%moderators)))))) - marionette)) - - (unless #$provisioning? (test-skip 1)) - (test-assert "jami service actions, ban/unban contacts" - (marionette-eval - '(begin - (use-modules (gnu services herd) - (rnrs base) - (srfi srfi-1)) - - ;; Globally ban a contact. - (with-shepherd-action 'jami - ('ban-contact "1dbcb0f5f37324228235564b79f2b9737e9a008f") _ - (with-shepherd-action 'jami ('list-banned-contacts) results - (every (match-lambda - ((username . banned-contacts) - (member "1dbcb0f5f37324228235564b79f2b9737e9a008f" - banned-contacts))) - (car results)))) - - ;; Ban a contact for a single account. - (with-shepherd-action 'jami - ('ban-contact "dddddddddddddddddddddddddddddddddddddddd" - #$username) _ - (with-shepherd-action 'jami ('list-banned-contacts) results - (every (match-lambda - ((username . banned-contacts) - (let ((found? (member "dddddddddddddddddddddddddddddddddddddddd" - banned-contacts))) - (if (string=? #$username username) - found? - (not found?))))) - (car results))))) - marionette)) - - (unless #$provisioning? (test-skip 1)) - (test-assert "jami service actions, enable/disable accounts" - (marionette-eval - '(begin - (use-modules (gnu services herd) - (rnrs base)) - - (with-shepherd-action 'jami - ('disable-account #$username) _ - (with-shepherd-action 'jami ('list-accounts) results - (let ((account (assoc-ref (car results) #$username))) - (assert (string= "false" - (assoc-ref account "Account.enable")))))) - - (with-shepherd-action 'jami - ('enable-account #$username) _ - (with-shepherd-action 'jami ('list-accounts) results - (let ((account (assoc-ref (car results) #$username))) - (assert (string= "true" - (assoc-ref account "Account.enable"))))))) - marionette)) - - (unless #$provisioning? (test-skip 1)) - (test-assert "jami account parameters" - (marionette-eval - '(begin - (use-modules (gnu services herd) - (rnrs base) - (srfi srfi-1)) - - (with-shepherd-action 'jami ('list-account-details) results - (let ((account-details (assoc-ref (car results) - #$username))) - (assert (lset<= - equal? - '(("Account.hostname" . - "bootstrap.me;fallback.another.host") - ("Account.peerDiscovery" . "false") - ("Account.rendezVous" . "true") - ("RingNS.uri" . "https://my.name.server")) - account-details))))) - marionette)) - - (test-end) - (exit (= (test-runner-fail-count (test-runner-current)) 0))))) - - (gexp->derivation (if provisioning? - "jami-provisioning-test" - "jami-test") - test)) - -(define %test-jami - (system-test - (name "jami") - (description "Basic tests for the jami service.") - (value (run-jami-test)))) - -(define %test-jami-provisioning - (system-test - (name "jami-provisioning") - (description "Provisioning test for the jami service.") - (value (run-jami-test #:provisioning? #t)))) - -;; Local Variables: -;; eval: (put 'with-retries 'scheme-indent-function 2) -;; End: diff --git a/tests/services/telephony.scm b/tests/services/telephony.scm deleted file mode 100644 index b4a0f120d4..0000000000 --- a/tests/services/telephony.scm +++ /dev/null @@ -1,446 +0,0 @@ -;;; GNU Guix --- Functional package management for GNU -;;; Copyright © 2021 Maxim Cournoyer -;;; -;;; This file is part of GNU Guix. -;;; -;;; GNU Guix is free software; you can redistribute it and/or modify it -;;; under the terms of the GNU General Public License as published by -;;; the Free Software Foundation; either version 3 of the License, or (at -;;; your option) any later version. -;;; -;;; GNU Guix is distributed in the hope that it will be useful, but -;;; WITHOUT ANY WARRANTY; without even the implied warranty of -;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -;;; GNU General Public License for more details. -;;; -;;; You should have received a copy of the GNU General Public License -;;; along with GNU Guix. If not, see . - -(define-module (tests services telephony) - #:use-module (gnu build jami-service) - #:use-module (gnu services telephony) - #:use-module (srfi srfi-64)) - -;;; Tests for the (gnu services telephony) and related modules. - -(test-begin "jami-service") - -(define parse-dbus-reply - (@@ (gnu build jami-service) parse-dbus-reply)) - -(define parse-account-ids - (@@ (gnu build jami-service) parse-account-ids)) - -(define parse-account-details - (@@ (gnu build jami-service) parse-account-details)) - -(define parse-contacts - (@@ (gnu build jami-service) parse-contacts)) - -(define jami-account->alist - (@@ (gnu services telephony) jami-account->alist)) - -;; $ dbus-send --print-reply --dest="cx.ring.Ring" \ -;; "/cx/ring/Ring/ConfigurationManager" \ -;; "cx.ring.Ring.ConfigurationManager.getAccountList" -(define getAccountList-reply "\ -method return time=1622217253.386711 sender=:1.7 -> destination=:1.14 serial=140 reply_serial=2 - array [ - string \"addf37fbb558d6a0\" - string \"d5cbeb7d08c98a65\" - string \"398af0c6b74ce101\" - ] -") - -(test-equal "parse-account-ids" - '("addf37fbb558d6a0" "d5cbeb7d08c98a65" "398af0c6b74ce101") - (parse-account-ids getAccountList-reply)) - -;; $ dbus-send --print-reply --dest="cx.ring.Ring" \ -;; "/cx/ring/Ring/ConfigurationManager" \ -;; "cx.ring.Ring.ConfigurationManager.getAccountDetails" \ -;; 'string:398af0c6b74ce101' -(define getAccountDetails-reply "\ -method return time=1622254991.789588 sender=:1.7 -> destination=:1.19 serial=145 reply_serial=2 - array [ - dict entry( - string \"Account.accountDiscovery\" - string \"false\" - ) - dict entry( - string \"Account.accountPublish\" - string \"false\" - ) - dict entry( - string \"Account.activeCallLimit\" - string \"-1\" - ) - dict entry( - string \"Account.alias\" - string \"some-rendezvous-point-name\" - ) - dict entry( - string \"Account.allModeratorEnabled\" - string \"true\" - ) - dict entry( - string \"Account.allowCertFromContact\" - string \"true\" - ) - dict entry( - string \"Account.allowCertFromHistory\" - string \"true\" - ) - dict entry( - string \"Account.allowCertFromTrusted\" - string \"true\" - ) - dict entry( - string \"Account.archiveHasPassword\" - string \"false\" - ) - dict entry( - string \"Account.audioPortMax\" - string \"32766\" - ) - dict entry( - string \"Account.audioPortMin\" - string \"16384\" - ) - dict entry( - string \"Account.autoAnswer\" - string \"false\" - ) - dict entry( - string \"Account.defaultModerators\" - string \"\" - ) - dict entry( - string \"Account.deviceID\" - string \"94b4070fc7a8afa8482c777a9822c52e6af2e1bd\" - ) - dict entry( - string \"Account.deviceName\" - string \"some-device\" - ) - dict entry( - string \"Account.dhtProxyListUrl\" - string \"https://config.jami.net/proxyList\" - ) - dict entry( - string \"Account.displayName\" - string \"some-rendezvous-point-name\" - ) - dict entry( - string \"Account.dtmfType\" - string \"overrtp\" - ) - dict entry( - string \"Account.enable\" - string \"true\" - ) - dict entry( - string \"Account.hasCustomUserAgent\" - string \"false\" - ) - dict entry( - string \"Account.hostname\" - string \"bootstrap.jami.net\" - ) - dict entry( - string \"Account.localInterface\" - string \"default\" - ) - dict entry( - string \"Account.localModeratorsEnabled\" - string \"true\" - ) - dict entry( - string \"Account.mailbox\" - string \"\" - ) - dict entry( - string \"Account.managerUri\" - string \"\" - ) - dict entry( - string \"Account.managerUsername\" - string \"\" - ) - dict entry( - string \"Account.peerDiscovery\" - string \"false\" - ) - dict entry( - string \"Account.presenceSubscribeSupported\" - string \"true\" - ) - dict entry( - string \"Account.proxyEnabled\" - string \"false\" - ) - dict entry( - string \"Account.proxyPushToken\" - string \"\" - ) - dict entry( - string \"Account.proxyServer\" - string \"dhtproxy.jami.net:[80-95]\" - ) - dict entry( - string \"Account.publishedAddress\" - string \"\" - ) - dict entry( - string \"Account.publishedPort\" - string \"5060\" - ) - dict entry( - string \"Account.publishedSameAsLocal\" - string \"true\" - ) - dict entry( - string \"Account.rendezVous\" - string \"true\" - ) - dict entry( - string \"Account.ringtoneEnabled\" - string \"true\" - ) - dict entry( - string \"Account.ringtonePath\" - string \"/usr/share/ring/ringtones/default.opus\" - ) - dict entry( - string \"Account.type\" - string \"RING\" - ) - dict entry( - string \"Account.upnpEnabled\" - string \"true\" - ) - dict entry( - string \"Account.useragent\" - string \"\" - ) - dict entry( - string \"Account.username\" - string \"ccb8bbe2382343f7feb140710ab48aaf1b55634e\" - ) - dict entry( - string \"Account.videoEnabled\" - string \"true\" - ) - dict entry( - string \"Account.videoPortMax\" - string \"65534\" - ) - dict entry( - string \"Account.videoPortMin\" - string \"49152\" - ) - dict entry( - string \"DHT.PublicInCalls\" - string \"true\" - ) - dict entry( - string \"DHT.port\" - string \"7766\" - ) - dict entry( - string \"RingNS.account\" - string \"3989b55313a911b6f0c004748b49b254f35c9ef6\" - ) - dict entry( - string \"RingNS.uri\" - string \"\" - ) - dict entry( - string \"SRTP.enable\" - string \"true\" - ) - dict entry( - string \"SRTP.keyExchange\" - string \"sdes\" - ) - dict entry( - string \"SRTP.rtpFallback\" - string \"false\" - ) - dict entry( - string \"STUN.enable\" - string \"false\" - ) - dict entry( - string \"STUN.server\" - string \"\" - ) - dict entry( - string \"TLS.certificateFile\" - string \"/var/lib/jami/.local/share/jami/398af0c6b74ce101/ring_device.crt\" - ) - dict entry( - string \"TLS.certificateListFile\" - string \"\" - ) - dict entry( - string \"TLS.ciphers\" - string \"\" - ) - dict entry( - string \"TLS.method\" - string \"Automatic\" - ) - dict entry( - string \"TLS.negotiationTimeoutSec\" - string \"-1\" - ) - dict entry( - string \"TLS.password\" - string \"\" - ) - dict entry( - string \"TLS.privateKeyFile\" - string \"/var/lib/jami/.local/share/jami/398af0c6b74ce101/ring_device.key\" - ) - dict entry( - string \"TLS.requireClientCertificate\" - string \"true\" - ) - dict entry( - string \"TLS.serverName\" - string \"\" - ) - dict entry( - string \"TLS.verifyClient\" - string \"true\" - ) - dict entry( - string \"TLS.verifyServer\" - string \"true\" - ) - dict entry( - string \"TURN.enable\" - string \"true\" - ) - dict entry( - string \"TURN.password\" - string \"ring\" - ) - dict entry( - string \"TURN.realm\" - string \"ring\" - ) - dict entry( - string \"TURN.server\" - string \"turn.jami.net\" - ) - dict entry( - string \"TURN.username\" - string \"ring\" - ) - ] -") - -(test-equal "parse-account-details; username, alias and display name" - '("ccb8bbe2382343f7feb140710ab48aaf1b55634e" ;username - "some-rendezvous-point-name" ;alias - "some-rendezvous-point-name") ;displayName - (let ((account-details (parse-account-details getAccountDetails-reply))) - (list (assoc-ref account-details "Account.username") - (assoc-ref account-details "Account.alias") - (assoc-ref account-details "Account.displayName")))) - -(define getContacts-reply "\ -method return time=1627014042.752673 sender=:1.113 -> destination=:1.186 serial=220 reply_serial=2 - array [ - array [ - dict entry( - string \"added\" - string \"1578883327\" - ) - dict entry( - string \"confirmed\" - string \"true\" - ) - dict entry( - string \"id\" - string \"1c7d5a09464223442549fef172a3cf6f4de9b01c\" - ) - ] - array [ - dict entry( - string \"added\" - string \"1623107941\" - ) - dict entry( - string \"confirmed\" - string \"true\" - ) - dict entry( - string \"id\" - string \"5903c6c9ac5cb863c64e559add3d5d1c8c563449\" - ) - ] - array [ - dict entry( - string \"added\" - string \"1595996256\" - ) - dict entry( - string \"confirmed\" - string \"true\" - ) - dict entry( - string \"id\" - string \"ff2d72a548693214fb3a0f0f7a943b5e2bb9be03\" - ) - ] - ]") - -(test-equal "parse-account-contacts" - '((("added" . "1578883327") - ("confirmed" . "true") - ("id" . "1c7d5a09464223442549fef172a3cf6f4de9b01c")) - (("added" . "1623107941") - ("confirmed" . "true") - ("id" . "5903c6c9ac5cb863c64e559add3d5d1c8c563449")) - (("added" . "1595996256") - ("confirmed" . "true") - ("id" . "ff2d72a548693214fb3a0f0f7a943b5e2bb9be03"))) - (parse-contacts getContacts-reply)) - -(define getContacts-empty-reply "\ -method return time=1627400787.873988 sender=:1.1197 -> destination=:1.1463 serial=2127 reply_serial=2 - array [ - ]") - -(test-equal "parse-account-contacts, empty array" - '() - (parse-contacts getContacts-empty-reply)) - -(define %dummy-jami-account (jami-account - (archive "/tmp/dummy.gz"))) - -(define %dummy-jami-account-2 (jami-account - (archive "/tmp/dummy.gz") - (rendezvous-point? #t) - (peer-discovery? #f) - (bootstrap-hostnames '("bootstrap.me" - "fallback.another.host")) - (name-server-uri "https://my.name.server"))) - -(test-equal "jami-account->alist, no account detail value set" - '() - (jami-account->alist %dummy-jami-account)) - -(test-equal "jami-account->alist, with account detail values" - '(("Account.hostname" . "bootstrap.me;fallback.another.host") - ("Account.peerDiscovery" . "false") - ("Account.rendezVous" . "true") - ("RingNS.uri" . "https://my.name.server")) - (sort (jami-account->alist %dummy-jami-account-2) - (lambda (x y) - (string<=? (car x) (car y))))) - -(test-end) -- cgit v1.2.3 From 10f554700c417b84c22c56fdd007567a52c45d75 Mon Sep 17 00:00:00 2001 From: Maxim Cournoyer Date: Mon, 2 Aug 2021 16:06:04 -0400 Subject: Reinstate "services: Add a service for Jami." This reverts commit 4673f817938d9d2b1b40a072ab2e0c44a32ccc97, which reverted commit 69dcc24c9f0cdfea674eb690e7755d26a25ced2b with the fix detailed below. Thanks to Christopher Baines for reporting the failure and proposing a fix. * guix/self.scm (compiled-guix) [*system-test-modules*]: Add the test data files via the 'extra-files' argument. * gnu/local.mk (dist_patch_DATA): Move the tests/data/jami-dummy-account.dat file to... * gnu/local.mk (MODULES_NOT_COMPILED): ... here. --- Makefile.am | 1 + doc/guix.texi | 228 ++++++++++++ gnu/build/jami-service.scm | 587 +++++++++++++++++++++++++++++ gnu/local.mk | 5 +- gnu/services/telephony.scm | 684 +++++++++++++++++++++++++++++++++- gnu/tests/data/jami-dummy-account.dat | 392 +++++++++++++++++++ gnu/tests/telephony.scm | 366 ++++++++++++++++++ guix/self.scm | 2 + tests/services/telephony.scm | 446 ++++++++++++++++++++++ 9 files changed, 2708 insertions(+), 3 deletions(-) create mode 100644 gnu/build/jami-service.scm create mode 100644 gnu/tests/data/jami-dummy-account.dat create mode 100644 gnu/tests/telephony.scm create mode 100644 tests/services/telephony.scm (limited to 'gnu/local.mk') diff --git a/Makefile.am b/Makefile.am index d5ec909213..5542aa1c56 100644 --- a/Makefile.am +++ b/Makefile.am @@ -489,6 +489,7 @@ SCM_TESTS = \ tests/services/file-sharing.scm \ tests/services/configuration.scm \ tests/services/linux.scm \ + tests/services/telephony.scm \ tests/sets.scm \ tests/size.scm \ tests/status.scm \ diff --git a/doc/guix.texi b/doc/guix.texi index 2298d512a1..9a9c85678c 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -22526,6 +22526,234 @@ and Error. @node Telephony Services @subsection Telephony Services +@cindex telephony, services +The @code{(gnu services telephony)} module contains Guix service +definitions for telephony services. Currently it provides the following +services: + +@subsubheading Jami + +@cindex jami, service + +This section describes how to configure a Jami server that can be used +to host video (or audio) conferences, among other uses. The following +example demonstrates how to specify Jami account archives (backups) to +be provisioned automatically: + +@lisp +(service jami-service-type + (jami-configuration + (accounts + (list (jami-account + (archive "/etc/jami/unencrypted-account-1.gz")) + (jami-account + (archive "/etc/jami/unencrypted-account-2.gz")))))) +@end lisp + +When the accounts field is specified, the Jami account files of the +service found under @file{/var/lib/jami} are recreated every time the +service starts. + +Jami accounts and their corresponding backup archives can be generated +using either the @code{jami-qt} or @code{jami-gnome} Jami clients. The +accounts should not be password-protected, but it is wise to ensure +their files are only readable by @samp{root}. + +The next example shows how to declare that only some contacts should be +allowed to communicate with a given account: + +@lisp +(service jami-service-type + (jami-configuration + (accounts + (list (jami-account + (archive "/etc/jami/unencrypted-account-1.gz") + (peer-discovery? #t) + (rendezvous-point? #t) + (allowed-contacts + '("1dbcb0f5f37324228235564b79f2b9737e9a008f" + "2dbcb0f5f37324228235564b79f2b9737e9a008f"))))))) +@end lisp + +In this mode, only the declared @code{allowed-contacts} can initiate +communication with the Jami account. This can be used, for example, +with rendezvous point accounts to create a private video conferencing +space. + +To put the system administrator in full control of the conferences +hosted on their system, the Jami service supports the following actions: + +@example sh +# herd doc jami list-actions jami +(list-accounts + list-account-details + list-banned-contacts + list-contacts + list-moderators + add-moderator + ban-contact + enable-account + disable-account) +@end example + +The above actions aim to provide the most valuable actions for +moderation purposes, not to cover the whole Jami API. Users wanting to +interact with the Jami daemon from Guile may be interested in +experimenting with the @code{(gnu build jami-service)} module, which +powers the above Shepherd actions. + +@c TODO: This should be auto-generated from the doc already defined on +@c the shepherd-actions themselves in (gnu services telephony). +The @code{add-moderator} and @code{ban-contact} actions accept a contact +@emph{fingerprint} (40 characters long hash) as first argument and an +account fingerprint or username as second argument: + +@example sh +# herd add-moderator jami 1dbcb0f5f37324228235564b79f2b9737e9a008f \ + f3345f2775ddfe07a4b0d95daea111d15fbc1199 + +# herd list-moderators jami +Moderators for account f3345f2775ddfe07a4b0d95daea111d15fbc1199: + - 1dbcb0f5f37324228235564b79f2b9737e9a008f + +@end example + +In the case of @code{ban-contact}, the second username argument is +optional; when omitted, the account is banned from all Jami accounts: + +@example sh +# herd ban-contact jami 1dbcb0f5f37324228235564b79f2b9737e9a008f + +# herd list-banned-contacts jami +Banned contacts for account f3345f2775ddfe07a4b0d95daea111d15fbc1199: + - 1dbcb0f5f37324228235564b79f2b9737e9a008f + +@end example + +Banned contacts are also stripped from their moderation privileges. + +The @code{disable-account} action allows to completely disconnect an +account from the network, making it unreachable, while +@code{enable-account} does the inverse. They accept a single account +username or fingerprint as first argument: + +@example sh +# herd disable-account jami f3345f2775ddfe07a4b0d95daea111d15fbc1199 + +# herd list-accounts jami +The following Jami accounts are available: + - f3345f2775ddfe07a4b0d95daea111d15fbc1199 (dummy) [disabled] + +@end example + +The @code{list-account-details} action prints the detailed parameters of +each accounts in the Recutils format, which means the @command{recsel} +command can be used to select accounts of interest (@pxref{Selection +Expressions,,,recutils, GNU recutils manual}). Note that period +characters (@samp{.}) found in the account parameter keys are mapped to +underscores (@samp{_}) in the output, to meet the requirements of the +Recutils format. The following example shows how to print the account +fingerprints for all accounts operating in the rendezvous point mode: + +@example sh +# herd list-account-details jami | \ + recsel -p Account.username -e 'Account.rendezVous ~ "true"' +Account_username: f3345f2775ddfe07a4b0d95daea111d15fbc1199 +@end example + +The remaining actions should be self-explanatory. + +The complete set of available configuration options is detailed below. + +@c TODO: Ideally, the following fragments would be auto-generated at +@c build time, so that they needn't be manually duplicated. +@c Auto-generated via (configuration->documentation 'jami-configuration) +@deftp {Data Type} jami-configuration +Available @code{jami-configuration} fields are: + +@table @asis +@item @code{jamid} (default: @code{libring}) (type: package) +The Jami daemon package to use. + +@item @code{dbus} (default: @code{dbus}) (type: package) +The D-Bus package to use to start the required D-Bus session. + +@item @code{nss-certs} (default: @code{nss-certs}) (type: package) +The nss-certs package to use to provide TLS certificates. + +@item @code{enable-logging?} (default: @code{#t}) (type: boolean) +Whether to enable logging to syslog. + +@item @code{debug?} (default: @code{#f}) (type: boolean) +Whether to enable debug level messages. + +@item @code{auto-answer?} (default: @code{#f}) (type: boolean) +Whether to force automatic answer to incoming calls. + +@item @code{accounts} (default: @code{disabled}) (type: maybe-jami-account-list) +A list of Jami accounts to be (re-)provisioned every time the Jami +daemon service starts. When providing this field, the account +directories under @file{/var/lib/jami/} are recreated every time the +service starts, ensuring a consistent state. + +@end table + +@end deftp + +@c Auto-generated via (configuration->documentation 'jami-account) +@deftp {Data Type} jami-account +Available @code{jami-account} fields are: + +@table @asis +@item @code{archive} (type: string-or-computed-file) +The account archive (backup) file name of the account. This is used to +provision the account when the service starts. The account archive +should @emph{not} be encrypted. It is highly recommended to make it +readable only to the @samp{root} user (i.e., not in the store), to guard +against leaking the secret key material of the Jami account it contains. + +@item @code{allowed-contacts} (default: @code{disabled}) (type: maybe-account-fingerprint-list) +The list of allowed contacts for the account, entered as their 40 +characters long fingerprint. Messages or calls from accounts not in +that list will be rejected. When unspecified, the configuration of the +account archive is used as-is with respect to contacts and public +inbound calls/messaging allowance, which typically defaults to allow any +contact to communicate with the account. + +@item @code{moderators} (default: @code{disabled}) (type: maybe-account-fingerprint-list) +The list of contacts that should have moderation privileges (to ban, +mute, etc. other users) in rendezvous conferences, entered as their 40 +characters long fingerprint. When unspecified, the configuration of the +account archive is used as-is with respect to moderation, which +typically defaults to allow anyone to moderate. + +@item @code{rendezvous-point?} (default: @code{disabled}) (type: maybe-boolean) +Whether the account should operate in the rendezvous mode. In this +mode, all the incoming audio/video calls are mixed into a conference. +When left unspecified, the value from the account archive prevails. + +@item @code{peer-discovery?} (default: @code{disabled}) (type: maybe-boolean) +Whether peer discovery should be enabled. Peer discovery is used to +discover other OpenDHT nodes on the local network, which can be useful +to maintain communication between devices on such network even when the +connection to the the Internet has been lost. When left unspecified, +the value from the account archive prevails. + +@item @code{bootstrap-hostnames} (default: @code{disabled}) (type: maybe-string-list) +A list of hostnames or IPs pointing to OpenDHT nodes, that should be +used to initially join the OpenDHT network. When left unspecified, the +value from the account archive prevails. + +@item @code{name-server-uri} (default: @code{disabled}) (type: maybe-string) +The URI of the name server to use, that can be used to retrieve the +account fingerprint for a registered username. + +@end table + +@end deftp + +@subsubheading Murmur (VoIP server) + @cindex Murmur (VoIP server) @cindex VoIP server This section describes how to set up and run a Murmur server. Murmur is diff --git a/gnu/build/jami-service.scm b/gnu/build/jami-service.scm new file mode 100644 index 0000000000..d44e87387d --- /dev/null +++ b/gnu/build/jami-service.scm @@ -0,0 +1,587 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2021 Maxim Cournoyer +;;; +;;; This file is part of GNU Guix. +;;; +;;; GNU Guix is free software; you can redistribute it and/or modify it +;;; under the terms of the GNU General Public License as published by +;;; the Free Software Foundation; either version 3 of the License, or (at +;;; your option) any later version. +;;; +;;; GNU Guix is distributed in the hope that it will be useful, but +;;; WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Guix. If not, see . + +;;; Commentary: +;;; +;;; This module contains helpers used as part of the jami-service-type +;;; definition. +;;; +;;; Code: + +(define-module (gnu build jami-service) + #:use-module (ice-9 format) + #:use-module (ice-9 match) + #:use-module (ice-9 peg) + #:use-module (ice-9 rdelim) + #:use-module (ice-9 regex) + #:use-module (rnrs io ports) + #:autoload (shepherd service) (fork+exec-command) + #:use-module (srfi srfi-1) + #:use-module (srfi srfi-26) + #:export (account-fingerprint? + account-details->recutil + get-accounts + get-usernames + set-account-details + add-account + account->username + username->account + username->contacts + enable-account + disable-account + + add-contact + remove-contact + + set-all-moderators + set-moderator + username->all-moderators? + username->moderators + + dbus-available-services + dbus-service-available? + + %send-dbus-binary + %send-dbus-bus + %send-dbus-user + %send-dbus-group + %send-dbus-debug + send-dbus + + with-retries)) + +;;; +;;; Utilities. +;;; + +(define-syntax-rule (with-retries n delay body ...) + "Retry the code in BODY up to N times until it doesn't raise an exception +nor return #f, else raise an error. A delay of DELAY seconds is inserted +before each retry." + (let loop ((attempts 0)) + (catch #t + (lambda () + (let ((result (begin body ...))) + (if (not result) + (error "failed attempt" attempts) + result))) + (lambda args + (if (< attempts n) + (begin + (sleep delay) ;else wait and retry + (loop (+ 1 attempts))) + (error "maximum number of retry attempts reached" + body ... args)))))) + +(define (alist->list alist) + "Flatten ALIST into a list." + (append-map (match-lambda + (() '()) + ((key . value) + (list key value))) + alist)) + +(define account-fingerprint-rx (make-regexp "[0-9A-f]{40}")) + +(define (account-fingerprint? val) + "A Jami account fingerprint is 40 characters long and only contains +hexadecimal characters." + (and (string? val) + (regexp-exec account-fingerprint-rx val))) + + +;;; +;;; D-Bus reply parser. +;;; + +(define (parse-dbus-reply reply) + "Return the parse tree of REPLY, a string returned by the 'dbus-send' +command." + ;; Refer to 'man 1 dbus-send' for the grammar reference. Note that the + ;; format of the replies doesn't match the format of the input, which is the + ;; one documented, but it gives an idea. For an even better reference, see + ;; the `print_iter' procedure of the 'dbus-print-message.c' file from the + ;; 'dbus' package sources. + (define-peg-string-patterns + "contents <- header (item / container (item / container*)?) + item <-- WS type WS value NL + container <- array / dict / variant + array <-- array-start (item / container)* array-end + dict <-- array-start dict-entry* array-end + dict-entry <-- dict-entry-start item item dict-entry-end + variant <-- variant-start item + type <-- 'string' / 'int16' / 'uint16' / 'int32' / 'uint32' / 'int64' / + 'uint64' / 'double' / 'byte' / 'boolean' / 'objpath' + value <-- (!NL .)* NL + header < (!NL .)* NL + variant-start < WS 'variant' + array-start < WS 'array [' NL + array-end < WS ']' NL + dict-entry-start < WS 'dict entry(' NL + dict-entry-end < WS ')' NL + DQ < '\"' + WS < ' '* + NL < '\n'*") + + (peg:tree (match-pattern contents reply))) + +(define (strip-quotes text) + "Strip the leading and trailing double quotes (\") characters from TEXT." + (let* ((text* (if (string-prefix? "\"" text) + (string-drop text 1) + text)) + (text** (if (string-suffix? "\"" text*) + (string-drop-right text* 1) + text*))) + text**)) + +(define (deserialize-item item) + "Return the value described by the ITEM parse tree as a Guile object." + ;; Strings are printed wrapped in double quotes (see the print_iter + ;; procedure in dbus-print-message.c). + (match item + (('item ('type "string") ('value value)) + (strip-quotes value)) + (('item ('type "boolean") ('value value)) + (if (string=? "true" value) + #t + #f)) + (('item _ ('value value)) + value))) + +(define (serialize-boolean bool) + "Return the serialized format expected by dbus-send for BOOL." + (format #f "boolean:~:[false~;true~]" bool)) + +(define (dict->alist dict-parse-tree) + "Translate a dict parse tree to an alist." + (define (tuples->alist tuples) + (map (lambda (x) (apply cons x)) tuples)) + + (match dict-parse-tree + ('dict + '()) + (('dict ('dict-entry keys values) ...) + (let ((keys* (map deserialize-item keys)) + (values* (map deserialize-item values))) + (tuples->alist (zip keys* values*)))))) + +(define (array->list array-parse-tree) + "Translate an array parse tree to a list." + (match array-parse-tree + ('array + '()) + (('array items ...) + (map deserialize-item items)))) + + +;;; +;;; Low-level, D-Bus-related procedures. +;;; + +;;; The following parameters are used in the jami-service-type service +;;; definition to conveniently customize the behavior of the send-dbus helper, +;;; even when called indirectly. +(define %send-dbus-binary (make-parameter "dbus-send")) +(define %send-dbus-bus (make-parameter #f)) +(define %send-dbus-user (make-parameter #f)) +(define %send-dbus-group (make-parameter #f)) +(define %send-dbus-debug (make-parameter #f)) + +(define* (send-dbus #:key service path interface method + bus + dbus-send + user group + timeout + arguments) + "Return the response of DBUS-SEND, else raise an error. Unless explicitly +provided, DBUS-SEND takes the value of the %SEND-DBUS-BINARY parameter. BUS +can be used to specify the bus address, such as 'unix:path=/var/run/jami/bus'. +Alternatively, the %SEND-DBUS-BUS parameter can be used. ARGUMENTS can be +used to pass input values to a D-Bus method call. TIMEOUT is the amount of +time to wait for a reply in milliseconds before giving up with an error. USER +and GROUP allow choosing under which user/group the DBUS-SEND command is +executed. Alternatively, the %SEND-DBUS-USER and %SEND-DBUS-GROUP parameters +can be used instead." + (let* ((command `(,(if dbus-send + dbus-send + (%send-dbus-binary)) + ,@(if (or bus (%send-dbus-bus)) + (list (string-append "--bus=" + (or bus (%send-dbus-bus)))) + '()) + "--print-reply" + ,@(if timeout + (list (format #f "--reply-timeout=~d" timeout)) + '()) + ,(string-append "--dest=" service) ;e.g., cx.ring.Ring + ,path ;e.g., /cx/ring/Ring/ConfigurationManager + ,(string-append interface "." method) + ,@(or arguments '()))) + (temp-port (mkstemp! (string-copy "/tmp/dbus-send-output-XXXXXXX"))) + (temp-file (port-filename temp-port))) + (dynamic-wind + (lambda () + (let* ((uid (or (and=> (or user (%send-dbus-user)) + (compose passwd:uid getpwnam)) -1)) + (gid (or (and=> (or group (%send-dbus-group)) + (compose group:gid getgrnam)) -1))) + (chown temp-port uid gid))) + (lambda () + (let ((pid (fork+exec-command command + #:user (or user (%send-dbus-user)) + #:group (or group (%send-dbus-group)) + #:log-file temp-file))) + (match (waitpid pid) + ((_ . status) + (let ((exit-status (status:exit-val status)) + (output (call-with-port temp-port get-string-all))) + (if (= 0 exit-status) + output + (error "the send-dbus command exited with: " + command exit-status output))))))) + (lambda () + (false-if-exception (delete-file temp-file)))))) + +(define (parse-account-ids reply) + "Return the Jami account IDs from REPLY, which is assumed to be the output +of the Jami D-Bus `getAccountList' method." + (array->list (parse-dbus-reply reply))) + +(define (parse-account-details reply) + "Parse REPLY, which is assumed to be the output of the Jami D-Bus +`getAccountDetails' method, and return its content as an alist." + (dict->alist (parse-dbus-reply reply))) + +(define (parse-contacts reply) + "Parse REPLY, which is assumed to be the output of the Jamid D-Bus +`getContacts' method, and return its content as an alist." + (match (parse-dbus-reply reply) + ('array + '()) + (('array dicts ...) + (map dict->alist dicts)))) + + +;;; +;;; Higher-level, D-Bus-related procedures. +;;; + +(define (validate-fingerprint fingerprint) + "Validate that fingerprint is 40 characters long." + (unless (account-fingerprint? fingerprint) + (error "Account fingerprint is not valid:" fingerprint))) + +(define (dbus-available-services) + "Return the list of available (acquired) D-Bus services." + (let ((reply (parse-dbus-reply + (send-dbus #:service "org.freedesktop.DBus" + #:path "/org/freedesktop/DBus" + #:interface "org.freedesktop.DBus" + #:method "ListNames")))) + ;; Remove entries such as ":1.7". + (remove (cut string-prefix? ":" <>) + (array->list reply)))) + +(define (dbus-service-available? service) + "Predicate to check for the D-Bus SERVICE availability." + (member service (dbus-available-services))) + +(define* (send-dbus/configuration-manager #:key method arguments timeout) + "Query the Jami D-Bus ConfigurationManager service." + (send-dbus #:service "cx.ring.Ring" + #:path "/cx/ring/Ring/ConfigurationManager" + #:interface "cx.ring.Ring.ConfigurationManager" + #:method method + #:arguments arguments + #:timeout timeout)) + +;;; The following methods are for internal use; they make use of the account +;;; ID, an implementation detail of Jami the user should not need to be +;;; concerned with. +(define (get-account-ids) + "Return the available Jami account identifiers (IDs). Account IDs are an +implementation detail used to identify the accounts in Jami." + (parse-account-ids + (send-dbus/configuration-manager #:method "getAccountList"))) + +(define (id->account-details id) + "Retrieve the account data associated with the given account ID." + (parse-account-details + (send-dbus/configuration-manager + #:method "getAccountDetails" + #:arguments (list (string-append "string:" id))))) + +(define (id->volatile-account-details id) + "Retrieve the account data associated with the given account ID." + (parse-account-details + (send-dbus/configuration-manager + #:method "getVolatileAccountDetails" + #:arguments (list (string-append "string:" id))))) + +(define (id->account id) + "Retrieve the complete account data associated with the given account ID." + (append (id->volatile-account-details id) + (id->account-details id))) + +(define %username-to-id-cache #f) + +(define (invalidate-username-to-id-cache!) + (set! %username-to-id-cache #f)) + +(define (username->id username) + "Return the first account ID corresponding to USERNAME." + (unless (assoc-ref %username-to-id-cache username) + (set! %username-to-id-cache + (append-map + (lambda (id) + (let* ((account (id->account id)) + (username (assoc-ref account "Account.username")) + (registered-name (assoc-ref account + "Account.registeredName"))) + `(,@(if username + (list (cons username id)) + '()) + ,@(if registered-name + (list (cons registered-name id)) + '())))) + (get-account-ids)))) + (or (assoc-ref %username-to-id-cache username) + (let ((message (format #f "Could not retrieve a local account ID\ + for ~:[username~;fingerprint~]" (account-fingerprint? username)))) + (error message username)))) + +(define (account->username account) + "Return USERNAME, the registered username associated with ACCOUNT, else its +public key fingerprint." + (or (assoc-ref account "Account.registeredName") + (assoc-ref account "Account.username"))) + +(define (id->username id) + "Return USERNAME, the registered username associated with ID, else its +public key fingerprint, else #f." + (account->username (id->account id))) + +(define (get-accounts) + "Return the list of all accounts, as a list of alists." + (map id->account (get-account-ids))) + +(define (get-usernames) + "Return the list of the usernames associated with the present accounts." + (map account->username (get-accounts))) + +(define (username->account username) + "Return the first account associated with USERNAME, else #f. +USERNAME can be either the account 40 characters public key fingerprint or a +registered username." + (find (lambda (account) + (member username + (list (assoc-ref account "Account.username") + (assoc-ref account "Account.registeredName")))) + (get-accounts))) + +(define (add-account archive) + "Import the Jami account ARCHIVE and return its account ID. The archive +should *not* be encrypted with a password. Return the username associated +with the account." + (invalidate-username-to-id-cache!) + (let ((reply (send-dbus/configuration-manager + #:method "addAccount" + #:arguments (list (string-append + "dict:string:string:Account.archivePath," + archive + ",Account.type,RING"))))) + ;; The account information takes some time to be populated. + (let ((id (deserialize-item (parse-dbus-reply reply)))) + (with-retries 20 1 + (let ((username (id->username id))) + (if (string-null? username) + #f + username)))))) + +(define (remove-account username) + "Delete the Jami account associated with USERNAME, the account 40 characters +fingerprint or a registered username." + (let ((id (username->id username))) + (send-dbus/configuration-manager + #:method "removeAccount" + #:arguments (list (string-append "string:" id)))) + (invalidate-username-to-id-cache!)) + +(define* (username->contacts username) + "Return the contacts associated with the account of USERNAME as two values; +the first one being the regular contacts and the second one the banned +contacts. USERNAME can be either the account 40 characters public key +fingerprint or a registered username. The contacts returned are represented +using their 40 characters fingerprint." + (let* ((id (username->id username)) + (reply (send-dbus/configuration-manager + #:method "getContacts" + #:arguments (list (string-append "string:" id)))) + (all-contacts (parse-contacts reply)) + (banned? (lambda (contact) + (and=> (assoc-ref contact "banned") + (cut string=? "true" <>)))) + (banned (filter banned? all-contacts)) + (not-banned (filter (negate banned?) all-contacts)) + (fingerprint (cut assoc-ref <> "id"))) + (values (map fingerprint not-banned) + (map fingerprint banned)))) + +(define* (remove-contact contact username #:key ban?) + "Remove CONTACT, the 40 characters public key fingerprint of a contact, from +the account associated with USERNAME (either a fingerprint or a registered +username). When BAN? is true, also mark the contact as banned." + (validate-fingerprint contact) + (let ((id (username->id username))) + (send-dbus/configuration-manager + #:method "removeContact" + #:arguments (list (string-append "string:" id) + (string-append "string:" contact) + (serialize-boolean ban?))))) + +(define (add-contact contact username) + "Add CONTACT, the 40 characters public key fingerprint of a contact, to the +account of USERNAME (either a fingerprint or a registered username)." + (validate-fingerprint contact) + (let ((id (username->id username))) + (send-dbus/configuration-manager + #:method "addContact" + #:arguments (list (string-append "string:" id) + (string-append "string:" contact))))) + +(define* (set-account-details details username #:key timeout) + "Set DETAILS, an alist containing the key value pairs to set for the account +of USERNAME, a registered username or account fingerprint. The value of the +parameters not provided are unchanged. TIMEOUT is a value in milliseconds to +pass to the `send-dbus/configuration-manager' procedure." + (let* ((id (username->id username)) + (current-details (id->account-details id)) + (updated-details (map (match-lambda + ((key . value) + (or (and=> (assoc-ref details key) + (cut cons key <>)) + (cons key value)))) + current-details)) + ;; dbus-send does not permit sending null strings (it throws a + ;; "malformed dictionary" error). Luckily they seem to have the + ;; semantic of "default account value" in Jami; so simply drop them. + (updated-details* (remove (match-lambda + ((_ . value) + (string-null? value))) + updated-details))) + (send-dbus/configuration-manager + #:timeout timeout + #:method "setAccountDetails" + #:arguments + (list (string-append "string:" id) + (string-append "dict:string:string:" + (string-join (alist->list updated-details*) + ",")))))) + +(define (set-all-moderators enabled? username) + "Set the 'AllModerators' property to enabled? for the account of USERNAME, a +registered username or account fingerprint." + (let ((id (username->id username))) + (send-dbus/configuration-manager + #:method "setAllModerators" + #:arguments + (list (string-append "string:" id) + (serialize-boolean enabled?))))) + +(define (username->all-moderators? username) + "Return the 'AllModerators' property for the account of USERNAME, a +registered username or account fingerprint." + (let* ((id (username->id username)) + (reply (send-dbus/configuration-manager + #:method "isAllModerators" + #:arguments + (list (string-append "string:" id))))) + (deserialize-item (parse-dbus-reply reply)))) + +(define (username->moderators username) + "Return the moderators for the account of USERNAME, a registered username or +account fingerprint." + (let* ((id (username->id username)) + (reply (send-dbus/configuration-manager + #:method "getDefaultModerators" + #:arguments + (list (string-append "string:" id))))) + (array->list (parse-dbus-reply reply)))) + +(define (set-moderator contact enabled? username) + "Set the moderator flag to ENABLED? for CONTACT, the 40 characters public +key fingerprint of a contact for the account of USERNAME, a registered +username or account fingerprint." + (validate-fingerprint contact) + (let* ((id (username->id username))) + (send-dbus/configuration-manager #:method "setDefaultModerator" + #:arguments + (list (string-append "string:" id) + (string-append "string:" contact) + (serialize-boolean enabled?))))) + +(define (disable-account username) + "Disable the account known by USERNAME, a registered username or account +fingerprint." + (set-account-details '(("Account.enable" . "false")) username + ;; Waiting for the reply on this command takes a very + ;; long time that trips the default D-Bus timeout value + ;; (25 s), for some reason. + #:timeout 60000)) + +(define (enable-account username) + "Enable the account known by USERNAME, a registered username or account +fingerprint." + (set-account-details '(("Account.enable" . "true")) username)) + + +;;; +;;; Presentation procedures. +;;; + +(define (.->_ text) + "Map each period character to underscore characters." + (string-map (match-lambda + (#\. #\_) + (c c)) + text)) + +(define (account-details->recutil account-details) + "Serialize the account-details alist into a recutil string. Period +characters in the keys are normalized to underscore to meet Recutils' format +requirements." + (define (pair->recutil-property pair) + (match pair + ((key . value) + (string-append (.->_ key) ": " value)))) + + (define sorted-account-details + ;; Have the account username, display name and alias appear first, for + ;; convenience. + (let ((first-items '("Account.username" + "Account.displayName" + "Account.alias"))) + (append (map (cut assoc <> account-details) first-items) + (fold alist-delete account-details first-items)))) + + (string-join (map pair->recutil-property sorted-account-details) "\n")) + +;; Local Variables: +;; eval: (put 'with-retries 'scheme-indent-function 2) +;; End: diff --git a/gnu/local.mk b/gnu/local.mk index 9e8f2d702d..e8494806fd 100644 --- a/gnu/local.mk +++ b/gnu/local.mk @@ -685,6 +685,7 @@ GNU_SYSTEM_MODULES = \ %D%/build/chromium-extension.scm \ %D%/build/cross-toolchain.scm \ %D%/build/image.scm \ + %D%/build/jami-service.scm \ %D%/build/file-systems.scm \ %D%/build/hurd-boot.scm \ %D%/build/install.scm \ @@ -722,6 +723,7 @@ GNU_SYSTEM_MODULES = \ %D%/tests/security-token.scm \ %D%/tests/singularity.scm \ %D%/tests/ssh.scm \ + %D%/tests/telephony.scm \ %D%/tests/version-control.scm \ %D%/tests/virtualization.scm \ %D%/tests/web.scm @@ -787,7 +789,8 @@ dist_installer_DATA = \ MODULES_NOT_COMPILED += \ %D%/build/locale.scm \ %D%/build/shepherd.scm \ - %D%/build/svg.scm + %D%/build/svg.scm \ + %D%/tests/data/jami-dummy-account.dat patchdir = $(guilemoduledir)/%D%/packages/patches dist_patch_DATA = \ diff --git a/gnu/services/telephony.scm b/gnu/services/telephony.scm index e1259cc2df..fd90840324 100644 --- a/gnu/services/telephony.scm +++ b/gnu/services/telephony.scm @@ -1,5 +1,6 @@ ;;; GNU Guix --- Functional package management for GNU ;;; Copyright © 2017 nee +;;; Copyright © 2021 Maxim Cournoyer ;;; ;;; This file is part of GNU Guix. ;;; @@ -17,16 +18,45 @@ ;;; along with GNU Guix. If not, see . (define-module (gnu services telephony) - #:use-module (gnu services) + #:use-module ((gnu build jami-service) #:select (account-fingerprint?)) + #:use-module ((gnu services) #:hide (delete)) + #:use-module (gnu services configuration) #:use-module (gnu services shepherd) #:use-module (gnu system shadow) #:use-module (gnu packages admin) + #:use-module (gnu packages certs) + #:use-module (gnu packages glib) + #:use-module (gnu packages jami) #:use-module (gnu packages telephony) #:use-module (guix records) + #:use-module (guix modules) + #:use-module (guix packages) #:use-module (guix gexp) #:use-module (srfi srfi-1) + #:use-module (srfi srfi-2) + #:use-module (srfi srfi-26) + #:use-module (ice-9 format) #:use-module (ice-9 match) - #:export (murmur-configuration + #:export (jami-account + jami-account-archive + jami-account-allowed-contacts + jami-account-moderators + jami-account-rendezvous-point? + jami-account-discovery? + jami-account-bootstrap-uri + jami-account-name-server-uri + + jami-configuration + jami-configuration-jamid + jami-configuration-dbus + jami-configuration-enable-logging? + jami-configuration-debug? + jami-configuration-auto-answer? + jami-configuration-accounts + + jami-service-type + + murmur-configuration make-murmur-configuration murmur-configuration? murmur-configuration-package @@ -74,6 +104,652 @@ murmur-service-type)) + +;;; +;;; Jami daemon. +;;; + +;;; XXX: Passing a computed-file object as the account is used for tests. +(define (string-or-computed-file? val) + (or (string? val) + (computed-file? val))) + +(define (string-list? val) + (and (list? val) + (and-map string? val))) + +(define (account-fingerprint-list? val) + (and (list? val) + (and-map account-fingerprint? val))) + +(define-maybe string-list) + +(define-maybe/no-serialization account-fingerprint-list) + +(define-maybe boolean) + +(define-maybe string) + +;;; The following serializers are used to derive an account details alist from +;;; a record. +(define (serialize-string-list _ val) + (string-join val ";")) + +(define (serialize-boolean _ val) + (format #f "~:[false~;true~]" val)) + +(define (serialize-string _ val) + val) + +;;; Note: Serialization is used to produce an account details alist that can +;;; be passed to the SET-ACCOUNT-DETAILS procedure. Fields that do not map to +;;; a Jami account 'detail' should have their serialization disabled via the +;;; 'empty-serializer' procedure. +(define-configuration jami-account + (archive + (string-or-computed-file) + "The account archive (backup) file name of the account. This is used to +provision the account when the service starts. The account archive should +@emph{not} be encrypted. It is highly recommended to make it readable only to +the @samp{root} user (i.e., not in the store), to guard against leaking the +secret key material of the Jami account it contains." + empty-serializer) + (allowed-contacts + (maybe-account-fingerprint-list 'disabled) + "The list of allowed contacts for the account, entered as their 40 +characters long fingerprint. Messages or calls from accounts not in that list +will be rejected. When unspecified, the configuration of the account archive +is used as-is with respect to contacts and public inbound calls/messaging +allowance, which typically defaults to allow any contact to communicate with +the account." + empty-serializer) + (moderators + (maybe-account-fingerprint-list 'disabled) + "The list of contacts that should have moderation privileges (to ban, mute, +etc. other users) in rendezvous conferences, entered as their 40 characters +long fingerprint. When unspecified, the configuration of the account archive +is used as-is with respect to moderation, which typically defaults to allow +anyone to moderate." + empty-serializer) + ;; The serializable fields below are to be set with set-account-details. + (rendezvous-point? + (maybe-boolean 'disabled) + "Whether the account should operate in the rendezvous mode. In this mode, +all the incoming audio/video calls are mixed into a conference. When left +unspecified, the value from the account archive prevails.") + (peer-discovery? + (maybe-boolean 'disabled) + "Whether peer discovery should be enabled. Peer discovery is used to +discover other OpenDHT nodes on the local network, which can be useful to +maintain communication between devices on such network even when the +connection to the the Internet has been lost. When left unspecified, the +value from the account archive prevails.") + (bootstrap-hostnames + (maybe-string-list 'disabled) + "A list of hostnames or IPs pointing to OpenDHT nodes, that should be used +to initially join the OpenDHT network. When left unspecified, the value from +the account archive prevails.") + (name-server-uri + (maybe-string 'disabled) + "The URI of the name server to use, that can be used to retrieve the +account fingerprint for a registered username.")) + +(define (jami-account->alist jami-account-object) + "Serialize the JAMI-ACCOUNT object as an alist suitable to be passed to +SET-ACCOUNT-DETAILS." + (define (field-name->account-detail name) + (match name + ('rendezvous-point? "Account.rendezVous") + ('peer-discovery? "Account.peerDiscovery") + ('bootstrap-hostnames "Account.hostname") + ('name-server-uri "RingNS.uri") + (_ #f))) + + (filter-map (lambda (field) + (and-let* ((name (field-name->account-detail + (configuration-field-name field))) + (value ((configuration-field-serializer field) + name ((configuration-field-getter field) + jami-account-object))) + ;; The define-maybe default serializer produces an + ;; empty string for the 'disabled value. + (value* (if (string-null? value) + #f + value))) + (cons name value*))) + jami-account-fields)) + +(define (jami-account-list? val) + (and (list? val) + (and-map jami-account? val))) + +(define-maybe/no-serialization jami-account-list) + +(define-configuration/no-serialization jami-configuration + (jamid + (package libring) + "The Jami daemon package to use.") + (dbus + (package dbus) + "The D-Bus package to use to start the required D-Bus session.") + (nss-certs + (package nss-certs) + "The nss-certs package to use to provide TLS certificates.") + (enable-logging? + (boolean #t) + "Whether to enable logging to syslog.") + (debug? + (boolean #f) + "Whether to enable debug level messages.") + (auto-answer? + (boolean #f) + "Whether to force automatic answer to incoming calls.") + (accounts + (maybe-jami-account-list 'disabled) + "A list of Jami accounts to be (re-)provisioned every time the Jami daemon +service starts. When providing this field, the account directories under +@file{/var/lib/jami/} are recreated every time the service starts, ensuring a +consistent state.")) + +(define %jami-accounts + (list (user-group (name "jami") (system? #t)) + (user-account + (name "jami") + (group "jami") + (system? #t) + (comment "Jami daemon user") + (home-directory "/var/lib/jami")))) + +(define (jami-configuration->command-line-arguments config) + "Derive the command line arguments to used to launch the Jami daemon from +CONFIG, a object." + (match-record config + (jamid dbus enable-logging? debug? auto-answer?) + `(,(file-append jamid "/lib/ring/dring") + "--persistent" ;stay alive after client quits + ,@(if enable-logging? + '() ;logs go to syslog by default + (list "--console")) ;else stdout/stderr + ,@(if debug? + (list "--debug") + '()) + ,@(if auto-answer? + (list "--auto-answer") + '())))) + +(define (jami-dbus-session-activation config) + "Create a directory to hold the Jami D-Bus session socket." + (with-imported-modules (source-module-closure '((gnu build activation))) + #~(begin + (use-modules (gnu build activation)) + (let ((user (getpwnam "jami"))) + (mkdir-p/perms "/var/run/jami" user #o700))))) + +(define (jami-shepherd-services config) + "Return a running the Jami daemon." + (let* ((jamid (jami-configuration-jamid config)) + (nss-certs (jami-configuration-nss-certs config)) + (dbus (jami-configuration-dbus config)) + (dbus-daemon (file-append dbus "/bin/dbus-daemon")) + (dbus-send (file-append dbus "/bin/dbus-send")) + (accounts (jami-configuration-accounts config)) + (declarative-mode? (not (eq? 'disabled accounts)))) + + (with-imported-modules (source-module-closure + '((gnu build jami-service) + (gnu build shepherd) + (gnu system file-systems))) + + (define list-accounts-action + (shepherd-action + (name 'list-accounts) + (documentation "List the available Jami accounts. Return the account +details alists keyed by their account username.") + (procedure + #~(lambda _ + (parameterize ((%send-dbus-binary #$dbus-send) + (%send-dbus-bus "unix:path=/var/run/jami/bus") + (%send-dbus-user "jami") + (%send-dbus-group "jami")) + ;; Print the accounts summary or long listing, according to + ;; user-provided option. + (let* ((usernames (get-usernames)) + (accounts (map-in-order username->account usernames))) + (match accounts + (() ;empty list + (format #t "There is no Jami account available.~%")) + ((one two ...) + (format #t "The following Jami accounts are available:~%") + (for-each + (lambda (account) + (define fingerprint (assoc-ref account + "Account.username")) + (define human-friendly-name + (or (assoc-ref account + "Account.registeredName") + (assoc-ref account + "Account.displayName") + (assoc-ref account + "Account.alias"))) + (define disabled? + (and=> (assoc-ref account "Account.enable") + (cut string=? "false" <>))) + + (format #t " - ~a~@[ (~a)~] ~:[~;[disabled]~]~%" + fingerprint human-friendly-name disabled?)) + accounts) + (display "\n"))) + ;; Return the account-details-list alist. + (map cons usernames accounts))))))) + + (define list-account-details-action + (shepherd-action + (name 'list-account-details) + (documentation "Display the account details of the available Jami +accounts in the @code{recutils} format. Return the account details alists +keyed by their account username.") + (procedure + #~(lambda _ + (parameterize ((%send-dbus-binary #$dbus-send) + (%send-dbus-bus "unix:path=/var/run/jami/bus") + (%send-dbus-user "jami") + (%send-dbus-group "jami")) + (let* ((usernames (get-usernames)) + (accounts (map-in-order username->account usernames))) + (for-each (lambda (account) + (display (account-details->recutil account)) + (display "\n\n")) + accounts) + (map cons usernames accounts))))))) + + (define list-contacts-action + (shepherd-action + (name 'list-contacts) + (documentation "Display the contacts for each Jami account. Return +an alist containing the contacts keyed by the account usernames.") + (procedure + #~(lambda _ + (parameterize ((%send-dbus-binary #$dbus-send) + (%send-dbus-bus "unix:path=/var/run/jami/bus") + (%send-dbus-user "jami") + (%send-dbus-group "jami")) + (let* ((usernames (get-usernames)) + (contacts (map-in-order username->contacts usernames))) + (for-each (lambda (username contacts) + (format #t "Contacts for account ~a:~%" + username) + (format #t "~{ - ~a~%~}~%" contacts)) + usernames contacts) + (map cons usernames contacts))))))) + + (define list-moderators-action + (shepherd-action + (name 'list-moderators) + (documentation "Display the moderators for each Jami account. Return +an alist containing the moderators keyed by the account usernames.") + (procedure + #~(lambda _ + (parameterize ((%send-dbus-binary #$dbus-send) + (%send-dbus-bus "unix:path=/var/run/jami/bus") + (%send-dbus-user "jami") + (%send-dbus-group "jami")) + (let* ((usernames (get-usernames)) + (moderators (map-in-order username->moderators + usernames))) + (for-each + (lambda (username moderators) + (if (username->all-moderators? username) + (format #t "Anyone can moderate for account ~a~%" + username) + (begin + (format #t "Moderators for account ~a:~%" username) + (format #t "~{ - ~a~%~}~%" moderators)))) + usernames moderators) + (map cons usernames moderators))))))) + + (define add-moderator-action + (shepherd-action + (name 'add-moderator) + (documentation "Add a moderator for a given Jami account. The +MODERATOR contact must be given as its 40 characters fingerprint, while the +Jami account can be provided as its registered USERNAME or fingerprint. + +@example +herd add-moderator jami 1dbcb0f5f37324228235564b79f2b9737e9a008f username +@end example + +Return the moderators for the account known by USERNAME.") + (procedure + #~(lambda (_ moderator username) + (parameterize ((%send-dbus-binary #$dbus-send) + (%send-dbus-bus "unix:path=/var/run/jami/bus") + (%send-dbus-user "jami") + (%send-dbus-group "jami")) + (set-all-moderators #f username) + (add-contact moderator username) + (set-moderator moderator #t username) + (username->moderators username)))))) + + (define ban-contact-action + (shepherd-action + (name 'ban-contact) + (documentation "Ban a contact for a given or all Jami accounts, and +clear their moderator flag. The CONTACT must be given as its 40 characters +fingerprint, while the Jami account can be provided as its registered USERNAME +or fingerprint, or omitted. When the account is omitted, CONTACT is banned +from all accounts. + +@example +herd ban-contact jami 1dbcb0f5f37324228235564b79f2b9737e9a008f [username] +@end example") + (procedure + #~(lambda* (_ contact #:optional username) + (parameterize ((%send-dbus-binary #$dbus-send) + (%send-dbus-bus "unix:path=/var/run/jami/bus") + (%send-dbus-user "jami") + (%send-dbus-group "jami")) + (let ((usernames (or (and=> username list) + (get-usernames)))) + (for-each (lambda (username) + (set-moderator contact #f username) + (remove-contact contact username #:ban? #t)) + usernames))))))) + + (define list-banned-contacts-action + (shepherd-action + (name 'list-banned-contacts) + (documentation "List the banned contacts for each accounts. Return +an alist of the banned contacts, keyed by the account usernames.") + (procedure + #~(lambda _ + (parameterize ((%send-dbus-binary #$dbus-send) + (%send-dbus-bus "unix:path=/var/run/jami/bus") + (%send-dbus-user "jami") + (%send-dbus-group "jami")) + + (define banned-contacts + (let ((usernames (get-usernames))) + (map cons usernames + (map-in-order (lambda (x) + (receive (_ banned) + (username->contacts x) + banned)) + usernames)))) + + (for-each (match-lambda + ((username . banned) + (unless (null? banned) + (format #t "Banned contacts for account ~a:~%" + username) + (format #t "~{ - ~a~%~}~%" banned)))) + banned-contacts) + banned-contacts))))) + + (define enable-account-action + (shepherd-action + (name 'enable-account) + (documentation "Enable an account. It takes USERNAME as an argument, +either a registered username or the fingerprint of the account.") + (procedure + #~(lambda (_ username) + (parameterize ((%send-dbus-binary #$dbus-send) + (%send-dbus-bus "unix:path=/var/run/jami/bus") + (%send-dbus-user "jami") + (%send-dbus-group "jami")) + (enable-account username)))))) + + (define disable-account-action + (shepherd-action + (name 'disable-account) + (documentation "Disable an account. It takes USERNAME as an +argument, either a registered username or the fingerprint of the account.") + (procedure + #~(lambda (_ username) + (parameterize ((%send-dbus-binary #$dbus-send) + (%send-dbus-bus "unix:path=/var/run/jami/bus") + (%send-dbus-user "jami") + (%send-dbus-group "jami")) + (disable-account username)))))) + + (list (shepherd-service + (documentation "Run a D-Bus session for the Jami daemon.") + (provision '(jami-dbus-session)) + (modules `((gnu build shepherd) + (gnu build jami-service) + (gnu system file-systems) + ,@%default-modules)) + ;; The requirement on dbus-system is to ensure other required + ;; activation for D-Bus, such as a /etc/machine-id file. + (requirement '(dbus-system syslogd)) + (start + #~(lambda args + (define pid + ((make-forkexec-constructor/container + (list #$dbus-daemon "--session" + "--address=unix:path=/var/run/jami/bus" + "--nofork" "--syslog-only" "--nopidfile") + #:mappings (list (file-system-mapping + (source "/dev/log") ;for syslog + (target source)) + (file-system-mapping + (source "/var/run/jami") + (target source) + (writable? #t))) + #:user "jami" + #:group "jami" + #:environment-variables + ;; This is so that the cx.ring.Ring service D-Bus + ;; definition is found by dbus-send. + (list (string-append "XDG_DATA_DIRS=" + #$jamid "/share"))))) + + ;; XXX: This manual synchronization probably wouldn't be + ;; needed if we were using a PID file, but providing it via a + ;; customized config file with would not override + ;; the one inherited from the base config of D-Bus. + (let ((sock (socket PF_UNIX SOCK_STREAM 0))) + (with-retries 20 1 (catch 'system-error + (lambda () + (connect sock AF_UNIX + "/var/run/jami/bus") + (close-port sock) + #t) + (lambda args + #f)))) + + pid)) + (stop #~(make-kill-destructor))) + + (shepherd-service + (documentation "Run the Jami daemon.") + (provision '(jami)) + (actions (list list-accounts-action + list-account-details-action + list-contacts-action + list-moderators-action + add-moderator-action + ban-contact-action + list-banned-contacts-action + enable-account-action + disable-account-action)) + (requirement '(jami-dbus-session)) + (modules `((ice-9 format) + (ice-9 ftw) + (ice-9 match) + (ice-9 receive) + (srfi srfi-1) + (srfi srfi-26) + (gnu build jami-service) + (gnu build shepherd) + (gnu system file-systems) + ,@%default-modules)) + (start + #~(lambda args + (define (delete-file-recursively/safe file) + ;; Ensure we're not deleting things outside of + ;; /var/lib/jami. This prevents a possible attack in case + ;; the daemon is compromised and an attacker gains write + ;; access to /var/lib/jami. + (let ((parent-directory (dirname file))) + (if (eq? 'symlink (stat:type (stat parent-directory))) + (error "abnormality detected; unexpected symlink found at" + parent-directory) + (delete-file-recursively file)))) + + (when #$declarative-mode? + ;; Clear the Jami configuration and accounts, to enforce the + ;; declared state. + (catch #t + (lambda () + (for-each (cut delete-file-recursively/safe <>) + '("/var/lib/jami/.cache/jami" + "/var/lib/jami/.config/jami" + "/var/lib/jami/.local/share/jami" + "/var/lib/jami/accounts"))) + (lambda args + #t)) + ;; Copy the Jami account archives from somewhere readable + ;; by root to a place only the jami user can read. + (let* ((accounts-dir "/var/lib/jami/accounts/") + (pwd (getpwnam "jami")) + (user (passwd:uid pwd)) + (group (passwd:gid pwd))) + (mkdir-p accounts-dir) + (chown accounts-dir user group) + (for-each (lambda (f) + (let ((dest (string-append accounts-dir + (basename f)))) + (copy-file f dest) + (chown dest user group))) + '#$(and declarative-mode? + (map jami-account-archive accounts))))) + + ;; Start the daemon. + (define daemon-pid + ((make-forkexec-constructor/container + '#$(jami-configuration->command-line-arguments config) + #:mappings + (list (file-system-mapping + (source "/dev/log") ;for syslog + (target source)) + (file-system-mapping + (source "/var/lib/jami") + (target source) + (writable? #t)) + (file-system-mapping + (source "/var/run/jami") + (target source) + (writable? #t)) + ;; Expose TLS certificates for GnuTLS. + (file-system-mapping + (source #$(file-append nss-certs "/etc/ssl/certs")) + (target "/etc/ssl/certs"))) + #:user "jami" + #:group "jami" + #:environment-variables + (list (string-append "DBUS_SESSION_BUS_ADDRESS=" + "unix:path=/var/run/jami/bus") + ;; Expose TLS certificates for OpenSSL. + "SSL_CERT_DIR=/etc/ssl/certs")))) + + (parameterize ((%send-dbus-binary #$dbus-send) + (%send-dbus-bus "unix:path=/var/run/jami/bus") + (%send-dbus-user "jami") + (%send-dbus-group "jami")) + + ;; Wait until the service name has been acquired by D-Bus. + (with-retries 20 1 + (dbus-service-available? "cx.ring.Ring")) + + (when #$declarative-mode? + ;; Provision the accounts via the D-Bus API of the daemon. + (let* ((jami-account-archives + (map (cut string-append + "/var/lib/jami/accounts/" <>) + (scandir "/var/lib/jami/accounts/" + (lambda (f) + (not (member f '("." ".."))))))) + (usernames (map-in-order (cut add-account <>) + jami-account-archives))) + + (define (archive-name->username archive) + (list-ref + usernames + (list-index (lambda (f) + (string-suffix? (basename archive) f)) + jami-account-archives))) + + (for-each + (lambda (archive allowed-contacts moderators + account-details) + (let ((username (archive-name->username + archive))) + (when (not (eq? 'disabled allowed-contacts)) + ;; Reject calls from unknown contacts. + (set-account-details + '(("DHT.PublicInCalls" . "false")) username) + ;; Remove all contacts. + (for-each (cut remove-contact <> username) + (username->contacts username)) + ;; Add allowed ones. + (for-each (cut add-contact <> username) + allowed-contacts)) + (when (not (eq? 'disabled moderators)) + ;; Disable the 'AllModerators' property. + (set-all-moderators #f username) + ;; Remove all moderators. + (for-each (cut set-moderator <> #f username) + (username->moderators username)) + ;; Add declared moderators. + (for-each (cut set-moderator <> #t username) + moderators)) + ;; Set the various account parameters. + (set-account-details account-details username))) + '#$(and declarative-mode? + (map-in-order (cut jami-account-archive <>) + accounts)) + '#$(and declarative-mode? + (map-in-order + (cut jami-account-allowed-contacts <>) + accounts)) + '#$(and declarative-mode? + (map-in-order (cut jami-account-moderators <>) + accounts)) + '#$(and declarative-mode? + (map-in-order jami-account->alist accounts)))))) + + ;; Finally, return the PID of the daemon process. + daemon-pid)) + (stop + #~(lambda (pid . args) + (kill pid SIGKILL) + ;; Wait for the process to exit; this prevents overlapping + ;; processes when issuing 'herd restart'. + (waitpid pid) + #f))))))) + +(define jami-service-type + (service-type + (name 'jami) + (default-value (jami-configuration)) + (extensions + (list (service-extension shepherd-root-service-type + jami-shepherd-services) + (service-extension account-service-type + (const %jami-accounts)) + (service-extension activation-service-type + jami-dbus-session-activation))) + (description "Run the Jami daemon (@command{dring}). This service is +geared toward the use case of hosting Jami rendezvous points over a headless +server. If you use Jami on your local machine, you may prefer to setup a user +Shepherd service for it instead; this way, the daemon will be shared via your +normal user D-Bus session bus."))) + + +;;; +;;; Murmur. +;;; + ;; https://github.com/mumble-voip/mumble/blob/master/scripts/murmur.ini (define-record-type* murmur-configuration @@ -305,3 +981,7 @@ suite.") (service-extension account-service-type murmur-accounts))) (default-value (murmur-configuration)))) + +;; Local Variables: +;; eval: (put 'with-retries 'scheme-indent-function 2) +;; End: diff --git a/gnu/tests/data/jami-dummy-account.dat b/gnu/tests/data/jami-dummy-account.dat new file mode 100644 index 0000000000..0e908396ca --- /dev/null +++ b/gnu/tests/data/jami-dummy-account.dat @@ -0,0 +1,392 @@ +;;; -*- mode: scheme; -*- +;;; JSON extracted from an actual Jami account and processed with +;;; Emacs/guile-json. +(define %jami-account-content-sexp + '(("RINGCAKEY" . "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUpRd0lCQURBTkJna3F\ +oa2lHOXcwQkFRRUZBQVNDQ1Mwd2dna3BBZ0VBQW9JQ0FRQzBxWUozSkYvTzhQRGEKRnUwRnpRcHBCaD\ +gybGJMdURrNTlVU0I0MUJSaS9kdDZGV1BRN29YOVpsY25vNGZzM2dmUHQ0dU1hRVBkVFBGKwowbGN2Q\ +jc2cytQTEFlcjlOZGpVQzQ2ZXp0UnNiNE9aQXc4ZUk1M3EwSU04QWJFd0o0ZjllLzBmQUFueHgrK3Qw\ +CmZDeGV1YTBUaVBqVHBpZVJMNmpwQkd5UGI2Qk5pU2ViTkZCNzJOMTBnbzI4REVQYlhkNE9CNkN1blZ\ +5RGZKSU0KOC9PRy8rMndNamI4WkRwT3JrYy93U2ZHbnQyZXA3U0xKSkgwOFIzV1FKWklsSndrcGdLTH\ +FRakVwWFNpclN4dAozSkdtdXdBdE9LaXFFTXh1R043elV3ZlNINEtHdkRaUFNkZklZVXJ3eEp4aDJZZ\ +3lobG5RRC9SSnhRN3d4YlJBCjFhMUZVV0FzbDhLODk5cEtESk1GL09VOWZMRUx6QlViblpaRDRmSlg1\ +NmcyTlluUnJobS94NG9FbFk0MFNYMUUKcHYzb01hNnZrVGN5RjJnUFhKL2FkUVJoS0dFaGRjaHBpeDl\ +5UVphaDFCUFBGYW5jNzBMcjhOaDZJeHFNQ1hiMQozMG9vWHpWZmZNMVFOd28rL3hzRnBlRkRqUTAxQ0\ +9pdWZocitKREcyc0txb0o0V0JwYVhubWI1YXVrVWUvV1RKCjAxVmRyaEkvSVExd3V4QzNMMnpac3dVU\ +1NTaDk0aXg1M0hpU3pWbkI5UkxmaVhZUUVCcFEyNHVoRTdiYlo0bm0KZTczZC9zenpPTXMzYUt3OWtW\ +a2VLMTVtYWhSVWZjdEdhSVQxTkhGWUNYYXByaWExakdNdVpmSk1pSUtZUzNidQpMbUhZckF6dEptNDZ\ +0aHpjdnN3NHlhMnFoa2xUUlFJREFRQUJBb0lDQVFDaHZaUm85KzZ5aFhFTHZ6U3FXZHcxCkZGOERibG\ +hIMmhVWkNuV0kxMDM5SmdyRkxMczFSU1krSzg1aFZYMk9hV1VTNk44TmNCYzUyL1hrdFltS09HUFQKM\ +WZqMnE2M3pPcDNSSFdGNWVPMXhNeExRN3JZSDhqMGZZTFFTUytKemdwb3ZRVnJLSXkrb21JSSt3aUN6\ +R1laRApGQUM0ODJzL0J5MHdtRjVjdC9JTEdIeVY3ZXNVUlo1Vi9iL0ltQzUwQ1lDUWpQR2xBb3JkeUx\ +1MHp2NjZZUXc2CkQycTg0VHAyVUg3SExEVmhFNytUbDg4Q04xWll0VGtpSkthbkNpMFVmbStPKzJFM0\ +5HM01hajk1aDl2NktqYkoKUlkxeTNDRTVmQmkyUFNLbVVzRjN3SzdhbzJDRks5MTgybmlxL2FaNm5WO\ +Xc3NmVrRjhEOWUvS1pqUE5ZT0xkaApFczBSL2laV3RpbUx2RHdXQWNWNFNnSFFjNXJvNU9yOEFUS1ZK\ +VmlzZGFuWXkvdUhmVXZWN3U5cDVLK2E4SHU2CllabW13ZTh4bnF5M3V2M0VabE9LY20zTnZvWllVMnJ\ +HUUFQQW1sWWQ4WlRsZGxPa1JCSGxxYzllMmJuSnNTQW8KNUhhS0N3aDJsWmZpalVGNXFrMXNQcm1kN3\ +BlMld2VVV3QmVuSjJnS1ZoTE5VVGtHWmtTWGhzNlV3WWRRMjVtRQppQzl6WjhXNkQ2OXBvb0lsTTVXT\ +01ySEs0Rng1ck9vT05kUHQ4NEk1bTI3cnpnbXM4QnJXVUlGLytZZjJ0bGdmClBIR0V4c3ZCK3JRQk52\ +WHU3dXoxcVdFTlJTL2YwR2E3dVF4ZW5sZ2dubHc5M1pNOW1GWXpXb1RpdWFmdnphTnAKWEsrTEVrV2F\ +RYUs1Q0VaNEhmUlhBUUtDQVFFQTUyK240OUxQODlyQkR2bFdsTkxNanJqTDdSb0xyQ3FVZGpQWQpyT1\ +hZS3ZkTkxyS2NTc1hNdkY4YW5HQng1UG5oVDZGY05ic2dzQ3BUUXowMThZYmcrbUUxQno5ZTdFNTJGZ\ +i9NCk9BbWZqSllXUnZueUtiNnB3SGlvOHlXUXlVVk1zZU1CcmpvWk1kNkpPZEZ0K2JITHBWOS9iSkdR\ +a3NTRE04WTkKbWxGQUlUL0gyNTh1K1ZKTWsrT0prU28zZmJQSk5Ja1Q2WVBKVmNaSnZTRGI5QU83WDF\ +lendCOXVRL0FEblZ6YwpSQkJOUVZaTStZS2ZNWFJBdmFuWnlmWFFwaUxCQW8rRVRPSHJCR1dDRUhtSF\ +RCaEZIMTkyamtxNlcrTStvS0R1Cm1xMitMc2hZWTVFc2NpL2hPOVZjK2FCM0hhaGliME03M092MHFNc\ +WZoTncyU1BncHdRS0NBUUVBeDlaR1gxQnQKL3MwdGtNcGV1QWhlWjFqTklnZmFEY0RRTWlmU0k4QjRx\ +WUhiL1hOREQ5NjFQME9zMDdCN2wzNE1iZ3U2QlNwcwpXdSt0Y1hjSFlqQVJUc0Qzd0pSaVRIb0RSQzR\ +YYkxEa2pHRUVCVzRKbFVqZTA1QWZrU0QrdkZSMkJwZStxQlBLCm5yb3Mwd1BWL3RXa3MzY2VFOUlBTV\ +pWWDhQeFA3RVNXbitVZDJEWkZhcVFLb0JybHZXRXhxelpYUEJSVjhoS3QKcFBqWnFkZXFQLzhUZTBtS\ +zh5MEVreXVXOWhFdGZ2Sm5HWXhMNStrSG9xd2hQVk1tODZ5YlZNVHRQYWJTdCtPUwp0WHhJTE9RMWRN\ +QkFabzRxSnNkUUZJcTJnSHA5WFYwa2ZNUms1ajdJT1Q4c2Z6TlpKVkRNK1k5VHVlSGJXSnduCnZsWld\ +VZ2NVZTlBaWhRS0NBUUVBbFJaK2h1ckUvNGdLR2dWUld5bTRrTEJHM2dTTFJHdGhuQXVtSnlzaFoveE\ +wKZ2l1Wk55bll5L2hRQWpDMjdoUnlxb04rRFRid3hjdGVPOUJ3c1poNzBZOVJROHYwOERGVExMVE43O\ +E56UG5OcApBbXY5TGhzZTYxaFBMZU1qTkNVcVZPV3hyWFRMeWk1YkpCM2Z4SnhlWGJmNU5BMUpudUpz\ +eXF1SC82TWJ0cytKCmhkY3p3WFRjMCtBZVBKOS9nOENQZXdKYkMzRFVBQ2R1VlNHWHo4ZWZxcm1xbDd\ +jbnB5ZzBpK2pJRkNpVU8rVEcKVFcxeDg3KzUvUFF2MGtSQ0Z1UUloZ2ZCNkcwWW9vcHBrUWRZdXhKZl\ +pPaHdUUldpbTVMMlF5K294WWZySGVQOQozSlltbGFCMmJiN3kxL1FoQjcvek9VMk1nTEtYdHl4Z09ve\ +EpoQlFwZ1FLQ0FRQkIwSUE4dy9CMkNuMEhRcDhQClhUSTZOelRZRUYzd1NhQkg1SFdBOE5MTWdNaERJ\ +TUxsWnlPcVFrK1pLSGFMM2llWjFxTGRNS3VmQjNESC9idWcKeXRQb2JBVXNsN0lJSGVjVmZWaVpvMml\ +pRXhHUCtEMlB2UUFtRFVGWU90V3FrT2FPSlV2VmJ5ODhOM1NyeW9lZgo5aHpZUGxMWmxFQWNGR055S3\ +FibjJXOENHaU5LSWhXYW1Zd21UclY3T1pkeUcrTi9GZk40Vms1NkZyc1pCTDQ5CmRYU2xGZ045TTBaZ\ +WNleTEvZEpPRE9lSHNuME5VK0gvNFZEUk1hR1NmelpwSkxJOXE4T2FiSWpVM0ttb24wQTcKdzFWeWNU\ +L1FwYlBxRUFVckt5dytvMzV3MlAyaUZ1czZiMlBvUUxFTGFTRVl6K3R6UEw5UTM1ejNRdGdMQytuagp\ +IUmxCQW9JQkFES0Q4NGhrYkphczlIQ00zanNNSU9kb213ZEMvcktxVUxKNHFWZU5QR0xDY2VpNEdocn\ +dlQnNICnNoN0hibFlZSDN2U2Y3RW1iZEVCb2xlWVJsaUx5ZzJDQU1ZOGpNK0lpUEwydk1yM2Y3SzZPT\ +mU0UkVPUFJSZkcKWlJDcTh4a0ZPQlg1SUJUbkVCV3QzdVdyb1NGY2x4RTdUa2I4VkUyVzExTG9ZNlkw\ +TUNPaHdxN21xYXRUVnNrawpTRDNySmkrTFR6a2Y4OEx1bjZZNjdiaFNOTWpKZkFaUXNQc0FTRkJBUTJ\ +rQnE5alRLZGVuaU4yYTJIbm0xNCtrCnJDeU9ZVE14Q2hQbWNpS25pVy9MWnFUL0U1dlNRUGdBVzc0dT\ +VLazJoSjRBajNjRW9NVEwxSytZbStWYWh2U0cKTi8xOFdYQ1JRQkg1d0p2eXJYczBtT29GQlRnTWg4d\ +z0KLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLQo=") + ("ringAccountKey" . "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUpRUUlCQURBTk\ +Jna3Foa2lHOXcwQkFRRUZBQVNDQ1Nzd2dna25BZ0VBQW9JQ0FRRDNCdDRnOUVUdk9EVnYKM3hWV0ZlS\ +1Nnbk5uVEF3S2dYa3IrQ1FhdU8vTGZWM01RenNSNHliL3hoaWhhb1Z2c2FtZ3ZRU1haL0M1R3I5QQpE\ +YlAxbHNHclRCK1pXMC9uMXVEb3hmVWdZRWY3SGtVanJtdVFjUGlFWGlUVkNiY002U0NzdVNrMnRxdE1\ +BNjBNClRacVo2LzAvbkEvblR5TnFNaUJNSmFRN0NUb2xOTTh2Z2tvd0tBRW14OGpJUG9YZEttMnd6MD\ +Z6SnhwU1d4WjUKc0FBTkdSSHU0b2ZXNWJiMERvamtnRzFBYUJ6Nm9uSmdKK1JFSWV1UkpNQWFHYmtzW\ +TQ4Z1Z1b21BVTU0UFNvUApFb3psVGdHd1k0cnhJTGE0V1Y0RVZMVExrR240ZTYrRUYrSjIvRURBUFdN\ +bXREdFpUeURCc2t5bStLaDRUT2xPCjdnN3JlUUhLdDd1R21YU0RnbzVKZ2hOOHNVRFUxL1Z2YmFFcUZ\ +tTDJrZWNGOVlVZmNsUWRGY1ZncmIrMkh5R3AKRVc0b3RkZjlYYzhOMWxrbGk1dFBqRGZuQ1U3OHB6QT\ +BxQmV1SWhZTnF1VjhGSm1NemhXeVFDbE1MUEFYbXFVdwpWYlY0MWduM0NkektuUlVhZXFONXlzOG5KQ\ +mRJNDBleWlYRUlvU0VKcFpyT1Z5ck1icnNCQzltaVFpZUFhSnlBClFvcjFaRGlwMkpZNUFza2phUGQ0\ +NGk0MGNkeFpob1RhNnpzako5UjZScllFYjhWTmZQemFLaElwSXJSd2NpbCsKWjFrbUUwSE1kY21ITXR\ +hbWZhK0l6WUR4dDV6OGVOZys1RGpVVG1MRkdyQUVWRW5hNDcxdllxYnk5UCs1T1cvNQpSdkxtUER2Tj\ +dlUURpTlZENlBYRFk5OTY4bTZaaHdJREFRQUJBb0lDQURjOEkrTCtlNE41OEFqcHV0MmEyeVNqCllxY\ +VFUSWowMW1GTWhOWXMwQUdTTUswQncyMkdleXZwNFl3R1EzdnNIOSsvSkEydXdoYkJzazNpUW9FQmlx\ +Q0EKenZmOWdPcDRFNlk0elV6RitwSmQvRnUwSG4wWHBab0Rhdnp2eFN4djNFeUN3b0puYWZuL1FHeGw\ +xZEhoQUtsKwpmZGZjekRCc3NPZ1Y2cGtBd1MyY2wwOHFOT2g3cVhaQWFkYk1sQ1lWM0owU1hhaVZiNz\ +lHZXNvTzNwUVBMUUZiClNjQjFjT2sxYnNxWkpOU244d0xmMis5QVBEdzMwWEtNNHg5eTdRTE42Q3oxQ\ +WpvcFJLQ0NIS3R1SEc4UmVETTIKcnRTbjJmTnltQ0VqeDZGVTB6MHFldDV3Y01UbU5weEZuYXdEMU5s\ +dFpnZXBsSllwTjVKZXNEUmo2cFlnWXBPKwo5UDU2cEdtZVNTVzVxcHRoWFFLQmFsdy8wWXp2YUlYdHp\ +hTnZyNUJzRFNrWkU3cldzODIvQmFzUE1RckFmOGpLClZFMU9pSzcrVllUVTRFVWVoZ0FZOXdzNjFqYk\ +lSSEpQbW9VQXpkWHcyL0d0Vk9JUTFwUFJnOXNYN0JaMmUyV1YKdmd5aThPUEJxRWtwblBiMkU5Z0d1U\ +25rY01OeWFVbUl0c3pFandadW14dnZrUW5HanlTb3pjY2R3dnNQNnBJagpoN0g5VUNQTHdOM0N5N1lp\ +UmliSlZBWlNjZkF6QzhubXNLODQrTzJUZHBzTXk0SEZDMmM4dlZiclpteTVkWC9qCk1ESnBzS25JWlZ\ +JMmpXSzRpRS82aUdIWVdoY3JvWnYvVEJ0YW1SQUxTWDVOYkhhWTI0bXVRSG5yMldtaytld3EKbHRGbC\ +90bXgyVkpWUitMZ0JCUGhBb0lCQVFENEI0MjQyVTgvbkJ4d2RzelhCdWxBOVFTa0I5Zktud0RlRkV3S\ +wp3Nks0eU14YVdRU04ycjRxRDhLcW93OVZVMzRYdkRWbFh0RUlDaVh3Q0hZdW5IL3g3cXNSdEVzbHJM\ +aWg4UHRPClpDSU8zUml4RmlIQXFlQUh2YXF0NVhXdndaREx6WnV2THRJOTdINkZ1QjYxck5qMnhxdlR\ +IN05pUmp2S0R0WXgKR1VtNURoQ094cm9tR0NkWHRnWHJGaS9WRU1TSmpQMkM3OTNrN1pTNmNZL0Nkc1\ +RqWEsrZ1UzeWM3OC9kN1pYbwpKMGg2WFlSdmhlQW9Bd2dkVTl4MWtYL2ZmY2tZK1hUcFRwV2xXWmtlU\ +ytsejBpQkRnUlJzMm45OFZDeTZDRmRZCldsZXZaZy9SWXZ6dzlKdWFVcXArOHpHbHNXR2xuOEhtZW5X\ +Q1luSHJnNFBxQnRkL0FvSUJBUUQrOXhDL1N5ZGIKVWZxMUJHYy95YktZc3RLb3A1azB0d3M0SlUwTzN\ +aT1U3MlZ5YTdLT2lTemdPSzVnL2QzckhMMXR2dHViTDBWNAo1dEF4a1AzSkVYbmxZZU5IVkpROTc2RH\ +NGS3Ztc0FGL2JJdVBsdFBRT1dyM3g1eW1RU3lCOTBUczV0dFdWMTVQCktYYVNnMTZidDhwNS9MeERkZ\ +ng2c1YzN1o5RDFRd21EQllreVFIcWQ4clljTm9ad1M5ZnI3UTZhN1ZNSDVtT3IKbEF5dzBCYVdZQk9k\ +bjFGd0pVV3NlRlpmTy9vNUVqZk9Hd0xMR1hiOEVmQ1hqdlRYcUNHLzNrT1JvN1NkOWY1eQowVjIxMmt\ +YVVNINHNDbFB0SmwzeHpaMWJxQ2RMVDNITWNLUTk5UGFFVFppQnNXQ1lOcXg1c0Q0RGhoMzdZQ2hKCm\ +hlN3VUM1E0MElINUFvSUJBREN1VXR1b0UweloyQjhld2grbUpKdnlPMEh5cENFSnlrTE1Xd3gxejNkV\ +E9nQzEKbmhZMWk4TjNxbTZSYUk0SHdDVHFkTlI3b3ExZ1NJZnZNVHIrem9IdXBUYnBXeUorM3hJeDJU\ +Rk9wL3lnMnByUApURHFqWE94SUJycnc0WU5vaTRIa3poeTVKTnl3a1RpdnBaOWsySVMvQTdTQmNWVGx\ +raENianVDK0pPRWthSTJOClpiWGFZY1p1WElVQ3FzcTM2c3RRbCtWZUxRQWt2VjlHc0wrclRnT09Dbz\ +UrTkdRZEVZQnVoRkMzZlJzL1JhSVoKOWFBRTBFL3BTTWp1a05tTnQ2Mm1NSk1tTUdydXhnWFRRblBRR\ +npNSW43aXB2Z0hxQjRsUDM4emdsbnMvbmZVcgo1NWRuZXk3ejhMRFFETHVIc0RHd3hINzNKQjgrTVR2\ +WGFVbkNwQU1DZ2dFQVNBSGxBL0dvdXR6TFRvWmcxcDRUClI1YnhjZHBycFh5d3VYbW5hclJmY3VldG9\ +nUVNtTGpiS0xRNVk0RXZSTENJTzA5MDNENGNnOG5FTU10L01XTXoKSnZwZll3emJGU2J4THR1anRQSX\ +VhaHR3eXV2UkJIVEM1aG5FL3h0WEE1bWZLTDBHWXpzbmtubm1WL2lzSnBSZwpwZFVnSW5sWEJodkRyR\ +FlreUsvWEp0N1FZWlhlUzI5NXlUd0krZndoamlzVVBlTWEyUmRUUE9rQ01JbUVaNUhZCjJHSmZjS25H\ +SkxDVHpDKzNPcGtQazdFRE4vTUlMS2F3YVUxaGp1cVlKWVVUVmpXQzFEM2VUL1ViWHptM0VQNHMKVEN\ +uYWpCYVMzN0N2YVd4ek5JektXZS9TSXdGbEFmYWNSTHlneUR4Z3Q3bHp1akVObEtvU2xya3h3ckpEND\ +Z2WAptUUtDQVFBcTVQWWxSQjgvNnFiWWt1OTA0NUZRdXk2QWtlYXBaMW0raS9SQzZtbFRvUXB6NDlPU\ +Gs4ZGx5YjVtCndvSVhpaEo2V05jN1RsWlRYMnpQTTRBS0M5VFNBUWJrWDg5bjEyU2VDSUlHbXVINnk0\ +TjZiY2lxZjVVcSsvc0IKcHJKeFRNYlRSUFpqS0VVd1N0SFg1MUQ1bi9sQnZERGY3Y2VEZytsYlE0RjR\ +KMTlPd09oZ1lGcjFheGQvNXd2VgpURjNoVlQwbFZGN2RyRC9iMHZOcmxnbUNjbEk4UDg1a2dkRUhZbG\ +ZtTFoxeXJIMkNXVy9SS0lsWk9ZdFVuNFNpCkp5a2VlNDROWElXU3ovalRBdFRta3VQTzRvUjF5d3dRc\ +jdhUTF5a3hRVm9rVm5vY2xqU0tyQlk4R294a0I0eDIKUDNrb3F1UnkvcUd3QzBnN1o4ZjBTQjNQZVZt\ +eQotLS0tLUVORCBQUklWQVRFIEtFWS0tLS0tCg==") + ("ringAccountCert" . "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZXakNDQTBLZ\ +0F3SUJBZ0lJRm1tNmZuaWRndEl3RFFZSktvWklodmNOQVFFTUJRQXdUREVRTUE0R0ExVUUKQXhNSFNt\ +RnRhU0JEUVRFNE1EWUdDZ21TSm9tVDhpeGtBUUVUS0RjNVpqSTJaVFZpWTJNeU9EWXlPREppT0dFMwp\ +PRFF6TUdOak1EWXpNakV4T1RFNFkyWm1PVGd3SGhjTk1qRXdOREUyTVRjek1qRXdXaGNOTXpFd05ERT\ +BNVGN6Ck1qRXdXakJKTVEwd0N3WURWUVFERXdSS1lXMXBNVGd3TmdZS0NaSW1pWlB5TEdRQkFSTW9aa\ +k16TkRWbU1qYzMKTldSa1ptVXdOMkUwWWpCa09UVmtZV1ZoTVRFeFpERTFabUpqTVRFNU9UQ0NBaUl3\ +RFFZSktvWklodmNOQVFFQgpCUUFEZ2dJUEFEQ0NBZ29DZ2dJQkFQY0czaUQwUk84NE5XL2ZGVllWNHB\ +LQ2MyZE1EQXFCZVN2NEpCcTQ3OHQ5ClhjeERPeEhqSnYvR0dLRnFoVyt4cWFDOUJKZG44TGthdjBBTn\ +MvV1d3YXRNSDVsYlQrZlc0T2pGOVNCZ1Ivc2UKUlNPdWE1QncrSVJlSk5VSnR3enBJS3k1S1RhMnEwd\ +0RyUXhObXBuci9UK2NEK2RQSTJveUlFd2xwRHNKT2lVMAp6eStDU2pBb0FTYkh5TWcraGQwcWJiRFBU\ +ck1uR2xKYkZubXdBQTBaRWU3aWg5Ymx0dlFPaU9TQWJVQm9IUHFpCmNtQW41RVFoNjVFa3dCb1p1U3h\ +qanlCVzZpWUJUbmc5S2c4U2pPVk9BYkJqaXZFZ3RyaFpYZ1JVdE11UWFmaDcKcjRRWDRuYjhRTUE5WX\ +lhME8xbFBJTUd5VEtiNHFIaE02VTd1RHV0NUFjcTN1NGFaZElPQ2prbUNFM3l4UU5UWAo5Vzl0b1NvV\ +1l2YVI1d1gxaFI5eVZCMFZ4V0N0djdZZklha1JiaWkxMS8xZHp3M1dXU1dMbTArTU4rY0pUdnluCk1E\ +U29GNjRpRmcycTVYd1VtWXpPRmJKQUtVd3M4QmVhcFRCVnRYaldDZmNKM01xZEZScDZvM25Lenlja0Y\ +wamoKUjdLSmNRaWhJUW1sbXM1WEtzeHV1d0VMMmFKQ0o0Qm9uSUJDaXZWa09LbllsamtDeVNObzkzam\ +lMalJ4M0ZtRwpoTnJyT3lNbjFIcEd0Z1J2eFUxOC9Ob3FFaWtpdEhCeUtYNW5XU1lUUWN4MXlZY3kxc\ +Vo5cjRqTmdQRzNuUHg0CjJEN2tPTlJPWXNVYXNBUlVTZHJqdlc5aXB2TDAvN2s1Yi9sRzh1WThPODN0\ +NUFPSTFVUG85Y05qMzNyeWJwbUgKQWdNQkFBR2pRekJCTUIwR0ExVWREZ1FXQkJUek5GOG5kZDMrQjZ\ +TdzJWMnVvUkhSWDd3Um1UQVBCZ05WSFJNQgpBZjhFQlRBREFRSC9NQThHQTFVZER3RUIvd1FGQXdNSE\ +JnQXdEUVlKS29aSWh2Y05BUUVNQlFBRGdnSUJBRDMrCjlscFluMjZyeG5pekY2UkNvZFFFVmgvOVF2R\ +Wp1V3dHUWZqa3gxb3VlVjdDMzUyWnpIT2hWU3VGcG43TUxkVFcKamI2dWhMRkpoMWtlTDlYZ0pHalMy\ +V0UwdDlJUGp2UWx5UHIwRWJoWGRJNDJMYUR0NDk0dWQ2MEE0bWg0bW1zbwovcjY2NERKOWMwUjZBOUd\ +QZUJjWi9zekhMQjJ4VmM5M3hYNjVjcmNoVTFLMDBVK2ZZUWtHS0xidFFXeUZzdnlKCmRHdFRxamVlYV\ +VNeGt5dFFWNWFxVHJ1SG4vV3U2RWRZejYrZ0ViQXJHUURRK1J3QmMxMDNGcWRIU0xReWdEWHoKd0pNV\ +GhDREdBVmR6V2NCemJYL1JIZms5bTZzK01HblNJUFBrNG9FOUdFbGdQc2JUZ2FrTzc3aElUdzRxZXJi\ +TAp2M0tiaTVOeVZZVFNVa3hDb0VRSkIrWlZJT3BVNFhRdVAvNkZkWldBRk1LU0Jkd1JLcnRmT1hZT1E\ +xcVBvYU1uCjZPR1VGMU0rYWZ0dTNCMkNhZ2ZMaE5hbVBoSjdxWTNSMzJhK2VRVllvYWlESXZKMElIUH\ +FnQ0NuWXlIaVBHTjQKS3VvaE9IZFpXTkxOUXdIQTg3SDV5NEwrSGZISmdFZFppdWYrbTNMa3JJcnN5M\ +WFoUjgyaEpZVkhreHVyZVRDcQpIR2NJaUIvTHpkSG91WFVrWGNrNjRvWXVRQnM3ZE9KVEx6bGlibU8x\ +elRzVWRoc0d4dE9zc2lYWkFjUURmZHBnCjNHdFJ3UkRwd0ZtL2k5TC9UWjMzMjBwY0VZd21aN1dBSzJ\ +Ra3Z6eWZlelRGeXdLWmh3c0RQRkwycllzLzhXdWsKRk1sek5BVmRMTytTS1RxeGlkM0l1elFaQ0FwU0\ +5ybTJMY2ZVN2grWAotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tQkVHSU4gQ0VSVElGSUNBV\ +EUtLS0tLQpNSUlGWFRDQ0EwV2dBd0lCQWdJSXV0T3MxUXBsSVRvd0RRWUpLb1pJaHZjTkFRRU1CUUF3\ +VERFUU1BNEdBMVVFCkF4TUhTbUZ0YVNCRFFURTRNRFlHQ2dtU0pvbVQ4aXhrQVFFVEtEYzVaakkyWlR\ +WaVkyTXlPRFl5T0RKaU9HRTMKT0RRek1HTmpNRFl6TWpFeE9URTRZMlptT1Rnd0hoY05NakV3TkRFMk\ +1UY3pNakExV2hjTk16RXdOREUwTVRjegpNakExV2pCTU1SQXdEZ1lEVlFRREV3ZEtZVzFwSUVOQk1UZ\ +3dOZ1lLQ1pJbWlaUHlMR1FCQVJNb056bG1NalpsCk5XSmpZekk0TmpJNE1tSTRZVGM0TkRNd1kyTXdO\ +ak15TVRFNU1UaGpabVk1T0RDQ0FpSXdEUVlKS29aSWh2Y04KQVFFQkJRQURnZ0lQQURDQ0Fnb0NnZ0l\ +CQUxTcGduY2tYODd3OE5vVzdRWE5DbWtHSHphVnN1NE9UbjFSSUhqVQpGR0w5MjNvVlk5RHVoZjFtVn\ +llamgremVCOCszaTR4b1E5MU04WDdTVnk4SHZxejQ4c0I2djAxMk5RTGpwN08xCkd4dmc1a0REeDRqb\ +mVyUWd6d0JzVEFuaC8xNy9SOEFDZkhINzYzUjhMRjY1clJPSStOT21KNUV2cU9rRWJJOXYKb0UySko1\ +czBVSHZZM1hTQ2pid01ROXRkM2c0SG9LNmRYSU44a2d6ejg0Yi83YkF5TnZ4a09rNnVSei9CSjhhZQo\ +zWjZudElza2tmVHhIZFpBbGtpVW5DU21Bb3VwQ01TbGRLS3RMRzNja2FhN0FDMDRxS29Rekc0WTN2Tl\ +RCOUlmCmdvYThOazlKMThoaFN2REVuR0haaURLR1dkQVA5RW5GRHZERnRFRFZyVVZSWUN5WHdyejMya\ +29Na3dYODVUMTgKc1F2TUZSdWRsa1BoOGxmbnFEWTFpZEd1R2IvSGlnU1ZqalJKZlVTbS9lZ3hycStS\ +TnpJWGFBOWNuOXAxQkdFbwpZU0YxeUdtTEgzSkJscUhVRTg4VnFkenZRdXZ3Mkhvakdvd0pkdlhmU2l\ +oZk5WOTh6VkEzQ2o3L0d3V2w0VU9OCkRUVUk2SzUrR3Y0a01iYXdxcWduaFlHbHBlZVp2bHE2UlI3OV\ +pNblRWVjJ1RWo4aERYQzdFTGN2Yk5tekJSSkoKS0gzaUxIbmNlSkxOV2NIMUV0K0pkaEFRR2xEYmk2R\ +VR0dHRuaWVaN3ZkMyt6UE00eXpkb3JEMlJXUjRyWG1acQpGRlI5eTBab2hQVTBjVmdKZHFtdUpyV01Z\ +eTVsOGt5SWdwaExkdTR1WWRpc0RPMG1ianEySE55K3pEakpyYXFHClNWTkZBZ01CQUFHalF6QkJNQjB\ +HQTFVZERnUVdCQlI1OG01YnpDaGlncmluaERETUJqSVJrWXovbURBUEJnTlYKSFJNQkFmOEVCVEFEQV\ +FIL01BOEdBMVVkRHdFQi93UUZBd01IQmdBd0RRWUpLb1pJaHZjTkFRRU1CUUFEZ2dJQgpBRWFDblluY\ +m9yWmRhWWZRUmxSb0dtSE94T1g5VFdNMXY0dWEweCtFcG11RDVWRDVlWEY1QVZkMlZadEdaeHYvCkd2\ +VnFBU2l0UTk3ampKV2p2bURWTUZtb3hQSmRYWDFkYTd5cmJYeFRmYm1mM1pac1RpdmZVdWQxYThxdUN\ +3dTUKTnBrdHFjV0JyMHRnNzFhOXlidHJOdm1GczZGRE1WMXkrY2JxYlp1UWlDWnc0WmZhekFaeTRQRG\ +NocE9SNjRCSwpBWFZIT05HcWFoV2hwcDkwd2E0TFEwUTE1U3FDR25kYVI4SHg5MHJOeEdkRjM1T1BLU\ +jd1TDdrWDU4ZGxaWStDClJJK1pKMndYMzJUZzgrc1RtTmNaUTliWDdvS0t3R3E5Uk94SjZJSUhOSnN2\ +bXhaSnlPcmE2N21hNTd3OWxiQnUKSzJlQ3cwZjRDeHdLNU1LNStkbWx6R3dhZmJlMG00TTBvVjlhWUx\ +ZTzAwR3Iza05heW9PdjRRVGtEM2pCMzVSMQpDMGJnQmk2eU1sTVJ2akZ5eEZkOFJpL003VG1jcXNObT\ +B5aklqbkZaenVtMFZTR1NLMXlRU3Flak40S0Y2R0JMCllpZ2JpM3c4WG5HYm9pZGdBUE9ncVVJeTJ1d\ +EU1MllzVXFsVHVncXhtM2xDOUhzaDM2UFJLNURDUG93eHVUNlgKcXo1M1ZiN2h6TkxLelpiRlJzbUdF\ +OFY2cWM2bXZTbUFXa25nL3QwaStXVmdGVkZuZFQrQ0oyNTJsa0ZacGljdAp6ekdETW44VUNDRUp4TDR\ +KTklTM2lLOUhlRys2MlZuay9QOEM3YVpLSXpVdjFud25rcVdUUUFYWDBKckJGdDdICjI5ZDk1RElmRT\ +RuT0FyS0JFNHc2Z1R4SU1uZzVzWi9ZbDFjcG5wUHlsR3VICi0tLS0tRU5EIENFUlRJRklDQVRFLS0tL\ +S0K") + ("ethKey" . "fN8cOT1lYNziaW0+pjBIgZ8r6+zMMhHsukkWBNPDsFo=") + ("TURN.username" . "ring") + ("TURN.server" . "turn.jami.net") + ("TURN.realm" . "ring") + ("TURN.password" . "ring") + ("TURN.enable" . "true") + ("TLS.verifyServer" . "true") + ("TLS.verifyClient" . "true") + ("TLS.serverName" . "") + ("TLS.requireClientCertificate" . "true") + ("TLS.privateKeyFile" . "LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUpRd0lCQU\ +RBTkJna3Foa2lHOXcwQkFRRUZBQVNDQ1Mwd2dna3BBZ0VBQW9JQ0FRQzM5b1Z0cXNtUGdaSUgKcHpTV\ +GtlT3BlWC9CSEx2KzFTYnJPSFpVRHEwNFZCUU5BNmJmSFNSWTJpbHE1WEVheXNVSmwzQmsvM0txZEhS\ +cQpEV01wQ1dpcE1Vc2FwSGxJR0tSWHEwbXhQZ29WODZSUVBub1dCRTdhWVVEZTlJZXlxMmllZXpDK1l\ +YSnBtWTljCk5tblpaMFlHOHJGMEVpWFA0SHpVWGphZklTKzdKTTJ5ZTZyUlpINXBvdHBQNmV4NXhqVU\ +VuNEFFdWhuWGJ6U0EKNnZMaC9wYnhIUVBHMVlmNHFhcU9TY1lXV0ZWaE5ySVBCVFJNZ2RaTWRGRTh0W\ +VFyaUNkNTV4dUhrYncrQmY1VQpmVENqN25tVEcybDJNbGcrSXBHVkFXUFRWNkl0NFNiS3VadW5MZmRD\ +S2FrSU03SnA3V2dJWjZNRHdkaFFQWjJNClJPQzN0b0c1ajh4aFZOU0Q5QlZiOGZBcnRmeHdldlliQk1\ +yOEdRV2JQYlRKYk9tZHZnUGRGcXNTb1F4QnRUcWQKSG1TMG53RjJ4UGZrdm1Cd3BSWWhHNHpuZlVtMl\ +BQcDBIK1g5Sk96d0R1QWI5K0c0cWZtK3Z6bDd1N0o3anJ0TQpHQlJlSlREdGFNd2RHa0lmcUNRZGREZ\ +HFpM1hzVHVVOVNubVN4NkdxT2V6ZU04bk55dmtZWjR2Uk9vODFzU1JjCjQ4L3pZZjJ1OFBJUm4vVy9x\ +Zk9hbjVIbkcxeFNBUDZkcHAzQ1Y0YTgwUFduZyszcXQ4NW5Ha2k2Nk00NUlnRjgKNHZwRGUyM2YyZnp\ +rTk5OOTk1VTBvUStKVGg5eG1HaWJmenlCaXk5d3RUVHVYWDB3Y3gwTm9wNjdYaFFDVVBGSApybVd6Wj\ +V5bXhEdkRhcUN1U3NwcG0vTkRVRWQyc1FJREFRQUJBb0lDQUYyWXo4bzhXdERvMjZPSkx2Ym1BeTcyC\ +jRra2VsWWZTYXpyQ1AzSUZCWnpqS2xCMHl6STVZWVRUZXI4b2ZhTmtCMXdaOE5WeUlxVVhHeVBhSzls\ +MU1BcmwKU1pFRW5iQlEyVXpCbVh6VVU4MVhhUUpxeHpMc2ZqSWR5U09teG1QaFVobFFGRHpJMTYxaXM\ +4MzI0V1A3WjJXaApsU2U1RkFQdjg1TVpYREVhY1c2R0N5SUVTYVMvdkpHQ2loQ2VzL0pCSmpoejdtNT\ +VRU3liSjl0dnJxUE5KSDhJCmhDTm1BUWhEU3NPYVJXNlpBdGV6UEdUb0FQUHNHMXhLMGdwUlVDM1YyR\ +C90bGRRa0VlSjhxaW5TaUN6ZjZIc3cKTnpncjVUbTMzTm96R3Rjc2Z4ZFl0cVB1UzRPRG40bktLSFpE\ +MTBLTng2Qi9HakdQTHIra21jUUVSMUV4U2s2QwpSemtzSTRzRml6VnVLYTFNK2ZqaVNVc3RTdHV1cTJ\ +QQTJJNzFNTTZlMHJmU3ZKM3VESEJYOWY5T1RFaGxZUFBkCnB1VWM2ek1pdEJKRTg2SFo1M08wR0MzdG\ +p1M1BnSnhmWnJyMHBCU20rSzQ3ajVTUFNkaytiVGh3UDZDZDhzWGUKNmljS3YvMXI0SjVqd1pFTmRna\ +2hKeHVuMkE1WVA0c0NBU21JSFFXWmU1cGdFQ0ljUXBHZjNBVllVdVpGeXZ6cApLL3VBRTc0L3NMK0VU\ +UnhDUlhkN0ZJM2cwZFVySTA2WUFIV1FkY1JpdWpIUU12ZXB3V2RrdUdkaE9wV3VRTy9oCjc0MGgvRlZ\ +0dFdOMjc3ajhhc3ptSDA3T2lRWFIwTU1mN2FEaldMaElMYkd2YkV4TzN0TnRBYXZ6cGxmMUNSNk8KUm\ +1nNDdhVTZpR3lON1MwTE85RUJBb0lCQVFEWWMwelh2UmJHTWxkVVZnOFFTT1ZHKzV4NXVocTVyQ2xtQ\ +lp2ZApvYitWS1hkMXBONThraWkxTFBiYUJmZG9JcWVUamdOK0E0YnVpT3RGNjdCWUdISkp0WTk4ZWR5\ +RkpZREtCSzIyCmthRml2eWVuV01UWG5jaHMvUmNXMnVGS1M3cFAyNDRXYkF2clQxZGxmTTF3L1JzbTZ\ +QVjF1dndPSmNJcEdLSzgKWWgyN2hiaThUbEF6bnJCSS9TbGZwNlpxV3Y0MFRLanNNdmVGVFNWRU5yd1\ +F2MVl4TjcxbjA1UWRVUkIrT0lJSQpPenpNMWpNcm43cjNmS3RKTEx0RllEdUhJZzJDL2E0cUZORmpjM\ +296V1laQkhlcDkrWlEyOTgxdy96VTJIZ0oxCkxiajZjMy9qQU5EakI3MnRkcWMxVUkvMWhwVUh2WDJz\ +emY2czM0L3VSY3RlaFgxWEFvSUJBUURaazVaT01RdVMKNzlVQkhCeTRFbFR2cVkvUUxjMUd1aG9LVXE\ +0dzhONEtJZlpMSDk5TTcwek9JWjNoaEdaYWJDREZHN3RqSDl4Vwo3UnhXVWt5cFRDK1h5TTc1YjF0YW\ +9jRVVyZ1hnUnE4R1JFRXg3OUtwWUE3ei95TVc1OHJCTWhHTWlGZ1JTM00zCmNQSzg2dHBvaTFDN2crd\ +lUxcjJwcDEzN0habzZiUHpKTFRPNXA1OG1tTFRPQVpKd1VSZ3pzZWJnc1dsWXZlR0wKZWNBZ3lYbUtt\ +WmVSeGRNUFlCK0NmMno1TWZ5Ujk4MHRVSDJPaitGQjlJNExzRTExREphZ2wwS1lHMWxKRTBFagpodEV\ +1cTFOTG9DcVkzYXVKYjhtRUpqNlA2Ylc2SytCZTBlUi9CS1MraGxxb3VOYWEySnlhSDc4Qis2U1VZWE\ +RuCnd6MityWUhVZEI4M0FvSUJBRFlXc2ZnaloyS0Z4KzdxUm45aVIvRXlCUXNpSjNXSWdSdmVnUEdrY\ +nRTZWRSeXYKNDIwcnRRSjVSd0o2aFRXL216S3pSVW9qSlgvTU5VYld1ODEzNW05bThJRkJqb3F6TVhq\ +S0xJSzM1NlZlY1ZGUApUSGs1RTVHd3VTbGI3dnA2N0FieXJaSUswL3VzYXdHUWEySTF6YWd1aE5BenR\ +yTHVXcE9jZFdZditwQVd2WEJJCi9aKzRvd0xLU0tGL3FvVmZVYkRPQzFSaTlCbWFpcHArTndiVVdYeV\ +pHanFzMDVGejVYUTFPTUZIMUV5M3BqZmIKaFlRODRpeTZBZDQzU3dqY3lKV1lRUUtCQzBZWDRFeWVyW\ +Dd1TTkvaEUxbWRHUGlJdmNwVk8zWCt3Ly9LQndZNQorUGtTd1NKc3lTSDRqTkRsSGE2K2VuNUpSNy81\ +YWVVNENiY0lFcWNDZ2dFQkFLelMvNnhDWnZnalN5V2poK2hxCm4wN3plQW1icUJmTEVZNHJtTFBGVUF\ +ucWFqSElNbDV4SXFnRnFkd05pQ1BCQ2RLbnNaUU9KYjVpZjRUTndKa2wKckJRNzdMUFRVVlJQY2dnVU\ +p4Uzc4S0Rnckl5Vys5V1FPTEIxZEJEb3MzUDhhbFlmb3h5eHV1Wko4SFpCY3BWaQpQQkdHdTFnSDd3V\ +0lyUzBmbVhkWlJQNGp5cGRvM3hFUWNXWEZkK1dCZE9EektmcEcwZkFzZTdDSFdDWnpBdmttCkFYQklH\ +OXQxdGZHNWQvMEZTS05GbTVPb0FPT3h3L0xZNTgrL0RmZXd0U0VBcFdRZkxTL1BmSWxVdUdvQ3FwcEM\ +Kc2pOVXVNSGxxc011Z2JsY29mNHNoZityWjMzQldYOEJSNWdIb21mRE1ibDNDQWp5TXd1dHpybzVxcD\ +BBUTBWWApxOGNDZ2dFQkFLakFXVVRYY1F2TE8yYkxOZmJBTUlSVXk1T2lTZmJCbDNYRThSZnNzaUt2V\ +VBnTFcwSlV1V3FLCjdGdUFxTlJPRHhrS0pMSTdyQlo2YVNqNitFWHpUMnJwY2dFWktnSjFOUEFRdFNs\ +UjNJUkcyU3JJdjBFK3UzbkUKK1laa3pOa2Q0MUJqTkRjRm1HV21lZk5ROUJmaVIrZlZFSkZmcE5oSkl\ +mNUloSWU0RUtZUE5VUXNua0tSVTlxUApzWi9idXBXc2w4bWVFcko3bllJQ05ucHpnSHRpNXdSMlliVF\ +VXT01odmRFUldxMnhTV3BBYmtNMElhZDBUc05kCmUrYVRQVmJOMXFibFZLMm1qUTl2YS9JSkVuSE51V\ +E9TREtJeUpvcVArQkxiRTVjQU5acXQ2OFFadWdOc2RxNHkKV2FoeStydU5LS1F3Mk5MYzQzZUtsNmxv\ +bXdtRlFZOD0KLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLQo=") + ("TLS.password" . "") + ("TLS.negotiationTimeoutSec" . "-1") + ("TLS.method" . "Automatic") + ("TLS.ciphers" . "") + ("TLS.certificateFile" . "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZHVENDQ\ +XdHZ0F3SUJBZ0lJU1pUdlZPQnh3akF3RFFZSktvWklodmNOQVFFTUJRQXdTVEVOTUFzR0ExVUUKQXhN\ +RVNtRnRhVEU0TURZR0NnbVNKb21UOGl4a0FRRVRLR1l6TXpRMVpqSTNOelZrWkdabE1EZGhOR0l3WkR\ +rMQpaR0ZsWVRFeE1XUXhOV1ppWXpFeE9Ua3dIaGNOTWpFd05ERTJNVGN6TWpFd1doY05NekV3TkRFME\ +1UY3pNakV3CldqQlFNUlF3RWdZRFZRUURFd3RLWVcxcElHUmxkbWxqWlRFNE1EWUdDZ21TSm9tVDhpe\ +GtBUUVUS0dFM09XTmwKTURVNE16VmhPRFV5WlRsbU5qWmxOelF3TURjeU5EUXdPVFZpTVdaa1kyVXdO\ +bVV3Z2dJaU1BMEdDU3FHU0liMwpEUUVCQVFVQUE0SUNEd0F3Z2dJS0FvSUNBUUMzOW9WdHFzbVBnWkl\ +IcHpTVGtlT3BlWC9CSEx2KzFTYnJPSFpVCkRxMDRWQlFOQTZiZkhTUlkyaWxxNVhFYXlzVUpsM0JrLz\ +NLcWRIUnFEV01wQ1dpcE1Vc2FwSGxJR0tSWHEwbXgKUGdvVjg2UlFQbm9XQkU3YVlVRGU5SWV5cTJpZ\ +WV6QytZWEpwbVk5Y05tblpaMFlHOHJGMEVpWFA0SHpVWGphZgpJUys3Sk0yeWU2clJaSDVwb3RwUDZl\ +eDV4alVFbjRBRXVoblhielNBNnZMaC9wYnhIUVBHMVlmNHFhcU9TY1lXCldGVmhOcklQQlRSTWdkWk1\ +kRkU4dFlRcmlDZDU1eHVIa2J3K0JmNVVmVENqN25tVEcybDJNbGcrSXBHVkFXUFQKVjZJdDRTYkt1Wn\ +VuTGZkQ0tha0lNN0pwN1dnSVo2TUR3ZGhRUFoyTVJPQzN0b0c1ajh4aFZOU0Q5QlZiOGZBcgp0Znh3Z\ +XZZYkJNcjhHUVdiUGJUSmJPbWR2Z1BkRnFzU29ReEJ0VHFkSG1TMG53RjJ4UGZrdm1Cd3BSWWhHNHpu\ +CmZVbTJQUHAwSCtYOUpPendEdUFiOStHNHFmbSt2emw3dTdKN2pydE1HQlJlSlREdGFNd2RHa0lmcUN\ +RZGREZHEKaTNYc1R1VTlTbm1TeDZHcU9lemVNOG5OeXZrWVo0dlJPbzgxc1NSYzQ4L3pZZjJ1OFBJUm\ +4vVy9xZk9hbjVIbgpHMXhTQVA2ZHBwM0NWNGE4MFBXbmcrM3F0ODVuR2tpNjZNNDVJZ0Y4NHZwRGUyM\ +2YyZnprTk5OOTk1VTBvUStKClRoOXhtR2liZnp5Qml5OXd0VFR1WFgwd2N4ME5vcDY3WGhRQ1VQRkhy\ +bVd6WjV5bXhEdkRhcUN1U3NwcG0vTkQKVUVkMnNRSURBUUFCTUEwR0NTcUdTSWIzRFFFQkRBVUFBNEl\ +DQVFCZmRCc0p4RVVWeFRBeG5vNll1bEFoR1ZvUAplWG8xMHIwOE5DcDZRZlJxeGJlTkZ5aXEvKzEwSE\ +NpL1RoZk41OVdLNlpudFF4NlFNZUxEUVZTb0NjNzlaaWQ4ClE2RUdsWkp1c2RTTmg0VjVteXRCQVZHZ\ +2J2aXJFWU1Wcm5jWWg3bHR2LzVuSGRsbyt2WXV3Vzh0aEhHTk1TUkIKQmhJN2xydmpqY3NkbVl6L1Bp\ +NFNZdkg3c3RaVWpRYmUzNzh3UE90b3lBMTNybEtQUTF4QjJFbUxISDYya21WTQowa25wL1hQQ2o2alR\ +zakpFWko1NzZNMmloYy9DTzkvREVlWWZ4RFlJb004NEF5dTc0UU9UUVN1cnVHRjF5T1RKCmlxRkltTH\ +hMcWRxNk1Zdmx1QjFmTzlKaU9QaS84UEJFTTVpeGYzajlGOFVMQTZUTU1NSklFR1ROeUNzOWpzQloKR\ +UNiWVgweTY4WWtGYnYzQmNWaGk2K1VVWVhBVUd0akhwQ3Z2VThYaHowL0RVZFVaTkpqejRJeWl1ZE5N\ +dnFjQgppSEJDdkxFS1B4VFd1ZlFZaGM5ZkVXTm1valc1WSsvalBVeDcrQWxPM1RXZm1OclBpWDJuSEd\ +5UG5tYjZIR2hWCmp3UU0wT1h4cGxnZ2dQMWpVZ0VYWWRucStjYzdRMy94RFIvdi9Fd244T1d4OVB3eV\ +Y0WFZwZXY5QnpmeWwyQTMKdFhWVkFKMzJtcTlPQmVadXczdWlWU2E2TWdCVG80eXcrYnBjU2VIU2xXO\ +XNRc1NYY3dUNjhvOUdhR2hMNjdXMApwQWx4R2VxSWRtbTZ0MkxIUCtQQnBtL3BweTMvTmh1NFIwMTNv\ +Znp5V2Y0bEcrVnpWVGE0eXVqU1J3UlluYmZyCjlSWER0L0I5VEg5TnNsamdBQT09Ci0tLS0tRU5EIEN\ +FUlRJRklDQVRFLS0tLS0KLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZXakNDQTBLZ0F3SU\ +JBZ0lJRm1tNmZuaWRndEl3RFFZSktvWklodmNOQVFFTUJRQXdUREVRTUE0R0ExVUUKQXhNSFNtRnRhU\ +0JEUVRFNE1EWUdDZ21TSm9tVDhpeGtBUUVUS0RjNVpqSTJaVFZpWTJNeU9EWXlPREppT0dFMwpPRFF6\ +TUdOak1EWXpNakV4T1RFNFkyWm1PVGd3SGhjTk1qRXdOREUyTVRjek1qRXdXaGNOTXpFd05ERTBNVGN\ +6Ck1qRXdXakJKTVEwd0N3WURWUVFERXdSS1lXMXBNVGd3TmdZS0NaSW1pWlB5TEdRQkFSTW9aak16Tk\ +RWbU1qYzMKTldSa1ptVXdOMkUwWWpCa09UVmtZV1ZoTVRFeFpERTFabUpqTVRFNU9UQ0NBaUl3RFFZS\ +ktvWklodmNOQVFFQgpCUUFEZ2dJUEFEQ0NBZ29DZ2dJQkFQY0czaUQwUk84NE5XL2ZGVllWNHBLQ2My\ +ZE1EQXFCZVN2NEpCcTQ3OHQ5ClhjeERPeEhqSnYvR0dLRnFoVyt4cWFDOUJKZG44TGthdjBBTnMvV1d\ +3YXRNSDVsYlQrZlc0T2pGOVNCZ1Ivc2UKUlNPdWE1QncrSVJlSk5VSnR3enBJS3k1S1RhMnEwd0RyUX\ +hObXBuci9UK2NEK2RQSTJveUlFd2xwRHNKT2lVMAp6eStDU2pBb0FTYkh5TWcraGQwcWJiRFBUck1uR\ +2xKYkZubXdBQTBaRWU3aWg5Ymx0dlFPaU9TQWJVQm9IUHFpCmNtQW41RVFoNjVFa3dCb1p1U3hqanlC\ +VzZpWUJUbmc5S2c4U2pPVk9BYkJqaXZFZ3RyaFpYZ1JVdE11UWFmaDcKcjRRWDRuYjhRTUE5WXlhME8\ +xbFBJTUd5VEtiNHFIaE02VTd1RHV0NUFjcTN1NGFaZElPQ2prbUNFM3l4UU5UWAo5Vzl0b1NvV1l2YV\ +I1d1gxaFI5eVZCMFZ4V0N0djdZZklha1JiaWkxMS8xZHp3M1dXU1dMbTArTU4rY0pUdnluCk1EU29GN\ +jRpRmcycTVYd1VtWXpPRmJKQUtVd3M4QmVhcFRCVnRYaldDZmNKM01xZEZScDZvM25Lenlja0YwamoK\ +UjdLSmNRaWhJUW1sbXM1WEtzeHV1d0VMMmFKQ0o0Qm9uSUJDaXZWa09LbllsamtDeVNObzkzamlMalJ\ +4M0ZtRwpoTnJyT3lNbjFIcEd0Z1J2eFUxOC9Ob3FFaWtpdEhCeUtYNW5XU1lUUWN4MXlZY3kxcVo5cj\ +RqTmdQRzNuUHg0CjJEN2tPTlJPWXNVYXNBUlVTZHJqdlc5aXB2TDAvN2s1Yi9sRzh1WThPODN0NUFPS\ +TFVUG85Y05qMzNyeWJwbUgKQWdNQkFBR2pRekJCTUIwR0ExVWREZ1FXQkJUek5GOG5kZDMrQjZTdzJW\ +MnVvUkhSWDd3Um1UQVBCZ05WSFJNQgpBZjhFQlRBREFRSC9NQThHQTFVZER3RUIvd1FGQXdNSEJnQXd\ +EUVlKS29aSWh2Y05BUUVNQlFBRGdnSUJBRDMrCjlscFluMjZyeG5pekY2UkNvZFFFVmgvOVF2RWp1V3\ +dHUWZqa3gxb3VlVjdDMzUyWnpIT2hWU3VGcG43TUxkVFcKamI2dWhMRkpoMWtlTDlYZ0pHalMyV0Uwd\ +DlJUGp2UWx5UHIwRWJoWGRJNDJMYUR0NDk0dWQ2MEE0bWg0bW1zbwovcjY2NERKOWMwUjZBOUdQZUJj\ +Wi9zekhMQjJ4VmM5M3hYNjVjcmNoVTFLMDBVK2ZZUWtHS0xidFFXeUZzdnlKCmRHdFRxamVlYVVNeGt\ +5dFFWNWFxVHJ1SG4vV3U2RWRZejYrZ0ViQXJHUURRK1J3QmMxMDNGcWRIU0xReWdEWHoKd0pNVGhDRE\ +dBVmR6V2NCemJYL1JIZms5bTZzK01HblNJUFBrNG9FOUdFbGdQc2JUZ2FrTzc3aElUdzRxZXJiTAp2M\ +0tiaTVOeVZZVFNVa3hDb0VRSkIrWlZJT3BVNFhRdVAvNkZkWldBRk1LU0Jkd1JLcnRmT1hZT1ExcVBv\ +YU1uCjZPR1VGMU0rYWZ0dTNCMkNhZ2ZMaE5hbVBoSjdxWTNSMzJhK2VRVllvYWlESXZKMElIUHFnQ0N\ +uWXlIaVBHTjQKS3VvaE9IZFpXTkxOUXdIQTg3SDV5NEwrSGZISmdFZFppdWYrbTNMa3JJcnN5MWFoUj\ +gyaEpZVkhreHVyZVRDcQpIR2NJaUIvTHpkSG91WFVrWGNrNjRvWXVRQnM3ZE9KVEx6bGlibU8xelRzV\ +WRoc0d4dE9zc2lYWkFjUURmZHBnCjNHdFJ3UkRwd0ZtL2k5TC9UWjMzMjBwY0VZd21aN1dBSzJRa3Z6\ +eWZlelRGeXdLWmh3c0RQRkwycllzLzhXdWsKRk1sek5BVmRMTytTS1RxeGlkM0l1elFaQ0FwU05ybTJ\ +MY2ZVN2grWAotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tQkVHSU4gQ0VSVElGSUNBVEUtLS\ +0tLQpNSUlGWFRDQ0EwV2dBd0lCQWdJSXV0T3MxUXBsSVRvd0RRWUpLb1pJaHZjTkFRRU1CUUF3VERFU\ +U1BNEdBMVVFCkF4TUhTbUZ0YVNCRFFURTRNRFlHQ2dtU0pvbVQ4aXhrQVFFVEtEYzVaakkyWlRWaVky\ +TXlPRFl5T0RKaU9HRTMKT0RRek1HTmpNRFl6TWpFeE9URTRZMlptT1Rnd0hoY05NakV3TkRFMk1UY3p\ +NakExV2hjTk16RXdOREUwTVRjegpNakExV2pCTU1SQXdEZ1lEVlFRREV3ZEtZVzFwSUVOQk1UZ3dOZ1\ +lLQ1pJbWlaUHlMR1FCQVJNb056bG1NalpsCk5XSmpZekk0TmpJNE1tSTRZVGM0TkRNd1kyTXdOak15T\ +VRFNU1UaGpabVk1T0RDQ0FpSXdEUVlKS29aSWh2Y04KQVFFQkJRQURnZ0lQQURDQ0Fnb0NnZ0lCQUxT\ +cGduY2tYODd3OE5vVzdRWE5DbWtHSHphVnN1NE9UbjFSSUhqVQpGR0w5MjNvVlk5RHVoZjFtVnllamg\ +remVCOCszaTR4b1E5MU04WDdTVnk4SHZxejQ4c0I2djAxMk5RTGpwN08xCkd4dmc1a0REeDRqbmVyUW\ +d6d0JzVEFuaC8xNy9SOEFDZkhINzYzUjhMRjY1clJPSStOT21KNUV2cU9rRWJJOXYKb0UySko1czBVS\ +HZZM1hTQ2pid01ROXRkM2c0SG9LNmRYSU44a2d6ejg0Yi83YkF5TnZ4a09rNnVSei9CSjhhZQozWjZu\ +dElza2tmVHhIZFpBbGtpVW5DU21Bb3VwQ01TbGRLS3RMRzNja2FhN0FDMDRxS29Rekc0WTN2TlRCOUl\ +mCmdvYThOazlKMThoaFN2REVuR0haaURLR1dkQVA5RW5GRHZERnRFRFZyVVZSWUN5WHdyejMya29Na3\ +dYODVUMTgKc1F2TUZSdWRsa1BoOGxmbnFEWTFpZEd1R2IvSGlnU1ZqalJKZlVTbS9lZ3hycStSTnpJW\ +GFBOWNuOXAxQkdFbwpZU0YxeUdtTEgzSkJscUhVRTg4VnFkenZRdXZ3Mkhvakdvd0pkdlhmU2loZk5W\ +OTh6VkEzQ2o3L0d3V2w0VU9OCkRUVUk2SzUrR3Y0a01iYXdxcWduaFlHbHBlZVp2bHE2UlI3OVpNblR\ +WVjJ1RWo4aERYQzdFTGN2Yk5tekJSSkoKS0gzaUxIbmNlSkxOV2NIMUV0K0pkaEFRR2xEYmk2RVR0dH\ +RuaWVaN3ZkMyt6UE00eXpkb3JEMlJXUjRyWG1acQpGRlI5eTBab2hQVTBjVmdKZHFtdUpyV01ZeTVsO\ +Gt5SWdwaExkdTR1WWRpc0RPMG1ianEySE55K3pEakpyYXFHClNWTkZBZ01CQUFHalF6QkJNQjBHQTFV\ +ZERnUVdCQlI1OG01YnpDaGlncmluaERETUJqSVJrWXovbURBUEJnTlYKSFJNQkFmOEVCVEFEQVFIL01\ +BOEdBMVVkRHdFQi93UUZBd01IQmdBd0RRWUpLb1pJaHZjTkFRRU1CUUFEZ2dJQgpBRWFDblluYm9yWm\ +RhWWZRUmxSb0dtSE94T1g5VFdNMXY0dWEweCtFcG11RDVWRDVlWEY1QVZkMlZadEdaeHYvCkd2VnFBU\ +2l0UTk3ampKV2p2bURWTUZtb3hQSmRYWDFkYTd5cmJYeFRmYm1mM1pac1RpdmZVdWQxYThxdUN3dTUK\ +TnBrdHFjV0JyMHRnNzFhOXlidHJOdm1GczZGRE1WMXkrY2JxYlp1UWlDWnc0WmZhekFaeTRQRGNocE9\ +SNjRCSwpBWFZIT05HcWFoV2hwcDkwd2E0TFEwUTE1U3FDR25kYVI4SHg5MHJOeEdkRjM1T1BLUjd1TD\ +drWDU4ZGxaWStDClJJK1pKMndYMzJUZzgrc1RtTmNaUTliWDdvS0t3R3E5Uk94SjZJSUhOSnN2bXhaS\ +nlPcmE2N21hNTd3OWxiQnUKSzJlQ3cwZjRDeHdLNU1LNStkbWx6R3dhZmJlMG00TTBvVjlhWUxZTzAw\ +R3Iza05heW9PdjRRVGtEM2pCMzVSMQpDMGJnQmk2eU1sTVJ2akZ5eEZkOFJpL003VG1jcXNObTB5akl\ +qbkZaenVtMFZTR1NLMXlRU3Flak40S0Y2R0JMCllpZ2JpM3c4WG5HYm9pZGdBUE9ncVVJeTJ1dEU1Ml\ +lzVXFsVHVncXhtM2xDOUhzaDM2UFJLNURDUG93eHVUNlgKcXo1M1ZiN2h6TkxLelpiRlJzbUdFOFY2c\ +WM2bXZTbUFXa25nL3QwaStXVmdGVkZuZFQrQ0oyNTJsa0ZacGljdAp6ekdETW44VUNDRUp4TDRKTklT\ +M2lLOUhlRys2MlZuay9QOEM3YVpLSXpVdjFud25rcVdUUUFYWDBKckJGdDdICjI5ZDk1RElmRTRuT0F\ +yS0JFNHc2Z1R4SU1uZzVzWi9ZbDFjcG5wUHlsR3VICi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K") + ("STUN.server" . "") + ("STUN.enable" . "false") + ("SRTP.rtpFallback" . "false") + ("SRTP.keyExchange" . "sdes") + ("SRTP.enable" . "true") + ("RingNS.uri" . "") + ("RingNS.account" . "0790738ce15fa05933b49dd77034312787da86c3") + ("DHT.PublicInCalls" . "true") + ("Account.videoPortMin" . "49152") + ("Account.videoPortMax" . "65534") + ("Account.videoEnabled" . "true") + ("Account.username" . "f3345f2775ddfe07a4b0d95daea111d15fbc1199") + ("Account.useragent" . "") + ("Account.upnpEnabled" . "true") + ("Account.type" . "RING") + ("Account.ringtoneEnabled" . "true") + ("Account.rendezVous" . "true") + ("Account.publishedSameAsLocal" . "true") + ("Account.publishedPort" . "5060") + ("Account.publishedAddress" . "") + ("Account.presenceSubscribeSupported" . "true") + ("Account.peerDiscovery" . "false") + ("Account.managerUsername" . "") + ("Account.managerUri" . "") + ("Account.mailbox" . "") + ("Account.localModeratorsEnabled" . "true") + ("Account.localInterface" . "default") + ("Account.hostname" . "bootstrap.jami.net") + ("Account.hasCustomUserAgent" . "false") + ("Account.enable" . "true") + ("Account.dtmfType" . "overrtp") + ("Account.displayName" . "dummy") + ("Account.defaultModerators" . "") + ("Account.audioPortMin" . "16384") + ("Account.audioPortMax" . "32766") + ("Account.archiveHasPassword" . "false") + ("Account.allowCertFromTrusted" . "true") + ("Account.allowCertFromHistory" . "true") + ("Account.allowCertFromContact" . "true") + ("Account.allModeratorEnabled" . "true") + ("Account.alias" . "dummy") + ("Account.activeCallLimit" . "-1") + ("Account.accountPublish" . "false") + ("Account.accountDiscovery" . "false"))) diff --git a/gnu/tests/telephony.scm b/gnu/tests/telephony.scm new file mode 100644 index 0000000000..1155a9dbc2 --- /dev/null +++ b/gnu/tests/telephony.scm @@ -0,0 +1,366 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2021 Maxim Cournoyer . +;;; +;;; This file is part of GNU Guix. +;;; +;;; GNU Guix is free software; you can redistribute it and/or modify it +;;; under the terms of the GNU General Public License as published by +;;; the Free Software Foundation; either version 3 of the License, or (at +;;; your option) any later version. +;;; +;;; GNU Guix is distributed in the hope that it will be useful, but +;;; WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Guix. If not, see . + +(define-module (gnu tests telephony) + #:use-module (gnu) + #:use-module (gnu packages) + #:use-module (gnu packages guile) + #:use-module (gnu tests) + #:use-module (gnu system vm) + #:use-module (gnu services) + #:use-module (gnu services dbus) + #:use-module (gnu services networking) + #:use-module (gnu services ssh) + #:use-module (gnu services telephony) + #:use-module (guix gexp) + #:use-module (guix modules) + #:export (%test-jami + %test-jami-provisioning)) + +;;; +;;; Jami daemon. +;;; + +(include "data/jami-dummy-account.dat") ;defines %jami-account-content-sexp + +(define %dummy-jami-account-archive + ;; A Jami account archive is a gzipped JSON file. + (computed-file + "dummy-jami-account.gz" + (with-extensions (list guile-json-4 guile-zlib) + #~(begin + (use-modules (json) (zlib)) + (let ((port (open-output-file #$output))) + (call-with-gzip-output-port port + (lambda (port) + (scm->json '#$%jami-account-content-sexp port)))))))) + +(define %allowed-contacts '("1dbcb0f5f37324228235564b79f2b9737e9a008f" + "2dbcb0f5f37324228235564b79f2b9737e9a008f")) + +(define %moderators '("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb")) + +(define %dummy-jami-account (jami-account + (archive %dummy-jami-account-archive) + (allowed-contacts %allowed-contacts) + (moderators %moderators) + (rendezvous-point? #t) + (peer-discovery? #f) + (bootstrap-hostnames '("bootstrap.me" + "fallback.another.host")) + (name-server-uri "https://my.name.server"))) + +(define* (make-jami-os #:key provisioning?) + (operating-system + (host-name "jami") + (timezone "America/Montreal") + (locale "en_US.UTF-8") + + (bootloader (bootloader-configuration + (bootloader grub-bootloader) + (target "/dev/sdX"))) + (file-systems (cons (file-system + (device (file-system-label "my-root")) + (mount-point "/") + (type "ext4")) + %base-file-systems)) + (firmware '()) + + (services (cons* (service jami-service-type + (if provisioning? + (jami-configuration + (debug? #t) + (accounts (list %dummy-jami-account))) + (jami-configuration + (debug? #t)))) + (service dbus-root-service-type) + ;; The following services/packages are added for + ;; debugging purposes. + (service dhcp-client-service-type) + (service openssh-service-type + (openssh-configuration + (permit-root-login #t) + (allow-empty-passwords? #t))) + %base-services)) + (packages (cons* (specification->package "recutils") + (specification->package "strace") + %base-packages)))) + +(define %jami-os + (make-jami-os)) + +(define %jami-os-provisioning + (make-jami-os #:provisioning? #t)) + +(define* (run-jami-test #:key provisioning?) + "Run tests in %JAMI-OS. When PROVISIONING? is true, test the +accounts provisioning feature of the service." + (define os (marionette-operating-system + (if provisioning? + %jami-os-provisioning + %jami-os) + #:imported-modules '((gnu services herd) + (guix combinators)))) + (define vm (virtual-machine + (operating-system os) + (memory-size 512))) + + (define username (assoc-ref %jami-account-content-sexp + "Account.username")) + + (define test + (with-imported-modules (source-module-closure + '((gnu build marionette) + (gnu build jami-service))) + #~(begin + (use-modules (rnrs base) + (srfi srfi-11) + (srfi srfi-64) + (gnu build marionette) + (gnu build jami-service)) + + (define marionette + (make-marionette (list #$vm))) + + (mkdir #$output) + (chdir #$output) + + (test-begin "jami") + + (test-assert "service is running" + (marionette-eval + '(begin + (use-modules (gnu services herd)) + (match (start-service 'jami) + (#f #f) + (('service response-parts ...) + (match (assq-ref response-parts 'running) + ((pid) (number? pid)))))) + marionette)) + + (test-assert "service can be stopped" + (marionette-eval + '(begin + (use-modules (gnu services herd) + (rnrs base)) + (setenv "PATH" "/run/current-system/profile/bin") + (let ((pid (match (start-service 'jami) + (#f #f) + (('service response-parts ...) + (match (assq-ref response-parts 'running) + ((pid) pid)))))) + + (assert (number? pid)) + + (match (stop-service 'jami) + (services ;a list of service symbols + (member 'jami services))) + ;; Sometimes, the process still appear in pgrep, even + ;; though we are using waitpid after sending it SIGTERM + ;; in the service; use retries. + (with-retries 20 1 + (not (zero? (status:exit-val + (system* "pgrep" "dring"))))))) + marionette)) + + (test-assert "service can be restarted" + (marionette-eval + '(begin + (use-modules (gnu services herd) + (rnrs base)) + ;; Start and retrieve the current PID. + (define pid (match (start-service 'jami) + (#f #f) + (('service response-parts ...) + (match (assq-ref response-parts 'running) + ((pid) pid))))) + (assert (number? pid)) + + ;; Restart the service. + (restart-service 'jami) + + (define new-pid (match (start-service 'jami) + (#f #f) + (('service response-parts ...) + (match (assq-ref response-parts 'running) + ((pid) pid))))) + (assert (number? new-pid)) + + (not (eq? pid new-pid))) + marionette)) + + (unless #$provisioning? (test-skip 1)) + (test-assert "jami accounts provisioning, account present" + (marionette-eval + '(begin + (use-modules (gnu services herd) + (rnrs base)) + ;; Accounts take some time to appear after being added. + (with-retries 20 1 + (with-shepherd-action 'jami ('list-accounts) results + (let ((account (assoc-ref (car results) #$username))) + (assert (string=? #$username + (assoc-ref account + "Account.username"))))))) + marionette)) + + (unless #$provisioning? (test-skip 1)) + (test-assert "jami accounts provisioning, allowed-contacts" + (marionette-eval + '(begin + (use-modules (gnu services herd) + (rnrs base) + (srfi srfi-1)) + + ;; Public mode is disabled. + (with-shepherd-action 'jami ('list-account-details) + results + (let ((account (assoc-ref (car results) #$username))) + (assert (string=? "false" + (assoc-ref account + "DHT.PublicInCalls"))))) + + ;; Allowed contacts match those declared in the configuration. + (with-shepherd-action 'jami ('list-contacts) results + (let ((contacts (assoc-ref (car results) #$username))) + (assert (lset= string-ci=? contacts '#$%allowed-contacts))))) + marionette)) + + (unless #$provisioning? (test-skip 1)) + (test-assert "jami accounts provisioning, moderators" + (marionette-eval + '(begin + (use-modules (gnu services herd) + (rnrs base) + (srfi srfi-1)) + + ;; Moderators match those declared in the configuration. + (with-shepherd-action 'jami ('list-moderators) results + (let ((moderators (assoc-ref (car results) #$username))) + (assert (lset= string-ci=? moderators '#$%moderators)))) + + ;; Moderators can be added via the Shepherd action. + (with-shepherd-action 'jami + ('add-moderator "cccccccccccccccccccccccccccccccccccccccc" + #$username) results + (let ((moderators (car results))) + (assert (lset= string-ci=? moderators + (cons "cccccccccccccccccccccccccccccccccccccccc" + '#$%moderators)))))) + marionette)) + + (unless #$provisioning? (test-skip 1)) + (test-assert "jami service actions, ban/unban contacts" + (marionette-eval + '(begin + (use-modules (gnu services herd) + (rnrs base) + (srfi srfi-1)) + + ;; Globally ban a contact. + (with-shepherd-action 'jami + ('ban-contact "1dbcb0f5f37324228235564b79f2b9737e9a008f") _ + (with-shepherd-action 'jami ('list-banned-contacts) results + (every (match-lambda + ((username . banned-contacts) + (member "1dbcb0f5f37324228235564b79f2b9737e9a008f" + banned-contacts))) + (car results)))) + + ;; Ban a contact for a single account. + (with-shepherd-action 'jami + ('ban-contact "dddddddddddddddddddddddddddddddddddddddd" + #$username) _ + (with-shepherd-action 'jami ('list-banned-contacts) results + (every (match-lambda + ((username . banned-contacts) + (let ((found? (member "dddddddddddddddddddddddddddddddddddddddd" + banned-contacts))) + (if (string=? #$username username) + found? + (not found?))))) + (car results))))) + marionette)) + + (unless #$provisioning? (test-skip 1)) + (test-assert "jami service actions, enable/disable accounts" + (marionette-eval + '(begin + (use-modules (gnu services herd) + (rnrs base)) + + (with-shepherd-action 'jami + ('disable-account #$username) _ + (with-shepherd-action 'jami ('list-accounts) results + (let ((account (assoc-ref (car results) #$username))) + (assert (string= "false" + (assoc-ref account "Account.enable")))))) + + (with-shepherd-action 'jami + ('enable-account #$username) _ + (with-shepherd-action 'jami ('list-accounts) results + (let ((account (assoc-ref (car results) #$username))) + (assert (string= "true" + (assoc-ref account "Account.enable"))))))) + marionette)) + + (unless #$provisioning? (test-skip 1)) + (test-assert "jami account parameters" + (marionette-eval + '(begin + (use-modules (gnu services herd) + (rnrs base) + (srfi srfi-1)) + + (with-shepherd-action 'jami ('list-account-details) results + (let ((account-details (assoc-ref (car results) + #$username))) + (assert (lset<= + equal? + '(("Account.hostname" . + "bootstrap.me;fallback.another.host") + ("Account.peerDiscovery" . "false") + ("Account.rendezVous" . "true") + ("RingNS.uri" . "https://my.name.server")) + account-details))))) + marionette)) + + (test-end) + (exit (= (test-runner-fail-count (test-runner-current)) 0))))) + + (gexp->derivation (if provisioning? + "jami-provisioning-test" + "jami-test") + test)) + +(define %test-jami + (system-test + (name "jami") + (description "Basic tests for the jami service.") + (value (run-jami-test)))) + +(define %test-jami-provisioning + (system-test + (name "jami-provisioning") + (description "Provisioning test for the jami service.") + (value (run-jami-test #:provisioning? #t)))) + +;; Local Variables: +;; eval: (put 'with-retries 'scheme-indent-function 2) +;; End: diff --git a/guix/self.scm b/guix/self.scm index 530632db7d..130f5d3492 100644 --- a/guix/self.scm +++ b/guix/self.scm @@ -974,6 +974,8 @@ itself." (list *core-package-modules* *package-modules* *extra-modules* *system-modules* *core-modules* *cli-modules*) ;for (guix scripts pack), etc. + #:extra-files (file-imports source "gnu/tests/data" + (const #t)) #:extensions dependencies #:guile-for-build guile-for-build)) diff --git a/tests/services/telephony.scm b/tests/services/telephony.scm new file mode 100644 index 0000000000..b4a0f120d4 --- /dev/null +++ b/tests/services/telephony.scm @@ -0,0 +1,446 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2021 Maxim Cournoyer +;;; +;;; This file is part of GNU Guix. +;;; +;;; GNU Guix is free software; you can redistribute it and/or modify it +;;; under the terms of the GNU General Public License as published by +;;; the Free Software Foundation; either version 3 of the License, or (at +;;; your option) any later version. +;;; +;;; GNU Guix is distributed in the hope that it will be useful, but +;;; WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with GNU Guix. If not, see . + +(define-module (tests services telephony) + #:use-module (gnu build jami-service) + #:use-module (gnu services telephony) + #:use-module (srfi srfi-64)) + +;;; Tests for the (gnu services telephony) and related modules. + +(test-begin "jami-service") + +(define parse-dbus-reply + (@@ (gnu build jami-service) parse-dbus-reply)) + +(define parse-account-ids + (@@ (gnu build jami-service) parse-account-ids)) + +(define parse-account-details + (@@ (gnu build jami-service) parse-account-details)) + +(define parse-contacts + (@@ (gnu build jami-service) parse-contacts)) + +(define jami-account->alist + (@@ (gnu services telephony) jami-account->alist)) + +;; $ dbus-send --print-reply --dest="cx.ring.Ring" \ +;; "/cx/ring/Ring/ConfigurationManager" \ +;; "cx.ring.Ring.ConfigurationManager.getAccountList" +(define getAccountList-reply "\ +method return time=1622217253.386711 sender=:1.7 -> destination=:1.14 serial=140 reply_serial=2 + array [ + string \"addf37fbb558d6a0\" + string \"d5cbeb7d08c98a65\" + string \"398af0c6b74ce101\" + ] +") + +(test-equal "parse-account-ids" + '("addf37fbb558d6a0" "d5cbeb7d08c98a65" "398af0c6b74ce101") + (parse-account-ids getAccountList-reply)) + +;; $ dbus-send --print-reply --dest="cx.ring.Ring" \ +;; "/cx/ring/Ring/ConfigurationManager" \ +;; "cx.ring.Ring.ConfigurationManager.getAccountDetails" \ +;; 'string:398af0c6b74ce101' +(define getAccountDetails-reply "\ +method return time=1622254991.789588 sender=:1.7 -> destination=:1.19 serial=145 reply_serial=2 + array [ + dict entry( + string \"Account.accountDiscovery\" + string \"false\" + ) + dict entry( + string \"Account.accountPublish\" + string \"false\" + ) + dict entry( + string \"Account.activeCallLimit\" + string \"-1\" + ) + dict entry( + string \"Account.alias\" + string \"some-rendezvous-point-name\" + ) + dict entry( + string \"Account.allModeratorEnabled\" + string \"true\" + ) + dict entry( + string \"Account.allowCertFromContact\" + string \"true\" + ) + dict entry( + string \"Account.allowCertFromHistory\" + string \"true\" + ) + dict entry( + string \"Account.allowCertFromTrusted\" + string \"true\" + ) + dict entry( + string \"Account.archiveHasPassword\" + string \"false\" + ) + dict entry( + string \"Account.audioPortMax\" + string \"32766\" + ) + dict entry( + string \"Account.audioPortMin\" + string \"16384\" + ) + dict entry( + string \"Account.autoAnswer\" + string \"false\" + ) + dict entry( + string \"Account.defaultModerators\" + string \"\" + ) + dict entry( + string \"Account.deviceID\" + string \"94b4070fc7a8afa8482c777a9822c52e6af2e1bd\" + ) + dict entry( + string \"Account.deviceName\" + string \"some-device\" + ) + dict entry( + string \"Account.dhtProxyListUrl\" + string \"https://config.jami.net/proxyList\" + ) + dict entry( + string \"Account.displayName\" + string \"some-rendezvous-point-name\" + ) + dict entry( + string \"Account.dtmfType\" + string \"overrtp\" + ) + dict entry( + string \"Account.enable\" + string \"true\" + ) + dict entry( + string \"Account.hasCustomUserAgent\" + string \"false\" + ) + dict entry( + string \"Account.hostname\" + string \"bootstrap.jami.net\" + ) + dict entry( + string \"Account.localInterface\" + string \"default\" + ) + dict entry( + string \"Account.localModeratorsEnabled\" + string \"true\" + ) + dict entry( + string \"Account.mailbox\" + string \"\" + ) + dict entry( + string \"Account.managerUri\" + string \"\" + ) + dict entry( + string \"Account.managerUsername\" + string \"\" + ) + dict entry( + string \"Account.peerDiscovery\" + string \"false\" + ) + dict entry( + string \"Account.presenceSubscribeSupported\" + string \"true\" + ) + dict entry( + string \"Account.proxyEnabled\" + string \"false\" + ) + dict entry( + string \"Account.proxyPushToken\" + string \"\" + ) + dict entry( + string \"Account.proxyServer\" + string \"dhtproxy.jami.net:[80-95]\" + ) + dict entry( + string \"Account.publishedAddress\" + string \"\" + ) + dict entry( + string \"Account.publishedPort\" + string \"5060\" + ) + dict entry( + string \"Account.publishedSameAsLocal\" + string \"true\" + ) + dict entry( + string \"Account.rendezVous\" + string \"true\" + ) + dict entry( + string \"Account.ringtoneEnabled\" + string \"true\" + ) + dict entry( + string \"Account.ringtonePath\" + string \"/usr/share/ring/ringtones/default.opus\" + ) + dict entry( + string \"Account.type\" + string \"RING\" + ) + dict entry( + string \"Account.upnpEnabled\" + string \"true\" + ) + dict entry( + string \"Account.useragent\" + string \"\" + ) + dict entry( + string \"Account.username\" + string \"ccb8bbe2382343f7feb140710ab48aaf1b55634e\" + ) + dict entry( + string \"Account.videoEnabled\" + string \"true\" + ) + dict entry( + string \"Account.videoPortMax\" + string \"65534\" + ) + dict entry( + string \"Account.videoPortMin\" + string \"49152\" + ) + dict entry( + string \"DHT.PublicInCalls\" + string \"true\" + ) + dict entry( + string \"DHT.port\" + string \"7766\" + ) + dict entry( + string \"RingNS.account\" + string \"3989b55313a911b6f0c004748b49b254f35c9ef6\" + ) + dict entry( + string \"RingNS.uri\" + string \"\" + ) + dict entry( + string \"SRTP.enable\" + string \"true\" + ) + dict entry( + string \"SRTP.keyExchange\" + string \"sdes\" + ) + dict entry( + string \"SRTP.rtpFallback\" + string \"false\" + ) + dict entry( + string \"STUN.enable\" + string \"false\" + ) + dict entry( + string \"STUN.server\" + string \"\" + ) + dict entry( + string \"TLS.certificateFile\" + string \"/var/lib/jami/.local/share/jami/398af0c6b74ce101/ring_device.crt\" + ) + dict entry( + string \"TLS.certificateListFile\" + string \"\" + ) + dict entry( + string \"TLS.ciphers\" + string \"\" + ) + dict entry( + string \"TLS.method\" + string \"Automatic\" + ) + dict entry( + string \"TLS.negotiationTimeoutSec\" + string \"-1\" + ) + dict entry( + string \"TLS.password\" + string \"\" + ) + dict entry( + string \"TLS.privateKeyFile\" + string \"/var/lib/jami/.local/share/jami/398af0c6b74ce101/ring_device.key\" + ) + dict entry( + string \"TLS.requireClientCertificate\" + string \"true\" + ) + dict entry( + string \"TLS.serverName\" + string \"\" + ) + dict entry( + string \"TLS.verifyClient\" + string \"true\" + ) + dict entry( + string \"TLS.verifyServer\" + string \"true\" + ) + dict entry( + string \"TURN.enable\" + string \"true\" + ) + dict entry( + string \"TURN.password\" + string \"ring\" + ) + dict entry( + string \"TURN.realm\" + string \"ring\" + ) + dict entry( + string \"TURN.server\" + string \"turn.jami.net\" + ) + dict entry( + string \"TURN.username\" + string \"ring\" + ) + ] +") + +(test-equal "parse-account-details; username, alias and display name" + '("ccb8bbe2382343f7feb140710ab48aaf1b55634e" ;username + "some-rendezvous-point-name" ;alias + "some-rendezvous-point-name") ;displayName + (let ((account-details (parse-account-details getAccountDetails-reply))) + (list (assoc-ref account-details "Account.username") + (assoc-ref account-details "Account.alias") + (assoc-ref account-details "Account.displayName")))) + +(define getContacts-reply "\ +method return time=1627014042.752673 sender=:1.113 -> destination=:1.186 serial=220 reply_serial=2 + array [ + array [ + dict entry( + string \"added\" + string \"1578883327\" + ) + dict entry( + string \"confirmed\" + string \"true\" + ) + dict entry( + string \"id\" + string \"1c7d5a09464223442549fef172a3cf6f4de9b01c\" + ) + ] + array [ + dict entry( + string \"added\" + string \"1623107941\" + ) + dict entry( + string \"confirmed\" + string \"true\" + ) + dict entry( + string \"id\" + string \"5903c6c9ac5cb863c64e559add3d5d1c8c563449\" + ) + ] + array [ + dict entry( + string \"added\" + string \"1595996256\" + ) + dict entry( + string \"confirmed\" + string \"true\" + ) + dict entry( + string \"id\" + string \"ff2d72a548693214fb3a0f0f7a943b5e2bb9be03\" + ) + ] + ]") + +(test-equal "parse-account-contacts" + '((("added" . "1578883327") + ("confirmed" . "true") + ("id" . "1c7d5a09464223442549fef172a3cf6f4de9b01c")) + (("added" . "1623107941") + ("confirmed" . "true") + ("id" . "5903c6c9ac5cb863c64e559add3d5d1c8c563449")) + (("added" . "1595996256") + ("confirmed" . "true") + ("id" . "ff2d72a548693214fb3a0f0f7a943b5e2bb9be03"))) + (parse-contacts getContacts-reply)) + +(define getContacts-empty-reply "\ +method return time=1627400787.873988 sender=:1.1197 -> destination=:1.1463 serial=2127 reply_serial=2 + array [ + ]") + +(test-equal "parse-account-contacts, empty array" + '() + (parse-contacts getContacts-empty-reply)) + +(define %dummy-jami-account (jami-account + (archive "/tmp/dummy.gz"))) + +(define %dummy-jami-account-2 (jami-account + (archive "/tmp/dummy.gz") + (rendezvous-point? #t) + (peer-discovery? #f) + (bootstrap-hostnames '("bootstrap.me" + "fallback.another.host")) + (name-server-uri "https://my.name.server"))) + +(test-equal "jami-account->alist, no account detail value set" + '() + (jami-account->alist %dummy-jami-account)) + +(test-equal "jami-account->alist, with account detail values" + '(("Account.hostname" . "bootstrap.me;fallback.another.host") + ("Account.peerDiscovery" . "false") + ("Account.rendezVous" . "true") + ("RingNS.uri" . "https://my.name.server")) + (sort (jami-account->alist %dummy-jami-account-2) + (lambda (x y) + (string<=? (car x) (car y))))) + +(test-end) -- cgit v1.2.3 From f71b381d1f467fe7a1b999663ee15ce93fadfa1c Mon Sep 17 00:00:00 2001 From: Efraim Flashner Date: Wed, 4 Aug 2021 16:23:37 +0300 Subject: gnu: samba: Update to 4.13.10. * gnu/packages/samba.scm (samba): Update to 4.13.10. [source]: Remove patch. * gnu/packages/patches/samba-fix-fcntl-hint-detection.patch: Remove file. * gnu/local.mk (dist_patch_DATA): Remove it. --- gnu/local.mk | 1 - .../patches/samba-fix-fcntl-hint-detection.patch | 55 ---------------------- gnu/packages/samba.scm | 5 +- 3 files changed, 2 insertions(+), 59 deletions(-) delete mode 100644 gnu/packages/patches/samba-fix-fcntl-hint-detection.patch (limited to 'gnu/local.mk') diff --git a/gnu/local.mk b/gnu/local.mk index e8494806fd..e6a2f735f9 100644 --- a/gnu/local.mk +++ b/gnu/local.mk @@ -1548,7 +1548,6 @@ dist_patch_DATA = \ %D%/packages/patches/plasma-framework-fix-KF5PlasmaMacros.cmake.patch \ %D%/packages/patches/ppsspp-disable-upgrade-and-gold.patch \ %D%/packages/patches/pthreadpool-system-libraries.patch \ - %D%/packages/patches/samba-fix-fcntl-hint-detection.patch \ %D%/packages/patches/sdcc-disable-non-free-code.patch \ %D%/packages/patches/sdl-pango-api_additions.patch \ %D%/packages/patches/sdl-pango-blit_overflow.patch \ diff --git a/gnu/packages/patches/samba-fix-fcntl-hint-detection.patch b/gnu/packages/patches/samba-fix-fcntl-hint-detection.patch deleted file mode 100644 index b56c628537..0000000000 --- a/gnu/packages/patches/samba-fix-fcntl-hint-detection.patch +++ /dev/null @@ -1,55 +0,0 @@ -From 454ccd986b61799908a6898a55d0480911f15306 Mon Sep 17 00:00:00 2001 -From: Ralph Boehme -Date: Mon, 21 Sep 2020 07:48:43 +0200 -Subject: [PATCH] s3: fix fcntl waf configure check - -RN: Fix fcntl waf configure check -BUG: https://bugzilla.samba.org/show_bug.cgi?id=14503 - -Signed-off-by: Ralph Boehme -Reviewed-by: Volker Lendecke - -Autobuild-User(master): Volker Lendecke -Autobuild-Date(master): Mon Sep 21 07:26:54 UTC 2020 on sn-devel-184 ---- - source3/wscript | 10 +++++----- - 1 file changed, 5 insertions(+), 5 deletions(-) - -diff --git a/source3/wscript b/source3/wscript -index 840ed430c0f..d3ef346eecd 100644 ---- a/source3/wscript -+++ b/source3/wscript -@@ -1244,7 +1244,7 @@ err: - - int main(void) - { -- uint64_t *hint, get_hint; -+ uint64_t hint, get_hint; - int fd; - - fd = open(DATA, O_RDONLY | O_CREAT | O_EXCL); -@@ -1252,8 +1252,8 @@ int main(void) - goto err; - } - -- *hint = RWH_WRITE_LIFE_SHORT; -- int ret = fcntl(fd, F_SET_RW_HINT, hint); -+ hint = RWH_WRITE_LIFE_SHORT; -+ int ret = fcntl(fd, F_SET_RW_HINT, &hint); - if (ret == -1) { - goto err; - } -@@ -1267,8 +1267,8 @@ int main(void) - goto err; - } - -- *hint = RWH_WRITE_LIFE_EXTREME; -- ret = fcntl(fd, F_SET_FILE_RW_HINT, hint); -+ hint = RWH_WRITE_LIFE_EXTREME; -+ ret = fcntl(fd, F_SET_FILE_RW_HINT, &hint); - if (ret == -1) { - goto err; - } --- -2.28.0 - diff --git a/gnu/packages/samba.scm b/gnu/packages/samba.scm index f9b20ccd47..6a1322b4b3 100644 --- a/gnu/packages/samba.scm +++ b/gnu/packages/samba.scm @@ -172,15 +172,14 @@ external dependencies.") (define-public samba (package (name "samba") - (version "4.13.4") + (version "4.13.10") (source (origin (method url-fetch) (uri (string-append "https://download.samba.org/pub/samba/stable/" "samba-" version ".tar.gz")) (sha256 - (base32 "0y2wc7njhyhg055krp878xfv9c3wbhrhzn02d5ich30hyxilrcx1")) - (patches (search-patches "samba-fix-fcntl-hint-detection.patch")) + (base32 "00q5hf2r71dyma785dckcyksv3082mqfgyy9q6k6rc6kqjwkirzh")) (modules '((guix build utils))) (snippet '(begin -- cgit v1.2.3 From 2e0ddc74f6641ec1e286f7f81eaf5bf6644eed52 Mon Sep 17 00:00:00 2001 From: Dmitry Polyakov Date: Wed, 4 Aug 2021 17:29:12 +0500 Subject: gnu: Add instead. * gnu/packages/game-development.scm (instead): New variable. * gnu/packages/patches/instead-use-games-path.patch: New file. * gnu/local.mk (dist_patch_DATA): Register it here. --- gnu/local.mk | 2 + gnu/packages/game-development.scm | 51 +++++++++++++++++++++++ gnu/packages/patches/instead-use-games-path.patch | 32 ++++++++++++++ 3 files changed, 85 insertions(+) create mode 100644 gnu/packages/patches/instead-use-games-path.patch (limited to 'gnu/local.mk') diff --git a/gnu/local.mk b/gnu/local.mk index e6a2f735f9..82e5fbc88f 100644 --- a/gnu/local.mk +++ b/gnu/local.mk @@ -43,6 +43,7 @@ # Copyright © 2021 Philip McGrath # Copyright © 2021 Arun Isaac # Copyright © 2021 Sharlatan Hellseher +# Copyright © 2021 Dmitry Polyakov # # This file is part of GNU Guix. # @@ -1261,6 +1262,7 @@ dist_patch_DATA = \ %D%/packages/patches/imagemagick-WriteTHUMBNAILImage-fix.patch \ %D%/packages/patches/inetutils-hurd.patch \ %D%/packages/patches/inkscape-poppler-0.76.patch \ + %D%/packages/patches/instead-use-games-path.patch \ %D%/packages/patches/inkscape-1.1-fix-build-witch-gcc7.5.patch \ %D%/packages/patches/intel-xed-fix-nondeterminism.patch \ %D%/packages/patches/intltool-perl-compatibility.patch \ diff --git a/gnu/packages/game-development.scm b/gnu/packages/game-development.scm index e1593c0f82..dbe5ac6739 100644 --- a/gnu/packages/game-development.scm +++ b/gnu/packages/game-development.scm @@ -21,6 +21,7 @@ ;;; Copyright © 2020 Timotej Lazar ;;; Copyright © 2020 Giacomo Leidi ;;; Copyright © 2021 Alexandru-Sergiu Marton +;;; Copyright © 2021 Dmitry Polyakov ;;; ;;; This file is part of GNU Guix. ;;; @@ -2115,6 +2116,56 @@ upon which people base their games, ports to new platforms, and other projects.") (license license:gpl2)))) +(define-public instead + (package + (name "instead") + (version "3.3.5") + (build-system cmake-build-system) + (source + (origin + (method git-fetch) + (uri (git-reference + (url "https://github.com/instead-hub/instead") + (commit version))) + (file-name (git-file-name name version)) + (sha256 + (base32 "02j8cw623j51qmr4991i5hsbrzmnp0qfzds8m6nwwr15sjv3hv1g")) + (patches + (search-patches + "instead-use-games-path.patch")) + (modules '((guix build utils))) + (snippet + '(begin + (delete-file-recursively "src/zlib"))))) + (arguments + '(#:configure-flags + (list (string-append + "-DLUA_INCLUDE_DIR=" + (assoc-ref %build-inputs "luajit") "/include/luajit-2.1/") + "-DWITH_LUAJIT=1" + "-DWITH_GTK3=1") + #:tests? #f)) + (inputs + `(("gtk+",gtk+) + ("lua" ,lua) + ("luajit" ,luajit) + ("pkg-config" ,pkg-config) + ("sdl2-images" ,sdl2-image) + ("sdl2-ttf" ,sdl2-ttf) + ("sdl2-mixer" ,sdl2-mixer) + ("zlib" ,zlib))) + (home-page "https://instead3.syscall.ru/") + (synopsis "Text adventure interpreter") + (description "The STEAD (Simple TExt ADventures) interpreter provides +functionality to play games that mix elements of visual novels, interactive +fiction and classic point-and-click adventures.") + (native-search-paths + (list (search-path-specification + (variable "INSTEAD_GAMES_PATH") + (separator #f) ;single entry + (files '("share/instead/games"))))) + (license license:expat))) + (define-public openvr (package (name "openvr") diff --git a/gnu/packages/patches/instead-use-games-path.patch b/gnu/packages/patches/instead-use-games-path.patch new file mode 100644 index 0000000000..783fd21add --- /dev/null +++ b/gnu/packages/patches/instead-use-games-path.patch @@ -0,0 +1,32 @@ +From 8b350daa847dd8e131d9e6b5b4434ce68a7903d0 Mon Sep 17 00:00:00 2001 +From: Dmitry Polyakov +Date: Wed, 4 Aug 2021 15:46:33 +0500 +Subject: [PATCH] Add support for INSTEAD_GAMES_PATH + +In some setups, users might prefer to determine on their own the path in +which games are located. This patch enables them to do so by setting +the “INSTEAD_GAMES_PATH” environmnent variable. +--- + src/main.c | 6 ++++++ + 1 file changed, 6 insertions(+) + +diff --git a/src/main.c b/src/main.c +index ba0ada1d..b05497b8 100644 +--- a/src/main.c ++++ b/src/main.c +@@ -394,6 +394,12 @@ int instead_main(int argc, char *argv[]) + setdir(game_cwd); + profile_load(NULL); + ++ char *gamespath_env = NULL; ++ ++ if (gamespath_env = getenv("INSTEAD_GAMES_PATH"), gamespath_env != NULL) { ++ games_sw = strdup(gamespath_env); ++ } ++ + for (i = 1; i < argc; i++) { + if (lua_sw) /* during load profile */ + break; +-- +2.32.0 + -- cgit v1.2.3 From dd55ad7ff896a0a099c669ef0b420ae2bcdb4498 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Wed, 4 Aug 2021 16:15:17 +0200 Subject: gnu: Add python-pytorch. * gnu/packages/machine-learning.scm (python-pytorch): New variable. * gnu/packages/patches/python-pytorch-runpath.patch, gnu/packages/patches/python-pytorch-system-libraries.patch: New files. * gnu/local.mk (dist_patch_DATA): Add them. --- gnu/local.mk | 2 + gnu/packages/machine-learning.scm | 143 ++++++++++++++++++++- gnu/packages/patches/python-pytorch-runpath.patch | 25 ++++ .../patches/python-pytorch-system-libraries.patch | 131 +++++++++++++++++++ 4 files changed, 300 insertions(+), 1 deletion(-) create mode 100644 gnu/packages/patches/python-pytorch-runpath.patch create mode 100644 gnu/packages/patches/python-pytorch-system-libraries.patch (limited to 'gnu/local.mk') diff --git a/gnu/local.mk b/gnu/local.mk index 82e5fbc88f..51a76e3638 100644 --- a/gnu/local.mk +++ b/gnu/local.mk @@ -1649,6 +1649,8 @@ dist_patch_DATA = \ %D%/packages/patches/python-pydot-regression-test.patch \ %D%/packages/patches/python2-pygobject-2-deprecation.patch \ %D%/packages/patches/python-pygpgme-fix-pinentry-tests.patch \ + %D%/packages/patches/python-pytorch-runpath.patch \ + %D%/packages/patches/python-pytorch-system-libraries.patch \ %D%/packages/patches/python-robotframework-source-date-epoch.patch \ %D%/packages/patches/python-seaborn-kde-test.patch \ %D%/packages/patches/python2-subprocess32-disable-input-test.patch \ diff --git a/gnu/packages/machine-learning.scm b/gnu/packages/machine-learning.scm index c3480d6523..4149defc47 100644 --- a/gnu/packages/machine-learning.scm +++ b/gnu/packages/machine-learning.scm @@ -65,10 +65,12 @@ #:use-module (gnu packages graphviz) #:use-module (gnu packages gstreamer) #:use-module (gnu packages image) + #:use-module (gnu packages libffi) #:use-module (gnu packages linux) #:use-module (gnu packages llvm) #:use-module (gnu packages maths) #:use-module (gnu packages mpi) + #:use-module (gnu packages ninja) #:use-module (gnu packages ocaml) #:use-module (gnu packages onc-rpc) #:use-module (gnu packages parallel) @@ -2546,7 +2548,7 @@ general non-linear dimension reduction.") (package (name "xnnpack") (version (git-version version revision commit)) - (home-page "https://github.com/google/XNNPACK") + (home-page "https://github.com/google/XNNPACK") ;fork of QNNPACK (source (origin (method git-fetch) (uri (git-reference (url home-page) (commit commit))) @@ -2586,3 +2588,142 @@ high-level machine learning frameworks, such as TensorFlow Lite, TensorFlow.js, PyTorch, and MediaPipe.") (license license:bsd-3)))) +(define-public python-pytorch + (package + (name "python-pytorch") + (version "1.9.0") + (source (origin + (method git-fetch) + (uri (git-reference + (url "https://github.com/pytorch/pytorch") + (commit (string-append "v" version)) + (recursive? #t))) + (file-name (git-file-name name version)) + (sha256 + (base32 + "0cznsh68hwk5761gv7iijb4g6jgjpvs3bbixwpzzmkbkbn2q96c1")) + (patches (search-patches "python-pytorch-system-libraries.patch" + "python-pytorch-runpath.patch")) + (modules '((guix build utils))) + (snippet + '(begin + ;; XXX: Let's be clear: this package is a bundling fest. We + ;; delete as much as we can, but there's still a lot left. + (for-each (lambda (directory) + (delete-file-recursively + (string-append "third_party/" directory))) + '("benchmark" "cpuinfo" "eigen" + + ;; FIXME: QNNPACK (of which XNNPACK is a fork) + ;; needs these. + ;; "FP16" "FXdiv" "gemmlowp" "psimd" + + "gloo" "googletest" "ios-cmake" + "onnx" "protobuf" "pthreadpool" + "pybind11" "python-enum" "python-peachpy" + "python-six" "tbb" "XNNPACK" "zstd")) + + ;; Adjust references to the onnx-optimizer headers. + (substitute* "caffe2/onnx/backend.cc" + (("onnx/optimizer/") + "onnxoptimizer/")))))) + (build-system python-build-system) + (arguments + '(#:phases (modify-phases %standard-phases + (add-before 'build 'use-system-libraries + (lambda* (#:key outputs #:allow-other-keys) + ;; Tell 'setup.py' to let 'CMakeLists.txt' know that we + ;; want to use "system libraries" instead of the bundled + ;; ones. + (setenv "USE_SYSTEM_LIBS" "1") + + ;; XXX: Disable that for simplicity for now. + (setenv "USE_FBGEMM" "0") + + ;; Set 'OUT/lib' rather than '$ORIGIN' as the RUNPATH on + ;; binaries that go to 'lib/python3.8/torch/bin' & co. + #;(substitute* "cmake/Dependencies.cmake" + (("^set\\(CMAKE_INSTALL_RPATH .*") + (string-append "set(CMAKE_INSTALL_RPATH \"" + (assoc-ref outputs "out") + "/lib\")\n"))))) + (add-before 'build 'make-things-writable + (lambda _ + ;; The 'build_caffe2' function in + ;; 'tools/build_pytorch_libs.py', called from the + ;; top-level 'setup.py', needs write access to this + ;; directory. + (for-each make-file-writable + (find-files "caffe2/proto" "." + #:directories? #t)))) + (replace 'check + (lambda* (#:key inputs outputs tests? #:allow-other-keys) + ;; Run the test suite following the instructions in + ;; 'CONTRIBUTING.md'. XXX: Unfortunately this doesn't + ;; work, unless you set PYTHONPATH presumably. + (when tests? + (let ((python-site (site-packages inputs outputs))) + (setenv "PYTHONPATH" + (string-append python-site ":" + (getenv "PYTHONPATH"))) + (invoke "python" "test/run_test.py"))))) + (add-after 'install 'remove-test-executables + (lambda* (#:key inputs outputs #:allow-other-keys) + ;; Remove test executables, but keep other executables + ;; such as 'torch_shm_manager' and and .so files such as + ;; 'libtorch_global_deps.so'. + (let ((python-site (site-packages inputs outputs))) + (for-each delete-file + (find-files python-site + "(^test_cpp_rpc|_test)$")))))) + + ;; XXX: Tests attempt to download data such as + ;; . + #:tests? #f)) + (native-inputs + `(("cmake" ,cmake) + ("ninja" ,ninja))) + (inputs + `(("eigen" ,eigen) + ;; ("fmt" ,fmt) + ("fp16" ,fp16) + ("gemmlowp" ,gemmlowp) + ("googletest" ,googletest) + ("googlebenchmark" ,googlebenchmark) + ("gloo" ,gloo) + ("openblas" ,openblas) + ("openmpi" ,openmpi) + ("pthreadpool" ,pthreadpool) + ("protobuf" ,protobuf) + ("pybind11" ,pybind11) + ("sleef" ,sleef) + ("xnnpack" ,xnnpack) + ("zstd" ,zstd))) + (propagated-inputs + `(("python-astunparse" ,python-astunparse) + ("python-numpy" ,python-numpy) + ("python-pyyaml" ,python-pyyaml) + ("python-cffi" ,python-cffi) + ("python-peachpy" ,python-peachpy) + ("python-typing-extensions" ,python-typing-extensions) + ("python-future" ,python-future) + ("python-six" ,python-six) + ("python-requests" ,python-requests) + ("onnx" ,onnx) ;propagated for its Python modules + ("onnx-optimizer" ,onnx-optimizer) + ("cpuinfo" ,cpuinfo))) + (home-page "https://pytorch.org/") + (synopsis "Python library for tensor computation and deep neural networks") + (description + "PyTorch is a Python package that provides two high-level features: + +@itemize +@item tensor computation (like NumPy) with strong GPU acceleration; +@item deep neural networks (DNNs) built on a tape-based autograd system. +@end itemize + +You can reuse Python packages such as NumPy, SciPy, and Cython to extend +PyTorch when needed. + +Note: currently this package does not provide GPU support.") + (license license:bsd-3))) diff --git a/gnu/packages/patches/python-pytorch-runpath.patch b/gnu/packages/patches/python-pytorch-runpath.patch new file mode 100644 index 0000000000..6f270ef9b1 --- /dev/null +++ b/gnu/packages/patches/python-pytorch-runpath.patch @@ -0,0 +1,25 @@ +Libraries (such as 'libtorch_cpu.so') and executables (such as 'torch_shm_manager') +get installed, quite surprisingly, to 'lib/python3.8/site-packages/{bin,lib}'. +Make sure RUNPATH matches that. + +--- a/cmake/Dependencies.cmake ++++ b/cmake/Dependencies.cmake +@@ -4,7 +4,7 @@ if(APPLE) + set(CMAKE_MACOSX_RPATH ON) + set(_rpath_portable_origin "@loader_path") + else() +- set(_rpath_portable_origin $ORIGIN) ++ set(_rpath_portable_origin $ORIGIN/../lib) + endif(APPLE) + # Use separate rpaths during build and install phases + set(CMAKE_SKIP_BUILD_RPATH FALSE) + +--- a/caffe2/CMakeLists.txt ++++ b/caffe2/CMakeLists.txt +@@ -1797,5 +1797,5 @@ if(BUILD_PYTHON) + if(${BUILDING_WITH_TORCH_LIBS}) + # site-packages/caffe2/python/caffe2_pybind11_state + # site-packages/torch/lib +- set(caffe2_pybind11_rpath "${_rpath_portable_origin}/../../torch/lib") ++ set(caffe2_pybind11_rpath $ORIGIN/../../torch/lib) + endif(${BUILDING_WITH_TORCH_LIBS}) diff --git a/gnu/packages/patches/python-pytorch-system-libraries.patch b/gnu/packages/patches/python-pytorch-system-libraries.patch new file mode 100644 index 0000000000..c8d14b3f56 --- /dev/null +++ b/gnu/packages/patches/python-pytorch-system-libraries.patch @@ -0,0 +1,131 @@ +Use our own googletest rather than the bundled one. +Get NNPACK to use our own PeachPy rather than the bundled one. + +diff --git a/cmake/Dependencies.cmake b/cmake/Dependencies.cmake +index 5d57b9ca78..620cca4e60 100644 +--- a/cmake/Dependencies.cmake ++++ b/cmake/Dependencies.cmake +@@ -644,11 +644,6 @@ if(BUILD_TEST OR BUILD_MOBILE_BENCHMARK OR BUILD_MOBILE_TEST) + # this shouldn't be necessary anymore. + get_property(INC_DIR_temp DIRECTORY PROPERTY INCLUDE_DIRECTORIES) + set_property(DIRECTORY PROPERTY INCLUDE_DIRECTORIES "") +- add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../third_party/googletest) +- set_property(DIRECTORY PROPERTY INCLUDE_DIRECTORIES ${INC_DIR_temp}) +- +- include_directories(BEFORE SYSTEM ${CMAKE_CURRENT_LIST_DIR}/../third_party/googletest/googletest/include) +- include_directories(BEFORE SYSTEM ${CMAKE_CURRENT_LIST_DIR}/../third_party/googletest/googlemock/include) + + # We will not need to test benchmark lib itself. + set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "Disable benchmark testing as we don't need it.") +@@ -1485,7 +1480,7 @@ if(CAFFE2_CMAKE_BUILDING_WITH_MAIN_REPO AND NOT INTERN_DISABLE_ONNX) + endif() + set_property(TARGET onnx_proto PROPERTY IMPORTED_LOCATION ${ONNX_PROTO_LIBRARY}) + message("-- Found onnx: ${ONNX_LIBRARY} ${ONNX_PROTO_LIBRARY}") +- list(APPEND Caffe2_DEPENDENCY_LIBS onnx_proto onnx) ++ list(APPEND Caffe2_DEPENDENCY_LIBS onnx_proto onnx onnx_optimizer) + endif() + include_directories(${FOXI_INCLUDE_DIRS}) + list(APPEND Caffe2_DEPENDENCY_LIBS foxi_loader) + +diff --git a/caffe2/CMakeLists.txt b/caffe2/CMakeLists.txt +index 50ebb224ce..5953d9ddf7 100644 +--- a/caffe2/CMakeLists.txt ++++ b/caffe2/CMakeLists.txt +@@ -1632,7 +1632,7 @@ if(BUILD_TEST) + if(NOT MSVC) + add_executable(${test_name}_${CPU_CAPABILITY} "${test_src}" ../aten/src/ATen/native/quantized/affine_quantizer_base.cpp) + # TODO: Get rid of c10 dependency (which is only needed for the implementation of AT_ERROR) +- target_link_libraries(${test_name}_${CPU_CAPABILITY} c10 sleef gtest_main) ++ target_link_libraries(${test_name}_${CPU_CAPABILITY} c10 sleef gtest_main gtest) + if(USE_FBGEMM) + target_link_libraries(${test_name}_${CPU_CAPABILITY} fbgemm) + endif() +@@ -1655,7 +1655,7 @@ if(BUILD_TEST) + foreach(test_src ${Caffe2_CPU_TEST_SRCS}) + get_filename_component(test_name ${test_src} NAME_WE) + add_executable(${test_name} "${test_src}") +- target_link_libraries(${test_name} torch_library gtest_main) ++ target_link_libraries(${test_name} torch_library gtest_main gtest) + target_include_directories(${test_name} PRIVATE $) + target_include_directories(${test_name} PRIVATE $) + target_include_directories(${test_name} PRIVATE ${Caffe2_CPU_INCLUDE}) +@@ -1673,7 +1673,7 @@ if(BUILD_TEST) + foreach(test_src ${Caffe2_GPU_TEST_SRCS}) + get_filename_component(test_name ${test_src} NAME_WE) + cuda_add_executable(${test_name} "${test_src}") +- target_link_libraries(${test_name} torch_library gtest_main) ++ target_link_libraries(${test_name} torch_library gtest_main gtest) + target_include_directories(${test_name} PRIVATE $) + target_include_directories(${test_name} PRIVATE ${Caffe2_CPU_INCLUDE}) + add_test(NAME ${test_name} COMMAND $) +@@ -1691,7 +1691,7 @@ if(BUILD_TEST) + foreach(test_src ${Caffe2_VULKAN_TEST_SRCS}) + get_filename_component(test_name ${test_src} NAME_WE) + add_executable(${test_name} "${test_src}") +- target_link_libraries(${test_name} torch_library gtest_main) ++ target_link_libraries(${test_name} torch_library gtest_main gtest) + target_include_directories(${test_name} PRIVATE $) + target_include_directories(${test_name} PRIVATE ${Caffe2_CPU_INCLUDE}) + add_test(NAME ${test_name} COMMAND $) +@@ -1709,7 +1709,7 @@ if(BUILD_TEST) + foreach(test_src ${Caffe2_HIP_TEST_SRCS}) + get_filename_component(test_name ${test_src} NAME_WE) + add_executable(${test_name} "${test_src}") +- target_link_libraries(${test_name} torch_library gtest_main) ++ target_link_libraries(${test_name} torch_library gtest_main gtest) + target_include_directories(${test_name} PRIVATE $) + target_include_directories(${test_name} PRIVATE ${Caffe2_CPU_INCLUDE} ${Caffe2_HIP_INCLUDE}) + target_compile_options(${test_name} PRIVATE ${HIP_CXX_FLAGS}) + +diff --git a/torch/lib/c10d/test/CMakeLists.txt b/torch/lib/c10d/test/CMakeLists.txt +index b74d4b65f7..fc7c207505 100644 +--- a/torch/lib/c10d/test/CMakeLists.txt ++++ b/torch/lib/c10d/test/CMakeLists.txt +@@ -16,25 +16,25 @@ function(c10d_add_test test_src) + add_test(NAME ${test_name} COMMAND $) + endfunction() + +-c10d_add_test(FileStoreTest.cpp c10d gtest_main) +-c10d_add_test(TCPStoreTest.cpp c10d gtest_main) ++c10d_add_test(FileStoreTest.cpp c10d gtest_main gtest) ++c10d_add_test(TCPStoreTest.cpp c10d gtest_main gtest) + if(NOT WIN32) +- c10d_add_test(HashStoreTest.cpp c10d gtest_main) ++ c10d_add_test(HashStoreTest.cpp c10d gtest_main gtest) + endif() + + if(USE_CUDA) + if(USE_C10D_GLOO) +- c10d_add_test(ProcessGroupGlooTest.cpp c10d c10d_cuda_test gtest_main) +- c10d_add_test(ProcessGroupGlooAsyncTest.cpp c10d c10d_cuda_test gtest_main) ++ c10d_add_test(ProcessGroupGlooTest.cpp c10d c10d_cuda_test gtest_main gtest) ++ c10d_add_test(ProcessGroupGlooAsyncTest.cpp c10d c10d_cuda_test gtest_main gtest) + endif() + if(USE_C10D_NCCL) +- c10d_add_test(ProcessGroupNCCLTest.cpp c10d c10d_cuda_test gtest_main) ++ c10d_add_test(ProcessGroupNCCLTest.cpp c10d c10d_cuda_test gtest_main gtest) + c10d_add_test(ProcessGroupNCCLErrorsTest.cpp c10d c10d_cuda_test +- gtest_main) ++ gtest_main gtest) + endif() + else() + if(USE_C10D_GLOO) +- c10d_add_test(ProcessGroupGlooTest.cpp c10d gtest_main) ++ c10d_add_test(ProcessGroupGlooTest.cpp c10d gtest_main gtest) + endif() + endif() + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 5ecd2df..24feae3 100644 +--- a/third_party/NNPACK/CMakeLists.txt ++++ b/third_party/NNPACK/CMakeLists.txt +@@ -427,8 +427,7 @@ IF(NNPACK_BACKEND STREQUAL "x86-64") + FILE(MAKE_DIRECTORY ${obj_dir}) + ADD_CUSTOM_COMMAND( + OUTPUT ${obj} +- COMMAND "PYTHONPATH=${PEACHPY_PYTHONPATH}" +- ${PYTHON_EXECUTABLE} -m peachpy.x86_64 ++ COMMAND ${PYTHON_EXECUTABLE} -m peachpy.x86_64 + -mabi=sysv -g4 -mimage-format=${PEACHPY_IMAGE_FORMAT} + "-I${PROJECT_SOURCE_DIR}/src" "-I${PROJECT_SOURCE_DIR}/src/x86_64-fma" "-I${FP16_SOURCE_DIR}/include" + -o ${obj} "${PROJECT_SOURCE_DIR}/${src}" -- cgit v1.2.3 From 49432f6534c900e90017726f08cb37397662a9b8 Mon Sep 17 00:00:00 2001 From: Maxim Cournoyer Date: Wed, 4 Aug 2021 14:54:09 -0400 Subject: gnu: pypy3: Update to 7.3.5. The patch disabling tests is removed because it only fixes 2 out of 43 test failures. * gnu/packages/patches/pypy3-7.3.1-fix-tests.patch: Delete file. * gnu/local.mk (dist_patch_DATA): De-register it. * gnu/packages/python.scm (pypy3): Update to 7.3.5. [patches]: Remove patch. --- gnu/local.mk | 1 - gnu/packages/patches/pypy3-7.3.1-fix-tests.patch | 278 ----------------------- gnu/packages/python.scm | 11 +- 3 files changed, 5 insertions(+), 285 deletions(-) delete mode 100644 gnu/packages/patches/pypy3-7.3.1-fix-tests.patch (limited to 'gnu/local.mk') diff --git a/gnu/local.mk b/gnu/local.mk index 51a76e3638..61ac39618a 100644 --- a/gnu/local.mk +++ b/gnu/local.mk @@ -1657,7 +1657,6 @@ dist_patch_DATA = \ %D%/packages/patches/python-unittest2-python3-compat.patch \ %D%/packages/patches/python-unittest2-remove-argparse.patch \ %D%/packages/patches/python-waitress-fix-tests.patch \ - %D%/packages/patches/pypy3-7.3.1-fix-tests.patch \ %D%/packages/patches/qemu-build-info-manual.patch \ %D%/packages/patches/qemu-CVE-2021-20203.patch \ %D%/packages/patches/qemu-meson-compat.patch \ diff --git a/gnu/packages/patches/pypy3-7.3.1-fix-tests.patch b/gnu/packages/patches/pypy3-7.3.1-fix-tests.patch deleted file mode 100644 index 464aad967f..0000000000 --- a/gnu/packages/patches/pypy3-7.3.1-fix-tests.patch +++ /dev/null @@ -1,278 +0,0 @@ -Fix a few testcases. Adapted from python-3-fix-tests.patch. - -diff -Naur pypy3.6-v7.3.1-src.orig/lib-python/3/ctypes/test/test_callbacks.py pypy3.6-v7.3.1-src/lib-python/3/ctypes/test/test_callbacks.py ---- pypy3.6-v7.3.1-src.orig/lib-python/3/ctypes/test/test_callbacks.py 1970-01-01 01:00:01.000000000 +0100 -+++ pypy3.6-v7.3.1-src/lib-python/3/ctypes/test/test_callbacks.py 2020-05-21 14:19:14.827288853 +0200 -@@ -4,6 +4,7 @@ - from ctypes import * - from ctypes.test import need_symbol - import _ctypes_test -+import platform - - class Callbacks(unittest.TestCase): - functype = CFUNCTYPE -@@ -178,6 +179,8 @@ - - self.assertLess(diff, 0.01, "%s not less than 0.01" % diff) - -+ @unittest.skipIf(platform.machine() in ['mips64'], -+ "This test fails on this platform") - def test_issue_8959_a(self): - from ctypes.util import find_library - libc_path = find_library("c") -diff -Naur pypy3.6-v7.3.1-src.orig/lib-python/3/ctypes/test/test_libc.py pypy3.6-v7.3.1-src/lib-python/3/ctypes/test/test_libc.py ---- pypy3.6-v7.3.1-src.orig/lib-python/3/ctypes/test/test_libc.py 1970-01-01 01:00:01.000000000 +0100 -+++ pypy3.6-v7.3.1-src/lib-python/3/ctypes/test/test_libc.py 2020-05-21 14:19:14.827288853 +0200 -@@ -2,6 +2,7 @@ - - from ctypes import * - import _ctypes_test -+import platform - - lib = CDLL(_ctypes_test.__file__) - -@@ -17,6 +18,8 @@ - import math - self.assertEqual(lib.my_sqrt(2.0), math.sqrt(2.0)) - -+ @unittest.skipIf(platform.machine() in ['mips64'], -+ "This test fails on this platform") - def test_qsort(self): - comparefunc = CFUNCTYPE(c_int, POINTER(c_char), POINTER(c_char)) - lib.my_qsort.argtypes = c_void_p, c_size_t, c_size_t, comparefunc -diff -Naur pypy3.6-v7.3.1-src.orig/lib-python/3/distutils/tests/test_archive_util.py pypy3.6-v7.3.1-src/lib-python/3/distutils/tests/test_archive_util.py ---- pypy3.6-v7.3.1-src.orig/lib-python/3/distutils/tests/test_archive_util.py 1970-01-01 01:00:01.000000000 +0100 -+++ pypy3.6-v7.3.1-src/lib-python/3/distutils/tests/test_archive_util.py 2020-05-21 14:19:14.827288853 +0200 -@@ -333,6 +333,7 @@ - self.assertEqual(os.path.basename(res), 'archive.tar.xz') - self.assertEqual(self._tarinfo(res), self._created_files) - -+ @unittest.skipIf(True, "getgrgid(0)[0] raises a KeyError on Guix") - def test_make_archive_owner_group(self): - # testing make_archive with owner and group, with various combinations - # this works even if there's not gid/uid support -@@ -362,6 +363,7 @@ - - @unittest.skipUnless(ZLIB_SUPPORT, "Requires zlib") - @unittest.skipUnless(UID_GID_SUPPORT, "Requires grp and pwd support") -+ @unittest.skipIf(True, "getgrgid(0)[0] raises a KeyError on Guix") - def test_tarfile_root_owner(self): - tmpdir = self._create_files() - base_name = os.path.join(self.mkdtemp(), 'archive') -diff -Naur pypy3.6-v7.3.1-src.orig/lib-python/3/distutils/tests/test_sdist.py pypy3.6-v7.3.1-src/lib-python/3/distutils/tests/test_sdist.py ---- pypy3.6-v7.3.1-src.orig/lib-python/3/distutils/tests/test_sdist.py 1970-01-01 01:00:01.000000000 +0100 -+++ pypy3.6-v7.3.1-src/lib-python/3/distutils/tests/test_sdist.py 2020-05-21 14:19:14.827288853 +0200 -@@ -443,6 +443,7 @@ - "The tar command is not found") - @unittest.skipIf(find_executable('gzip') is None, - "The gzip command is not found") -+ @unittest.skipIf(True, "getgrgid(0)[0] raises a KeyError on Guix") - def test_make_distribution_owner_group(self): - # now building a sdist - dist, cmd = self.get_cmd() -diff -Naur pypy3.6-v7.3.1-src.orig/lib-python/3/test/test_asyncio/test_base_events.py pypy3.6-v7.3.1-src/lib-python/3/test/test_asyncio/test_base_events.py ---- pypy3.6-v7.3.1-src.orig/lib-python/3/test/test_asyncio/test_base_events.py 1970-01-01 01:00:01.000000000 +0100 -+++ pypy3.6-v7.3.1-src/lib-python/3/test/test_asyncio/test_base_events.py 2020-05-21 14:19:14.827288853 +0200 -@@ -1296,6 +1296,8 @@ - self._test_create_connection_ip_addr(m_socket, False) - - @patch_socket -+ @unittest.skipUnless(support.is_resource_enabled('network'), -+ 'network is not enabled') - def test_create_connection_service_name(self, m_socket): - m_socket.getaddrinfo = socket.getaddrinfo - sock = m_socket.socket.return_value -diff -Naur pypy3.6-v7.3.1-src.orig/lib-python/3/test/test_generators.py pypy3.6-v7.3.1-src/lib-python/3/test/test_generators.py ---- pypy3.6-v7.3.1-src.orig/lib-python/3/test/test_generators.py 1970-01-01 01:00:01.000000000 +0100 -+++ pypy3.6-v7.3.1-src/lib-python/3/test/test_generators.py 2020-05-21 14:19:14.827288853 +0200 -@@ -35,6 +35,7 @@ - else: - return "FAILED" - -+ @unittest.skipIf(True, 'Keyboard interrupts do not work in the Guix build environment') - def test_raise_and_yield_from(self): - gen = self.generator1() - gen.send(None) -diff -Naur pypy3.6-v7.3.1-src.orig/lib-python/3/test/_test_multiprocessing.py pypy3.6-v7.3.1-src/lib-python/3/test/_test_multiprocessing.py ---- pypy3.6-v7.3.1-src.orig/lib-python/3/test/_test_multiprocessing.py 1970-01-01 01:00:01.000000000 +0100 -+++ pypy3.6-v7.3.1-src/lib-python/3/test/_test_multiprocessing.py 2020-05-21 14:19:14.827288853 +0200 -@@ -1212,6 +1212,7 @@ - if pid is not None: - os.kill(pid, signal.SIGINT) - -+ @unittest.skipIf(True, "This fails for unknown reasons on Guix") - def test_wait_result(self): - if isinstance(self, ProcessesMixin) and sys.platform != 'win32': - pid = os.getpid() -diff -Naur pypy3.6-v7.3.1-src.orig/lib-python/3/test/test_normalization.py pypy3.6-v7.3.1-src/lib-python/3/test/test_normalization.py ---- pypy3.6-v7.3.1-src.orig/lib-python/3/test/test_normalization.py 1970-01-01 01:00:01.000000000 +0100 -+++ pypy3.6-v7.3.1-src/lib-python/3/test/test_normalization.py 2020-05-21 14:19:14.827288853 +0200 -@@ -2,6 +2,7 @@ - import unittest - - from http.client import HTTPException -+from urllib.error import URLError - import sys - from unicodedata import normalize, unidata_version - -@@ -43,6 +44,8 @@ - except PermissionError: - self.skipTest(f"Permission error when downloading {TESTDATAURL} " - f"into the test data directory") -+ except URLError: -+ self.skipTest("DNS lookups are not enabled.") - except (OSError, HTTPException): - self.fail(f"Could not retrieve {TESTDATAURL}") - -diff -Naur pypy3.6-v7.3.1-src.orig/lib-python/3/test/test_pathlib.py pypy3.6-v7.3.1-src/lib-python/3/test/test_pathlib.py ---- pypy3.6-v7.3.1-src.orig/lib-python/3/test/test_pathlib.py 1970-01-01 01:00:01.000000000 +0100 -+++ pypy3.6-v7.3.1-src/lib-python/3/test/test_pathlib.py 2020-05-21 14:19:14.827288853 +0200 -@@ -2130,8 +2130,7 @@ - self.assertEqual(given, expect) - self.assertEqual(set(p.rglob("FILEd*")), set()) - -- @unittest.skipUnless(hasattr(pwd, 'getpwall'), -- 'pwd module does not expose getpwall()') -+ @unittest.skipIf(True, "Guix builder home is '/' which causes trouble for these tests") - def test_expanduser(self): - P = self.cls - support.import_module('pwd') -diff -Naur pypy3.6-v7.3.1-src.orig/lib-python/3/test/test_pdb.py pypy3.6-v7.3.1-src/lib-python/3/test/test_pdb.py ---- pypy3.6-v7.3.1-src.orig/lib-python/3/test/test_pdb.py 1970-01-01 01:00:01.000000000 +0100 -+++ pypy3.6-v7.3.1-src/lib-python/3/test/test_pdb.py 2020-05-21 14:20:24.377203281 +0200 -@@ -1136,11 +1136,11 @@ - > (6)test_function() - -> print('pdb %d: %s' % (i, sess._previous_sigint_handler)) - (Pdb) continue -- pdb 1: -+ pdb 1: Handlers.SIG_IGN - > (6)test_function() - -> print('pdb %d: %s' % (i, sess._previous_sigint_handler)) - (Pdb) continue -- pdb 2: -+ pdb 2: Handlers.SIG_IGN - """ - - class PdbTestCase(unittest.TestCase): -diff -Naur pypy3.6-v7.3.1-src.orig/lib-python/3/test/test_regrtest.py pypy3.6-v7.3.1-src/lib-python/3/test/test_regrtest.py ---- pypy3.6-v7.3.1-src.orig/lib-python/3/test/test_regrtest.py 1970-01-01 01:00:01.000000000 +0100 -+++ pypy3.6-v7.3.1-src/lib-python/3/test/test_regrtest.py 2020-05-21 14:19:14.827288853 +0200 -@@ -766,6 +766,7 @@ - output = self.run_tests('--fromfile', filename) - self.check_executed_tests(output, tests) - -+ @unittest.skipIf(True, 'Keyboard interrupts do not work in the Guix build environment.') - def test_interrupted(self): - code = TEST_INTERRUPTED - test = self.create_test('sigint', code=code) -diff -Naur pypy3.6-v7.3.1-src.orig/lib-python/3/test/test_resource.py pypy3.6-v7.3.1-src/lib-python/3/test/test_resource.py ---- pypy3.6-v7.3.1-src.orig/lib-python/3/test/test_resource.py 1970-01-01 01:00:01.000000000 +0100 -+++ pypy3.6-v7.3.1-src/lib-python/3/test/test_resource.py 2020-05-21 14:19:14.827288853 +0200 -@@ -146,6 +146,7 @@ - - @unittest.skipUnless(hasattr(resource, 'prlimit'), 'no prlimit') - @support.requires_linux_version(2, 6, 36) -+ @unittest.skipIf(True, "Bug: the PermissionError is not raised") - def test_prlimit(self): - self.assertRaises(TypeError, resource.prlimit) - self.assertRaises(ProcessLookupError, resource.prlimit, -diff -Naur pypy3.6-v7.3.1-src.orig/lib-python/3/test/test_shutil.py pypy3.6-v7.3.1-src/lib-python/3/test/test_shutil.py ---- pypy3.6-v7.3.1-src.orig/lib-python/3/test/test_shutil.py 1970-01-01 01:00:01.000000000 +0100 -+++ pypy3.6-v7.3.1-src/lib-python/3/test/test_shutil.py 2020-05-21 14:19:14.827288853 +0200 -@@ -1138,6 +1138,7 @@ - self.assertRaises(ValueError, make_archive, base_name, 'xxx') - - @support.requires_zlib -+ @unittest.skipIf(True, "getgrgid(0)[0] raises a KeyError on Guix") - def test_make_archive_owner_group(self): - # testing make_archive with owner and group, with various combinations - # this works even if there's not gid/uid support -@@ -1166,6 +1167,7 @@ - - - @support.requires_zlib -+ @unittest.skipIf(True, "getgrgid(0)[0] raises a KeyError on Guix") - @unittest.skipUnless(UID_GID_SUPPORT, "Requires grp and pwd support") - def test_tarfile_root_owner(self): - root_dir, base_dir = self._create_files() -diff -Naur pypy3.6-v7.3.1-src.orig/lib-python/3/test/test_socket.py pypy3.6-v7.3.1-src/lib-python/3/test/test_socket.py ---- pypy3.6-v7.3.1-src.orig/lib-python/3/test/test_socket.py 1970-01-01 01:00:01.000000000 +0100 -+++ pypy3.6-v7.3.1-src/lib-python/3/test/test_socket.py 2020-05-21 14:19:14.827288853 +0200 -@@ -815,6 +815,8 @@ - if not fqhn in all_host_names: - self.fail("Error testing host resolution mechanisms. (fqdn: %s, all: %s)" % (fqhn, repr(all_host_names))) - -+ @unittest.skipUnless(support.is_resource_enabled('network'), -+ 'network is not enabled') - def test_host_resolution(self): - for addr in [support.HOST, '10.0.0.1', '255.255.255.255']: - self.assertEqual(socket.gethostbyname(addr), addr) -@@ -934,6 +936,8 @@ - self.assertRaises(OverflowError, socket.htonl, k) - self.assertRaises(OverflowError, socket.htons, k) - -+ @unittest.skipUnless(os.path.exists("/etc/services"), -+ "getservbyname uses /etc/services, which is not in the chroot") - def testGetServBy(self): - eq = self.assertEqual - # Find one service that exists, then check all the related interfaces. -@@ -1278,6 +1282,8 @@ - raise - self.assertRaises(TypeError, s.ioctl, socket.SIO_LOOPBACK_FAST_PATH, None) - -+ @unittest.skipUnless(os.path.exists("/etc/gai.conf"), -+ "getaddrinfo() will fail") - def testGetaddrinfo(self): - try: - socket.getaddrinfo('localhost', 80) -@@ -1357,6 +1363,8 @@ - # only IP addresses are allowed - self.assertRaises(OSError, socket.getnameinfo, ('mail.python.org',0), 0) - -+ @unittest.skipUnless(os.path.exists("/etc/gai.conf"), -+ "getaddrinfo() will fail") - @unittest.skipUnless(support.is_resource_enabled('network'), - 'network is not enabled') - def test_idna(self): -diff -Naur pypy3.6-v7.3.1-src.orig/lib-python/3/test/test_spwd.py pypy3.6-v7.3.1-src/lib-python/3/test/test_spwd.py ---- pypy3.6-v7.3.1-src.orig/lib-python/3/test/test_spwd.py 1970-01-01 01:00:01.000000000 +0100 -+++ pypy3.6-v7.3.1-src/lib-python/3/test/test_spwd.py 2020-05-21 14:19:14.827288853 +0200 -@@ -5,8 +5,7 @@ - spwd = support.import_module('spwd') - - --@unittest.skipUnless(hasattr(os, 'geteuid') and os.geteuid() == 0, -- 'root privileges required') -+@unittest.skipUnless(os.path.exists("/etc/shadow"), 'spwd tests require /etc/shadow') - class TestSpwdRoot(unittest.TestCase): - - def test_getspall(self): -@@ -56,8 +55,7 @@ - self.assertRaises(TypeError, spwd.getspnam, bytes_name) - - --@unittest.skipUnless(hasattr(os, 'geteuid') and os.geteuid() != 0, -- 'non-root user required') -+@unittest.skipUnless(os.path.exists("/etc/shadow"), 'spwd tests require /etc/shadow') - class TestSpwdNonRoot(unittest.TestCase): - - def test_getspnam_exception(self): -diff -Naur pypy3.6-v7.3.1-src.orig/lib-python/3/test/test_tarfile.py pypy3.6-v7.3.1-src/lib-python/3/test/test_tarfile.py ---- pypy3.6-v7.3.1-src.orig/lib-python/3/test/test_tarfile.py 1970-01-01 01:00:01.000000000 +0100 -+++ pypy3.6-v7.3.1-src/lib-python/3/test/test_tarfile.py 2020-05-21 14:19:14.827288853 +0200 -@@ -2491,9 +2491,12 @@ - import pwd, grp - except ImportError: - return False -- if pwd.getpwuid(0)[0] != 'root': -- return False -- if grp.getgrgid(0)[0] != 'root': -+ try: -+ if pwd.getpwuid(0)[0] != 'root': -+ return False -+ if grp.getgrgid(0)[0] != 'root': -+ return False -+ except KeyError: - return False - return True - diff --git a/gnu/packages/python.scm b/gnu/packages/python.scm index e694321e17..5cc02d5ba3 100644 --- a/gnu/packages/python.scm +++ b/gnu/packages/python.scm @@ -716,15 +716,14 @@ ease from the desktop to a microcontroller or embedded system.") (define-public pypy3 (package (name "pypy3") - (version "7.3.1") + (version "7.3.5") (source (origin (method url-fetch) - (uri (string-append "https://bitbucket.org/pypy/pypy/downloads/" ; - "pypy3.6-v" version "-src.tar.bz2")) + (uri (string-append "https://downloads.python.org/pypy/" + "pypy3.7-v" version "-src.tar.bz2")) (sha256 (base32 - "10zsk8jby8j6visk5mzikpb1cidvz27qq4pfpa26jv53klic6b0c")) - (patches (search-patches "pypy3-7.3.1-fix-tests.patch")))) + "18lrdmpcczlbk3cfarkgwqdmilrybz56i1dafk8dkjlyk90gw86r")))) (build-system gnu-build-system) (native-inputs `(("python-2" ,python-2) @@ -749,7 +748,7 @@ ease from the desktop to a microcontroller or embedded system.") ("bash-minimal" ,bash-minimal) ; Used as /bin/sh ("xz" ,xz))) ; liblzma (arguments - `(#:tests? #f ;FIXME: Disabled for now, there are many tests failing. + `(#:tests? #f ;FIXME: 43 out of 364 tests are failing #:modules ((ice-9 ftw) (ice-9 match) (guix build utils) (guix build gnu-build-system)) #:phases (modify-phases %standard-phases -- cgit v1.2.3 From 1273548f4facefa379ae3607b47891959c42c13d Mon Sep 17 00:00:00 2001 From: Sarah Morgensen Date: Thu, 5 Aug 2021 13:20:07 -0700 Subject: gnu: restic: Patch tests for go-1.16. * gnu/packages/patches/restic-0.9.6-fix-tests-for-go1.15.patch: New file. * gnu/local.mk (dist_patch_DATA): Register it. * gnu/packages/backup.scm (restic): Use it. Signed-off-by: Maxim Cournoyer --- gnu/local.mk | 1 + gnu/packages/backup.scm | 5 ++- .../restic-0.9.6-fix-tests-for-go1.15.patch | 51 ++++++++++++++++++++++ 3 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 gnu/packages/patches/restic-0.9.6-fix-tests-for-go1.15.patch (limited to 'gnu/local.mk') diff --git a/gnu/local.mk b/gnu/local.mk index 61ac39618a..caa9090bbd 100644 --- a/gnu/local.mk +++ b/gnu/local.mk @@ -1693,6 +1693,7 @@ dist_patch_DATA = \ %D%/packages/patches/rtags-separate-rct.patch \ %D%/packages/patches/racket-minimal-sh-via-rktio.patch \ %D%/packages/patches/remake-impure-dirs.patch \ + %D%/packages/patches/restic-0.9.6-fix-tests-for-go1.15.patch \ %D%/packages/patches/retroarch-LIBRETRO_DIRECTORY.patch \ %D%/packages/patches/rnp-add-version.cmake.patch \ %D%/packages/patches/rnp-disable-ruby-rnp-tests.patch \ diff --git a/gnu/packages/backup.scm b/gnu/packages/backup.scm index 9ec4e281d8..d8d8728a14 100644 --- a/gnu/packages/backup.scm +++ b/gnu/packages/backup.scm @@ -20,6 +20,7 @@ ;;; Copyright © 2020 Michael Rohleder ;;; Copyright © 2021 Timothy Sample ;;; Copyright © 2021 Brice Waegeneire +;;; Copyright © 2021 Sarah Morgensen ;;; ;;; This file is part of GNU Guix. ;;; @@ -928,7 +929,9 @@ is like a time machine for your data. ") (file-name (string-append name "-" version ".tar.gz")) (sha256 (base32 - "1zmh42aah32ah8w5n6ilz9bci0y2xrf8p7qshy3yf1lzm5gnbj0w")))) + "1zmh42aah32ah8w5n6ilz9bci0y2xrf8p7qshy3yf1lzm5gnbj0w")) + (patches + (search-patches "restic-0.9.6-fix-tests-for-go1.15.patch")))) (build-system go-build-system) (arguments `(#:import-path "github.com/restic/restic" diff --git a/gnu/packages/patches/restic-0.9.6-fix-tests-for-go1.15.patch b/gnu/packages/patches/restic-0.9.6-fix-tests-for-go1.15.patch new file mode 100644 index 0000000000..cc510c1cfe --- /dev/null +++ b/gnu/packages/patches/restic-0.9.6-fix-tests-for-go1.15.patch @@ -0,0 +1,51 @@ +This cherry-picked patch fixes tests for Go >= 1.15. Restic v0.10 (which +includes this patch) requires go module support from the Go build system. +Original patch follows. + +--- +From 97950ab81a18de06b95384da6d8646fef87c9d97 Mon Sep 17 00:00:00 2001 +From: Alexander Neumann +Date: Sat, 12 Sep 2020 17:36:44 +0200 +Subject: [PATCH] options: Fix test for Go >= 1.15 + +--- + internal/options/options_test.go | 12 +++++++++--- + 1 file changed, 9 insertions(+), 3 deletions(-) + +diff --git a/internal/options/options_test.go b/internal/options/options_test.go +index de94fc90a1..8d268992a3 100644 +--- a/internal/options/options_test.go ++++ b/internal/options/options_test.go +@@ -3,6 +3,7 @@ package options + import ( + "fmt" + "reflect" ++ "regexp" + "testing" + "time" + ) +@@ -199,7 +200,7 @@ var invalidSetTests = []struct { + "timeout": "2134", + }, + "ns", +- `time: missing unit in duration 2134`, ++ `time: missing unit in duration "?2134"?`, + }, + } + +@@ -212,8 +213,13 @@ func TestOptionsApplyInvalid(t *testing.T) { + t.Fatalf("expected error %v not found", test.err) + } + +- if err.Error() != test.err { +- t.Fatalf("expected error %q, got %q", test.err, err.Error()) ++ matched, err := regexp.MatchString(test.err, err.Error()) ++ if err != nil { ++ t.Fatal(err) ++ } ++ ++ if !matched { ++ t.Fatalf("expected error to match %q, got %q", test.err, err.Error()) + } + }) + } -- cgit v1.2.3 From c5ee2cf72a72624eff4dfbdf4670851c6075191e Mon Sep 17 00:00:00 2001 From: Lars-Dominik Braun Date: Fri, 6 Aug 2021 08:18:26 +0200 Subject: gnu: Add llvm-for-rocm. * gnu/packages/rocm.scm (llvm-for-rocm): New variable. * gnu/packages/patches/llvm-roc-3.0.0-add_libraries.patch, gnu/packages/patches/llvm-roc-4.0.0-remove-isystem-usr-include.patch, gnu/packages/patches/llvm-roc-4.2.0-add_Object.patch: New files. * gnu/local.mk (dist_patch_DATA): Add them. --- gnu/local.mk | 3 ++ .../patches/llvm-roc-3.0.0-add_libraries.patch | 22 ++++++++++++++ ...llvm-roc-4.0.0-remove-isystem-usr-include.patch | 29 ++++++++++++++++++ .../patches/llvm-roc-4.2.0-add_Object.patch | 13 ++++++++ gnu/packages/rocm.scm | 35 ++++++++++++++++++++++ 5 files changed, 102 insertions(+) create mode 100644 gnu/packages/patches/llvm-roc-3.0.0-add_libraries.patch create mode 100644 gnu/packages/patches/llvm-roc-4.0.0-remove-isystem-usr-include.patch create mode 100644 gnu/packages/patches/llvm-roc-4.2.0-add_Object.patch (limited to 'gnu/local.mk') diff --git a/gnu/local.mk b/gnu/local.mk index caa9090bbd..59dc7b01b5 100644 --- a/gnu/local.mk +++ b/gnu/local.mk @@ -1416,6 +1416,9 @@ dist_patch_DATA = \ %D%/packages/patches/llvm-9-fix-bitcast-miscompilation.patch \ %D%/packages/patches/llvm-9-fix-lpad-miscompilation.patch \ %D%/packages/patches/llvm-9-fix-scev-miscompilation.patch \ + %D%/packages/patches/llvm-roc-3.0.0-add_libraries.patch \ + %D%/packages/patches/llvm-roc-4.0.0-remove-isystem-usr-include.patch \ + %D%/packages/patches/llvm-roc-4.2.0-add_Object.patch \ %D%/packages/patches/lm-sensors-hwmon-attrs.patch \ %D%/packages/patches/lrcalc-includes.patch \ %D%/packages/patches/lsh-fix-x11-forwarding.patch \ diff --git a/gnu/packages/patches/llvm-roc-3.0.0-add_libraries.patch b/gnu/packages/patches/llvm-roc-3.0.0-add_libraries.patch new file mode 100644 index 0000000000..f6bc639f33 --- /dev/null +++ b/gnu/packages/patches/llvm-roc-3.0.0-add_libraries.patch @@ -0,0 +1,22 @@ +Taken from https://gitweb.gentoo.org/repo/gentoo.git/tree/sys-devel/llvm-roc/files + +diff -Naur a/clang/lib/Basic/CMakeLists.txt b/clang/lib/Basic/CMakeLists.txt +--- a/clang/lib/Basic/CMakeLists.txt 2019-12-28 07:42:49.119055933 +0100 ++++ b/clang/lib/Basic/CMakeLists.txt 2019-12-28 07:42:13.265056070 +0100 +@@ -2,6 +2,7 @@ + Core + MC + Support ++ Option + ) + + find_first_existing_vc_file("${LLVM_MAIN_SRC_DIR}" llvm_vc) +diff -Naur a/clang/lib/Driver/CMakeLists.txt b/clang/lib/Driver/CMakeLists.txt +--- a/clang/lib/Driver/CMakeLists.txt 2019-12-28 07:41:39.521056199 +0100 ++++ b/clang/lib/Driver/CMakeLists.txt 2019-12-28 07:40:23.998056487 +0100 +@@ -79,4 +79,5 @@ + LINK_LIBS + clangBasic + ${system_libs} ++ pthread + ) diff --git a/gnu/packages/patches/llvm-roc-4.0.0-remove-isystem-usr-include.patch b/gnu/packages/patches/llvm-roc-4.0.0-remove-isystem-usr-include.patch new file mode 100644 index 0000000000..f14ec4ac0d --- /dev/null +++ b/gnu/packages/patches/llvm-roc-4.0.0-remove-isystem-usr-include.patch @@ -0,0 +1,29 @@ +Author: Wilfried (justxi) Holzke + +Adopted from https://github.com/justxi/rocm/blob/master/sys-devel/llvm-roc/files/llvm-roc-4.0.0-remove-isystem-usr-include.patch + +Index: llvm-project-rocm-4.0.0/clang/lib/Driver/ToolChains/AMDGPU.cpp +=================================================================== +--- llvm-project-rocm-4.0.0.orig/clang/lib/Driver/ToolChains/AMDGPU.cpp ++++ llvm-project-rocm-4.0.0/clang/lib/Driver/ToolChains/AMDGPU.cpp +@@ -326,11 +326,6 @@ void RocmInstallationDetector::AddHIPInc + // + // ROCm 3.5 does not fully support the wrapper headers. Therefore it needs + // a workaround. +- SmallString<128> P(D.ResourceDir); +- if (UsesRuntimeWrapper) +- llvm::sys::path::append(P, "include", "cuda_wrappers"); +- CC1Args.push_back("-internal-isystem"); +- CC1Args.push_back(DriverArgs.MakeArgString(P)); + } + + if (DriverArgs.hasArg(options::OPT_nogpuinc)) +@@ -341,8 +336,6 @@ void RocmInstallationDetector::AddHIPInc + return; + } + +- CC1Args.push_back("-internal-isystem"); +- CC1Args.push_back(DriverArgs.MakeArgString(getIncludePath())); + if (UsesRuntimeWrapper) + CC1Args.append({"-include", "__clang_hip_runtime_wrapper.h"}); + } diff --git a/gnu/packages/patches/llvm-roc-4.2.0-add_Object.patch b/gnu/packages/patches/llvm-roc-4.2.0-add_Object.patch new file mode 100644 index 0000000000..f1762a558b --- /dev/null +++ b/gnu/packages/patches/llvm-roc-4.2.0-add_Object.patch @@ -0,0 +1,13 @@ +Taken from https://gitweb.gentoo.org/repo/gentoo.git/tree/sys-devel/llvm-roc/files + +diff --color -uprN orig/lib/Target/AMDGPU/Disassembler/CMakeLists.txt llvm/lib/Target/AMDGPU/Disassembler/CMakeLists.txt +--- a/llvm/lib/Target/AMDGPU/Disassembler/CMakeLists.txt 2021-06-14 11:57:54.222796911 +0800 ++++ b/llvm/lib/Target/AMDGPU/Disassembler/CMakeLists.txt 2021-06-14 11:58:35.206796875 +0800 +@@ -11,6 +11,7 @@ add_llvm_component_library(LLVMAMDGPUDis + MC + MCDisassembler + Support ++ Object + + ADD_TO_COMPONENT + AMDGPU diff --git a/gnu/packages/rocm.scm b/gnu/packages/rocm.scm index b04eb8050e..17685c7bcb 100644 --- a/gnu/packages/rocm.scm +++ b/gnu/packages/rocm.scm @@ -55,3 +55,38 @@ (description "ROCm cmake modules provides cmake modules for common build tasks needed for the ROCM software stack.") (license license:ncsa))) + +(define-public llvm-for-rocm + (hidden-package + (package + ;; Actually based on LLVM 13 as of v4.3, but llvm-12 works just fine. + (inherit llvm-12) + (name "llvm-for-rocm") + (version %rocm-version) + (source (origin + (method git-fetch) + (uri (git-reference + (url "https://github.com/RadeonOpenCompute/llvm-project.git") + (commit (string-append "rocm-" version)))) + (file-name (git-file-name name version)) + (sha256 + (base32 + "0p75nr1qpmy6crymdax5hm40wkimman4lnglz4x5cnbiqindya7s")) + (patches + (search-patches "llvm-roc-4.2.0-add_Object.patch" + "llvm-roc-3.0.0-add_libraries.patch" + "llvm-roc-4.0.0-remove-isystem-usr-include.patch")))) + (arguments + (substitute-keyword-arguments (package-arguments llvm-12) + ((#:phases phases '%standard-phases) + `(modify-phases ,phases + (add-after 'unpack 'chdir + (lambda _ + (chdir "llvm"))))) + ((#:configure-flags flags) + ''("-DLLVM_ENABLE_PROJECTS=llvm;clang;lld" + "-DLLVM_TARGETS_TO_BUILD=AMDGPU;X86" + "-DCMAKE_SKIP_BUILD_RPATH=FALSE" + "-DCMAKE_BUILD_WITH_INSTALL_RPATH=FALSE" + "-DBUILD_SHARED_LIBS:BOOL=TRUE" + "-DLLVM_VERSION_SUFFIX="))))))) -- cgit v1.2.3 From 9c083e706329909d41ab13d0721e726031ab78c7 Mon Sep 17 00:00:00 2001 From: Lars-Dominik Braun Date: Fri, 6 Aug 2021 08:21:20 +0200 Subject: gnu: Add rocm-comgr. * gnu/packages/rocm.scm (rocm-comgr): New variable. * gnu/packages/patches/rocm-comgr-3.1.0-dependencies.patch: New file. * gnu/local.mk (dist_patch_DATA): Add it. --- gnu/local.mk | 1 + .../patches/rocm-comgr-3.1.0-dependencies.patch | 52 ++++++++++++++++++++++ gnu/packages/rocm.scm | 32 +++++++++++++ 3 files changed, 85 insertions(+) create mode 100644 gnu/packages/patches/rocm-comgr-3.1.0-dependencies.patch (limited to 'gnu/local.mk') diff --git a/gnu/local.mk b/gnu/local.mk index 59dc7b01b5..d545db39dd 100644 --- a/gnu/local.mk +++ b/gnu/local.mk @@ -1701,6 +1701,7 @@ dist_patch_DATA = \ %D%/packages/patches/rnp-add-version.cmake.patch \ %D%/packages/patches/rnp-disable-ruby-rnp-tests.patch \ %D%/packages/patches/rnp-unbundle-googletest.patch \ + %D%/packages/patches/rocm-comgr-3.1.0-dependencies.patch \ %D%/packages/patches/ruby-sanitize-system-libxml.patch \ %D%/packages/patches/rust-1.19-mrustc.patch \ %D%/packages/patches/rust-1.25-accept-more-detailed-gdb-lines.patch \ diff --git a/gnu/packages/patches/rocm-comgr-3.1.0-dependencies.patch b/gnu/packages/patches/rocm-comgr-3.1.0-dependencies.patch new file mode 100644 index 0000000000..c91d273f92 --- /dev/null +++ b/gnu/packages/patches/rocm-comgr-3.1.0-dependencies.patch @@ -0,0 +1,52 @@ +https://github.com/RadeonOpenCompute/ROCm-CompilerSupport/pull/25 + +From c65cba2e73f9118e128b9ab7e655ee0f8a7798e7 Mon Sep 17 00:00:00 2001 +From: Craig Andrews +Date: Sun, 1 Mar 2020 19:24:22 -0500 +Subject: [PATCH] Link additional required LLVM libraries + +Without these additional required dependencies, linking fails with errors such as: +`undefined reference to llvm::errs()'` +--- + CMakeLists.txt | 20 ++++++++++++++++++-- + 1 file changed, 18 insertions(+), 2 deletions(-) + +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 1794a07..c7b852a 100644 +--- a/lib/comgr/CMakeLists.txt ++++ b/lib/comgr/CMakeLists.txt +@@ -207,7 +207,11 @@ install(FILES + DESTINATION "${AMD_COMGR_PACKAGE_PREFIX}") + + set(CLANG_LIBS +- clangFrontendTool) ++ clangFrontendTool ++ clangFrontend ++ clangBasic ++ clangDriver ++ clangSerialization) + + set(LLD_LIBS + lldELF +@@ -218,8 +222,20 @@ if (LLVM_LINK_LLVM_DYLIB) + else() + llvm_map_components_to_libnames(LLVM_LIBS + ${LLVM_TARGETS_TO_BUILD} ++ Option + DebugInfoDWARF +- Symbolize) ++ Symbolize ++ Support ++ Object ++ BitWriter ++ MC ++ MCParser ++ MCDisassembler ++ Core ++ IRReader ++ CodeGen ++ Linker ++ BinaryFormat) + endif() + + target_link_libraries(amd_comgr diff --git a/gnu/packages/rocm.scm b/gnu/packages/rocm.scm index 48d709969e..5ecc741d11 100644 --- a/gnu/packages/rocm.scm +++ b/gnu/packages/rocm.scm @@ -115,3 +115,35 @@ tasks needed for the ROCM software stack.") (description "AMD-specific device-side language runtime libraries, namely oclc, ocml, ockl, opencl, hip and hc.") (license license:ncsa))) + +(define-public rocm-comgr + (package + (name "rocm-comgr") + (version %rocm-version) + (source (origin + (method git-fetch) + (uri (git-reference + (url "https://github.com/RadeonOpenCompute/ROCm-CompilerSupport.git") + (commit (string-append "rocm-" version)))) + (file-name (git-file-name name version)) + (sha256 + (base32 + "0bakbm7shr0l67lph44b5cnc9psd6rivg1mp79qizaawkn380x60")) + (patches + (search-patches "rocm-comgr-3.1.0-dependencies.patch")))) + (build-system cmake-build-system) + (arguments + `(#:phases + (modify-phases %standard-phases + (add-after 'unpack 'chdir + (lambda _ + (chdir "lib/comgr")))))) + (inputs + `(("rocm-device-libs" ,rocm-device-libs) + ("llvm" ,llvm-for-rocm) + ("lld" ,lld))) + (home-page "https://github.com/RadeonOpenCompute/ROCm-CompilerSupport") + (synopsis "ROCm Code Object Manager") + (description "The Comgr library provides APIs for compiling and inspecting +AMDGPU code objects.") + (license license:ncsa))) -- cgit v1.2.3 From c0f10a5dd4be70a990223b707be1067f6982c4ae Mon Sep 17 00:00:00 2001 From: Lars-Dominik Braun Date: Fri, 6 Aug 2021 08:27:32 +0200 Subject: gnu: Add rocm-opencl-runtime. * gnu/packages/rocm.scm (rocm-opencl-runtime): New variable. * gnu/packages/patches/rocm-opencl-runtime-3.10.0-add-rocclr-include-directories.patch, gnu/packages/patches/rocm-opencl-runtime-4.3-noclinfo.patch, gnu/packages/patches/rocm-opencl-runtime-4.3-nocltrace.patch, gnu/packages/patches/rocm-opencl-runtime-4.3-noopencl.patch: New files. * gnu/local.mk (dist_patch_DATA): Add them. --- gnu/local.mk | 4 ++ ...ime-3.10.0-add-rocclr-include-directories.patch | 14 +++++ .../patches/rocm-opencl-runtime-4.3-noclinfo.patch | 28 ++++++++++ .../rocm-opencl-runtime-4.3-nocltrace.patch | 25 +++++++++ .../patches/rocm-opencl-runtime-4.3-noopencl.patch | 63 ++++++++++++++++++++++ gnu/packages/rocm.scm | 51 ++++++++++++++++++ 6 files changed, 185 insertions(+) create mode 100644 gnu/packages/patches/rocm-opencl-runtime-3.10.0-add-rocclr-include-directories.patch create mode 100644 gnu/packages/patches/rocm-opencl-runtime-4.3-noclinfo.patch create mode 100644 gnu/packages/patches/rocm-opencl-runtime-4.3-nocltrace.patch create mode 100644 gnu/packages/patches/rocm-opencl-runtime-4.3-noopencl.patch (limited to 'gnu/local.mk') diff --git a/gnu/local.mk b/gnu/local.mk index d545db39dd..3f09b167b1 100644 --- a/gnu/local.mk +++ b/gnu/local.mk @@ -1702,6 +1702,10 @@ dist_patch_DATA = \ %D%/packages/patches/rnp-disable-ruby-rnp-tests.patch \ %D%/packages/patches/rnp-unbundle-googletest.patch \ %D%/packages/patches/rocm-comgr-3.1.0-dependencies.patch \ + %D%/packages/patches/rocm-opencl-runtime-3.10.0-add-rocclr-include-directories.patch \ + %D%/packages/patches/rocm-opencl-runtime-4.3-noclinfo.patch \ + %D%/packages/patches/rocm-opencl-runtime-4.3-nocltrace.patch \ + %D%/packages/patches/rocm-opencl-runtime-4.3-noopencl.patch \ %D%/packages/patches/ruby-sanitize-system-libxml.patch \ %D%/packages/patches/rust-1.19-mrustc.patch \ %D%/packages/patches/rust-1.25-accept-more-detailed-gdb-lines.patch \ diff --git a/gnu/packages/patches/rocm-opencl-runtime-3.10.0-add-rocclr-include-directories.patch b/gnu/packages/patches/rocm-opencl-runtime-3.10.0-add-rocclr-include-directories.patch new file mode 100644 index 0000000000..d81bb0747f --- /dev/null +++ b/gnu/packages/patches/rocm-opencl-runtime-3.10.0-add-rocclr-include-directories.patch @@ -0,0 +1,14 @@ +Add missing include dirs for ROCclr. + +--- a/amdocl/CMakeLists.txt 2020-12-05 22:05:55.838529158 +0100 ++++ b/amdocl/CMakeLists.txt 2020-12-05 22:07:35.677524507 +0100 +@@ -23,6 +23,9 @@ + include_directories(${CMAKE_CURRENT_LIST_DIR}/../khronos) + include_directories(${CMAKE_CURRENT_LIST_DIR}/../khronos/headers) + include_directories(${CMAKE_CURRENT_LIST_DIR}/../khronos/headers/opencl2.2) ++include_directories(${ROCclr_DIR}/../../../include) ++include_directories(${ROCclr_DIR}/../../../include/compiler/lib/include/) ++include_directories(${ROCclr_DIR}/../../../include/elf/) + + if((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR + (CMAKE_${COMPILER}_COMPILER_ID MATCHES "Clang")) diff --git a/gnu/packages/patches/rocm-opencl-runtime-4.3-noclinfo.patch b/gnu/packages/patches/rocm-opencl-runtime-4.3-noclinfo.patch new file mode 100644 index 0000000000..5709e0d19a --- /dev/null +++ b/gnu/packages/patches/rocm-opencl-runtime-4.3-noclinfo.patch @@ -0,0 +1,28 @@ +Do not build and install clinfo. + +diff --git a/CMakeLists.txt.orig b/CMakeLists.txt +index 76847d3..3f62bfe 100644 +--- a/CMakeLists.txt.orig ++++ b/CMakeLists.txt +@@ -72,7 +72,7 @@ find_package(ROCclr REQUIRED CONFIG + + add_subdirectory(khronos/icd) + add_subdirectory(amdocl) +-add_subdirectory(tools/clinfo) ++#add_subdirectory(tools/clinfo) + add_subdirectory(tools/cltrace) + if(BUILD_TESTS) + add_subdirectory(tests/ocltst) +@@ -108,9 +108,9 @@ endif() + ###--- Packaging ------------------------------------------------------------### + + # MAIN package +-install(PROGRAMS $ +- DESTINATION bin +- COMPONENT MAIN) ++#install(PROGRAMS $ ++# DESTINATION bin ++# COMPONENT MAIN) + install(PROGRAMS $ + DESTINATION lib + COMPONENT MAIN) diff --git a/gnu/packages/patches/rocm-opencl-runtime-4.3-nocltrace.patch b/gnu/packages/patches/rocm-opencl-runtime-4.3-nocltrace.patch new file mode 100644 index 0000000000..e0328d7734 --- /dev/null +++ b/gnu/packages/patches/rocm-opencl-runtime-4.3-nocltrace.patch @@ -0,0 +1,25 @@ +Do not build and install cltrace. + +diff --git a/CMakeLists.txt.orig b/CMakeLists.txt +index c449db4..9cff673 100644 +--- a/CMakeLists.txt.orig ++++ b/CMakeLists.txt +@@ -73,7 +73,7 @@ find_package(ROCclr REQUIRED CONFIG + #add_subdirectory(khronos/icd) + add_subdirectory(amdocl) + #add_subdirectory(tools/clinfo) +-add_subdirectory(tools/cltrace) ++#add_subdirectory(tools/cltrace) + if(BUILD_TESTS) + add_subdirectory(tests/ocltst) + endif() +@@ -108,9 +108,6 @@ endif() + #install(PROGRAMS $ + # DESTINATION bin + # COMPONENT MAIN) +-install(PROGRAMS $ +- DESTINATION lib +- COMPONENT MAIN) + install(PROGRAMS $ + DESTINATION lib + COMPONENT MAIN) diff --git a/gnu/packages/patches/rocm-opencl-runtime-4.3-noopencl.patch b/gnu/packages/patches/rocm-opencl-runtime-4.3-noopencl.patch new file mode 100644 index 0000000000..ed20f02503 --- /dev/null +++ b/gnu/packages/patches/rocm-opencl-runtime-4.3-noopencl.patch @@ -0,0 +1,63 @@ +Do not build and install libOpenCL. + +--- b/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -70,7 +70,7 @@ + ${ROCclr_DIR} + ${LIBROCclr_STATIC_DIR}) + +-add_subdirectory(khronos/icd) ++#add_subdirectory(khronos/icd) + add_subdirectory(amdocl) + #add_subdirectory(tools/clinfo) + add_subdirectory(tools/cltrace) +@@ -93,9 +93,6 @@ + OUTPUT_VARIABLE OPENCL_VERSION_GITDATE + OUTPUT_STRIP_TRAILING_WHITESPACE) + +-get_target_property(OPENCL_LIB_VERSION_MAJOR OpenCL SOVERSION) +-get_target_property(OPENCL_LIB_VERSION_STRING OpenCL VERSION) +- + find_package(ROCM QUIET CONFIG PATHS /opt/rocm) + + if(ROCM_FOUND) +@@ -117,25 +114,6 @@ + install(PROGRAMS $ + DESTINATION lib + COMPONENT MAIN) +-install(PROGRAMS $ +- DESTINATION lib +- COMPONENT MAIN) +-install(PROGRAMS $ +- DESTINATION lib +- COMPONENT MAIN) +- +-# DEV package +-install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/khronos/headers/opencl2.2/CL" +- DESTINATION include +- COMPONENT DEV +- USE_SOURCE_PERMISSIONS +- PATTERN cl_d3d10.h EXCLUDE +- PATTERN cl_d3d11.h EXCLUDE +- PATTERN cl_dx9_media_sharing.h EXCLUDE +- PATTERN cl_egl.h EXCLUDE) +-install(PROGRAMS $ +- DESTINATION lib +- COMPONENT DEV) + + ############################# + # Packaging steps +--- a/khronos/icd/CMakeLists.txt 2020-06-07 16:05:32.425022904 +0200 ++++ b/khronos/icd/CMakeLists.txt 2020-06-07 16:06:03.273022786 +0200 +@@ -132,7 +132,7 @@ + add_subdirectory (test) + endif() + +-install (TARGETS OpenCL +- RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +- ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} +- LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) ++#install (TARGETS OpenCL ++# RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} ++# ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} ++# LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) diff --git a/gnu/packages/rocm.scm b/gnu/packages/rocm.scm index 631d8336c0..07e395c9f0 100644 --- a/gnu/packages/rocm.scm +++ b/gnu/packages/rocm.scm @@ -253,3 +253,54 @@ applications to launch compute kernels to available HSA ROCm kernel agents.") interact with to different backends such as ROCr or PAL. This abstraction allows runtimes to work on Windows as well as on Linux without much effort.") (license license:ncsa))) + +(define-public rocm-opencl-runtime + (package + (name "rocm-opencl-runtime") + (version %rocm-version) + (source (origin + (method git-fetch) + (uri (git-reference + (url "https://github.com/RadeonOpenCompute/ROCm-OpenCL-Runtime.git") + (commit (string-append "rocm-" version)))) + (file-name (git-file-name name version)) + (sha256 + (base32 + "1cglpiaj3ny1z74ssmy6j63vj92sfy4q38ix6qsga0mg3b2wvqz3")) + (patches + (search-patches + "rocm-opencl-runtime-3.10.0-add-rocclr-include-directories.patch" + ;; Do not install libOpenCL, which ocl-icd provides. + "rocm-opencl-runtime-4.3-noopencl.patch" + ;; Guix includes a program clinfo already. + "rocm-opencl-runtime-4.3-noclinfo.patch" + ;; cltrace linking fails, remove it. + "rocm-opencl-runtime-4.3-nocltrace.patch")))) + (build-system cmake-build-system) + (arguments + `(#:tests? #f ; Not sure how to run them. + #:phases + (modify-phases %standard-phases + (add-after 'install 'create-icd + ;; Manually install ICD, which simply consists of dumping + ;; the path of the .so into the correct file. + (lambda* (#:key outputs #:allow-other-keys) + (let* ((out (assoc-ref outputs "out")) + (vendors (string-append out "/etc/OpenCL/vendors")) + (sopath (string-append out "/lib/libamdocl64.so"))) + (mkdir-p vendors) + (with-output-to-file (string-append vendors "/amdocl64.icd") + (lambda _ (display sopath))))))))) + (inputs + `(("mesa" ,mesa) + ("rocm-comgr" ,rocm-comgr) + ("rocr-runtime" ,rocr-runtime) + ("rocclr" ,rocclr) + ("ocl-icd" ,ocl-icd) + ("glew" ,glew))) + (native-inputs `()) + (home-page "https://github.com/RadeonOpenCompute/ROCm-OpenCL-Runtime") + (synopsis "ROCm OpenCL Runtime") + (description "OpenCL 2.0 compatible language runtime, supporting offline +and in-process/in-memory compilation.") + (license license:ncsa))) -- cgit v1.2.3 From 168d107abf5608e6a1bfb37ee3448c4087856602 Mon Sep 17 00:00:00 2001 From: Ludovic Courtès Date: Tue, 10 Aug 2021 23:51:06 +0200 Subject: gnu: rocm-opencl-runtime: Shorten patch file name. This appeases 'guix lint -c patch-file-names'. * gnu/packages/patches/rocm-opencl-runtime-3.10.0-add-rocclr-include-directories.patch: Rename to... * gnu/packages/patches/rocm-opencl-runtime-3.10.0-includes.patch: ... this. * gnu/packages/rocm.scm (rocm-opencl-runtime)[source]: Adjust accordingly. * gnu/local.mk (dist_patch_DATA): Likewise. --- gnu/local.mk | 2 +- ...ncl-runtime-3.10.0-add-rocclr-include-directories.patch | 14 -------------- .../patches/rocm-opencl-runtime-3.10.0-includes.patch | 14 ++++++++++++++ gnu/packages/rocm.scm | 2 +- 4 files changed, 16 insertions(+), 16 deletions(-) delete mode 100644 gnu/packages/patches/rocm-opencl-runtime-3.10.0-add-rocclr-include-directories.patch create mode 100644 gnu/packages/patches/rocm-opencl-runtime-3.10.0-includes.patch (limited to 'gnu/local.mk') diff --git a/gnu/local.mk b/gnu/local.mk index 3f09b167b1..32ac19d20e 100644 --- a/gnu/local.mk +++ b/gnu/local.mk @@ -1702,7 +1702,7 @@ dist_patch_DATA = \ %D%/packages/patches/rnp-disable-ruby-rnp-tests.patch \ %D%/packages/patches/rnp-unbundle-googletest.patch \ %D%/packages/patches/rocm-comgr-3.1.0-dependencies.patch \ - %D%/packages/patches/rocm-opencl-runtime-3.10.0-add-rocclr-include-directories.patch \ + %D%/packages/patches/rocm-opencl-runtime-3.10.0-includes.patch \ %D%/packages/patches/rocm-opencl-runtime-4.3-noclinfo.patch \ %D%/packages/patches/rocm-opencl-runtime-4.3-nocltrace.patch \ %D%/packages/patches/rocm-opencl-runtime-4.3-noopencl.patch \ diff --git a/gnu/packages/patches/rocm-opencl-runtime-3.10.0-add-rocclr-include-directories.patch b/gnu/packages/patches/rocm-opencl-runtime-3.10.0-add-rocclr-include-directories.patch deleted file mode 100644 index d81bb0747f..0000000000 --- a/gnu/packages/patches/rocm-opencl-runtime-3.10.0-add-rocclr-include-directories.patch +++ /dev/null @@ -1,14 +0,0 @@ -Add missing include dirs for ROCclr. - ---- a/amdocl/CMakeLists.txt 2020-12-05 22:05:55.838529158 +0100 -+++ b/amdocl/CMakeLists.txt 2020-12-05 22:07:35.677524507 +0100 -@@ -23,6 +23,9 @@ - include_directories(${CMAKE_CURRENT_LIST_DIR}/../khronos) - include_directories(${CMAKE_CURRENT_LIST_DIR}/../khronos/headers) - include_directories(${CMAKE_CURRENT_LIST_DIR}/../khronos/headers/opencl2.2) -+include_directories(${ROCclr_DIR}/../../../include) -+include_directories(${ROCclr_DIR}/../../../include/compiler/lib/include/) -+include_directories(${ROCclr_DIR}/../../../include/elf/) - - if((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR - (CMAKE_${COMPILER}_COMPILER_ID MATCHES "Clang")) diff --git a/gnu/packages/patches/rocm-opencl-runtime-3.10.0-includes.patch b/gnu/packages/patches/rocm-opencl-runtime-3.10.0-includes.patch new file mode 100644 index 0000000000..d81bb0747f --- /dev/null +++ b/gnu/packages/patches/rocm-opencl-runtime-3.10.0-includes.patch @@ -0,0 +1,14 @@ +Add missing include dirs for ROCclr. + +--- a/amdocl/CMakeLists.txt 2020-12-05 22:05:55.838529158 +0100 ++++ b/amdocl/CMakeLists.txt 2020-12-05 22:07:35.677524507 +0100 +@@ -23,6 +23,9 @@ + include_directories(${CMAKE_CURRENT_LIST_DIR}/../khronos) + include_directories(${CMAKE_CURRENT_LIST_DIR}/../khronos/headers) + include_directories(${CMAKE_CURRENT_LIST_DIR}/../khronos/headers/opencl2.2) ++include_directories(${ROCclr_DIR}/../../../include) ++include_directories(${ROCclr_DIR}/../../../include/compiler/lib/include/) ++include_directories(${ROCclr_DIR}/../../../include/elf/) + + if((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR + (CMAKE_${COMPILER}_COMPILER_ID MATCHES "Clang")) diff --git a/gnu/packages/rocm.scm b/gnu/packages/rocm.scm index 8d6e3e69e2..66a28fc169 100644 --- a/gnu/packages/rocm.scm +++ b/gnu/packages/rocm.scm @@ -270,7 +270,7 @@ allows runtimes to work on Windows as well as on Linux without much effort.") "1cglpiaj3ny1z74ssmy6j63vj92sfy4q38ix6qsga0mg3b2wvqz3")) (patches (search-patches - "rocm-opencl-runtime-3.10.0-add-rocclr-include-directories.patch" + "rocm-opencl-runtime-3.10.0-includes.patch" ;; Do not install libOpenCL, which ocl-icd provides. "rocm-opencl-runtime-4.3-noopencl.patch" ;; Guix includes a program clinfo already. -- cgit v1.2.3