diff options
author | Fabio Natali <me@fabionatali.com> | 2024-10-15 16:31:40 +0100 |
---|---|---|
committer | Arun Isaac <arunisaac@systemreboot.net> | 2024-10-18 20:56:02 +0100 |
commit | 8c6d24d388bcd72b595b5293c7afc6e06bde941b (patch) | |
tree | 57546a703fc37360fa01c662cb5876700113af4b /gnu | |
parent | 061e0acd596262420facef7c2d1fc9cc4327d75a (diff) | |
download | guix-8c6d24d388bcd72b595b5293c7afc6e06bde941b.tar guix-8c6d24d388bcd72b595b5293c7afc6e06bde941b.tar.gz |
gnu: services: Add readymedia service.
* gnu/services/upnp.scm, gnu/tests/upnp.scm: New files.
* gnu/local.mk (GNU_SYSTEM_MODULES): Add them.
* doc/guix.texi (Miscellaneous Services): Document the service.
Change-Id: I6a3c9db9e7504df308038343ed48e4409a323581
Signed-off-by: Arun Isaac <arunisaac@systemreboot.net>
Diffstat (limited to 'gnu')
-rw-r--r-- | gnu/local.mk | 2 | ||||
-rw-r--r-- | gnu/services/upnp.scm | 207 | ||||
-rw-r--r-- | gnu/tests/upnp.scm | 155 |
3 files changed, 364 insertions, 0 deletions
diff --git a/gnu/local.mk b/gnu/local.mk index 29d76e7bce..81031c9bdd 100644 --- a/gnu/local.mk +++ b/gnu/local.mk @@ -756,6 +756,7 @@ GNU_SYSTEM_MODULES = \ %D%/services/syncthing.scm \ %D%/services/sysctl.scm \ %D%/services/telephony.scm \ + %D%/services/upnp.scm \ %D%/services/version-control.scm \ %D%/services/vnc.scm \ %D%/services/vpn.scm \ @@ -846,6 +847,7 @@ GNU_SYSTEM_MODULES = \ %D%/tests/singularity.scm \ %D%/tests/ssh.scm \ %D%/tests/telephony.scm \ + %D%/tests/upnp.scm \ %D%/tests/version-control.scm \ %D%/tests/virtualization.scm \ %D%/tests/vnc.scm \ diff --git a/gnu/services/upnp.scm b/gnu/services/upnp.scm new file mode 100644 index 0000000000..09121326fe --- /dev/null +++ b/gnu/services/upnp.scm @@ -0,0 +1,207 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2024 Fabio Natali <me@fabionatali.com> +;;; +;;; 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 <http://www.gnu.org/licenses/>. + +(define-module (gnu services upnp) + #:use-module (gnu build linux-container) + #:use-module (gnu packages admin) + #:use-module (gnu packages upnp) + #:use-module (gnu services admin) + #:use-module (gnu services base) + #:use-module (gnu services shepherd) + #:use-module (gnu services) + #:use-module (gnu system file-systems) + #:use-module (gnu system shadow) + #:use-module (guix gexp) + #:use-module (guix least-authority) + #:use-module (guix modules) + #:use-module (guix records) + #:use-module (ice-9 match) + #:export (%readymedia-default-cache-directory + %readymedia-default-log-directory + %readymedia-default-port + %readymedia-log-file + %readymedia-user-account + %readymedia-user-group + readymedia-configuration + readymedia-configuration? + readymedia-configuration-readymedia + readymedia-configuration-port + readymedia-configuration-cache-directory + readymedia-configuration-extra-config + readymedia-configuration-friendly-name + readymedia-configuration-log-directory + readymedia-configuration-media-directories + readymedia-media-directory + readymedia-media-directory-path + readymedia-media-directory-types + readymedia-media-directory? + readymedia-service-type)) + +;;; Commentary: +;;; +;;; UPnP services. +;;; +;;; Code: + +(define %readymedia-default-cache-directory "/var/cache/readymedia") +(define %readymedia-default-log-directory "/var/log/readymedia") +(define %readymedia-log-file "minidlna.log") +(define %readymedia-user-group "readymedia") +(define %readymedia-user-account "readymedia") + +(define-record-type* <readymedia-configuration> + readymedia-configuration make-readymedia-configuration + readymedia-configuration? + (readymedia readymedia-configuration-readymedia + (default readymedia)) + (port readymedia-configuration-port + (default #f)) + (cache-directory readymedia-configuration-cache-directory + (default %readymedia-default-cache-directory)) + (log-directory readymedia-configuration-log-directory + (default %readymedia-default-log-directory)) + (friendly-name readymedia-configuration-friendly-name + (default #f)) + (media-directories readymedia-configuration-media-directories) + (extra-config readymedia-configuration-extra-config + (default '()))) + +;; READYMEDIA-MEDIA-DIR is a record that indicates the path of a media folder +;; and the types of media included within it. Allowed individual types are the +;; symbols 'A' for audio, 'V' for video, and 'P' for pictures. The types field +;; can contain any combination of individual types; an empty list means that +;; no type is specified. +(define-record-type* <readymedia-media-directory> + readymedia-media-directory make-readymedia-media-directory + readymedia-media-directory? + (path readymedia-media-directory-path) + (types readymedia-media-directory-types + (default '()))) + +(define (readymedia-configuration->config-file config) + "Return the ReadyMedia/MiniDLNA configuration file corresponding to CONFIG." + (match-record config <readymedia-configuration> + (port friendly-name cache-directory log-directory media-directories extra-config) + (apply mixed-text-file + "minidlna.conf" + "db_dir=" cache-directory "\n" + "log_dir=" log-directory "\n" + (if friendly-name + (string-append "friendly_name=" friendly-name "\n") + "") + (if port + (string-append "port=" (number->string port) "\n") + "") + (append (map (match-record-lambda <readymedia-media-directory> + (path types) + (apply string-append + "media_dir=" + (append (map symbol->string types) + (match types + (() (list)) + (_ (list ","))) + (list path)))) + media-directories) + (map (match-lambda + ((key . value) + (string-append key "=" value "\n"))) + extra-config))))) + +(define (readymedia-shepherd-service config) + "Return a least-authority ReadyMedia/MiniDLNA Shepherd service." + (match-record config <readymedia-configuration> + (cache-directory log-directory media-directories) + (let ((minidlna-conf (readymedia-configuration->config-file config))) + (shepherd-service + (documentation "Run the ReadyMedia/MiniDLNA daemon.") + (provision '(readymedia)) + (requirement '(networking user-processes)) + (start + #~(make-forkexec-constructor + (list #$(least-authority-wrapper + (file-append (readymedia-configuration-readymedia config) + "/sbin/minidlnad") + #:name "minidlna" + #:mappings + (cons* (file-system-mapping + (source cache-directory) + (target source) + (writable? #t)) + (file-system-mapping + (source log-directory) + (target source) + (writable? #t)) + (file-system-mapping + (source minidlna-conf) + (target source)) + (map (lambda (directory) + (file-system-mapping + (source (readymedia-media-directory-path directory)) + (target source))) + media-directories)) + #:namespaces (delq 'net %namespaces)) + "-f" + #$minidlna-conf + "-S") + #:log-file #$(string-append log-directory "/" %readymedia-log-file) + #:user #$%readymedia-user-account + #:group #$%readymedia-user-group)) + (stop #~(make-kill-destructor)))))) + +(define readymedia-accounts + (list (user-account + (name "readymedia") + (group "readymedia") + (system? #t) + (comment "ReadyMedia/MiniDLNA daemon user") + (home-directory "/var/empty") + (shell (file-append shadow "/sbin/nologin"))) + (user-group + (name "readymedia") + (system? #t)))) + +(define (readymedia-activation config) + "Set up directories for ReadyMedia/MiniDLNA." + (match-record config <readymedia-configuration> + (cache-directory log-directory media-directories) + (with-imported-modules (source-module-closure '((gnu build activation))) + #~(begin + (use-modules (gnu build activation)) + + (for-each (lambda (directory) + (unless (file-exists? directory) + (mkdir-p/perms directory + (getpw #$%readymedia-user-account) + #o755))) + (list #$cache-directory + #$log-directory + #$@(map readymedia-media-directory-path + media-directories))))))) + +(define readymedia-service-type + (service-type + (name 'readymedia) + (extensions + (list (service-extension shepherd-root-service-type + (compose list readymedia-shepherd-service)) + (service-extension account-service-type + (const readymedia-accounts)) + (service-extension activation-service-type + readymedia-activation))) + (description + "Run @command{minidlnad}, the ReadyMedia/MiniDLNA media server."))) diff --git a/gnu/tests/upnp.scm b/gnu/tests/upnp.scm new file mode 100644 index 0000000000..e4bce30d89 --- /dev/null +++ b/gnu/tests/upnp.scm @@ -0,0 +1,155 @@ +;;; GNU Guix --- Functional package management for GNU +;;; Copyright © 2024 Fabio Natali <me@fabionatali.com> +;;; +;;; 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 <http://www.gnu.org/licenses/>. + +(define-module (gnu tests upnp) + #:use-module (gnu services) + #:use-module (gnu services networking) + #:use-module (gnu services upnp) + #:use-module (gnu system vm) + #:use-module (gnu tests) + #:use-module (guix gexp) + #:export (%test-readymedia)) + +(define %readymedia-cache-file "files.db") +(define %readymedia-cache-path + (string-append %readymedia-default-cache-directory + "/" + %readymedia-cache-file)) +(define %readymedia-log-path + (string-append %readymedia-default-log-directory + "/" + %readymedia-log-file)) +(define %readymedia-default-port 8200) +(define %readymedia-media-directory "/media") +(define %readymedia-configuration-test + (readymedia-configuration + (media-directories + (list (readymedia-media-directory (path %readymedia-media-directory) + (types '(A V))))))) + +(define (run-readymedia-test) + (define os + (marionette-operating-system + (simple-operating-system + (service dhcp-client-service-type) + (service readymedia-service-type + %readymedia-configuration-test)) + #:imported-modules '((gnu services herd) + (json parser)) + #:requirements '(readymedia))) + + (define test + (with-imported-modules '((gnu build marionette)) + #~(begin + (use-modules (gnu build marionette) + (srfi srfi-64)) + + (define marionette + (make-marionette + (list #$(virtual-machine + (operating-system os) + (port-forwardings '()))))) + + (test-runner-current (system-test-runner #$output)) + (test-begin "readymedia") + + ;; ReadyMedia user + (test-assert "ReadyMedia user exists" + (marionette-eval + '(begin + (getpwnam #$%readymedia-user-account) + #t) + marionette)) + (test-assert "ReadyMedia group exists" + (marionette-eval + '(begin + (getgrnam #$%readymedia-user-group) + #t) + marionette)) + + ;; Cache directory and file + (test-assert "cache directory exists" + (marionette-eval + '(eq? (stat:type (stat #$%readymedia-default-cache-directory)) + 'directory) + marionette)) + (test-assert "cache directory has correct ownership" + (marionette-eval + '(let ((cache-dir (stat #$%readymedia-default-cache-directory)) + (user (getpwnam #$%readymedia-user-account))) + (and (eqv? (stat:uid cache-dir) (passwd:uid user)) + (eqv? (stat:gid cache-dir) (passwd:gid user)))) + marionette)) + (test-assert "cache directory has expected permissions" + (marionette-eval + '(eqv? (stat:perms (stat #$%readymedia-default-cache-directory)) + #o755) + marionette)) + + ;; Log directory and file + (test-assert "log directory exists" + (marionette-eval + '(eq? (stat:type (stat #$%readymedia-default-log-directory)) + 'directory) + marionette)) + (test-assert "log directory has correct ownership" + (marionette-eval + '(let ((log-dir (stat #$%readymedia-default-log-directory)) + (user (getpwnam #$%readymedia-user-account))) + (and (eqv? (stat:uid log-dir) (passwd:uid user)) + (eqv? (stat:gid log-dir) (passwd:gid user)))) + marionette)) + (test-assert "log directory has expected permissions" + (marionette-eval + '(eqv? (stat:perms (stat #$%readymedia-default-log-directory)) + #o755) + marionette)) + (test-assert "log file exists" + (marionette-eval + '(file-exists? #$%readymedia-log-path) + marionette)) + (test-assert "log file has expected permissions" + (marionette-eval + '(eqv? (stat:perms (stat #$%readymedia-log-path)) + #o640) + marionette)) + + ;; Service + (test-assert "ReadyMedia service is running" + (marionette-eval + '(begin + (use-modules (gnu services herd) + (srfi srfi-1)) + (live-service-running + (find (lambda (live-service) + (memq 'readymedia + (live-service-provision live-service))) + (current-services)))) + marionette)) + (test-assert "ReadyMedia service is listening for connections" + (wait-for-tcp-port #$%readymedia-default-port marionette)) + + (test-end)))) + + (gexp->derivation "readymedia-test" test)) + +(define %test-readymedia + (system-test + (name "readymedia") + (description "Test the ReadyMedia service.") + (value (run-readymedia-test)))) |