unofficial mirror of guix-patches@gnu.org 
 help / color / mirror / code / Atom feed
From: Fabio Natali via Guix-patches via <guix-patches@gnu.org>
To: 72398@debbugs.gnu.org
Cc: arunisaac@systemreboot.net, mirai@makinata.eu,
	"Fabio Natali" <me@fabionatali.com>,
	"Florian Pelz" <pelzflorian@pelzflorian.de>,
	"Ludovic Courtès" <ludo@gnu.org>,
	"Matthew Trzcinski" <matt@excalamus.com>,
	"Maxim Cournoyer" <maxim.cournoyer@gmail.com>
Subject: [bug#72398] [PATCH v4] services: Add readymedia-service-type.
Date: Fri, 23 Aug 2024 12:04:42 +0100	[thread overview]
Message-ID: <d24b715465fe88c32c2543ff7e42ff8e1339c21a.1724411076.git.me@fabionatali.com> (raw)
In-Reply-To: <87r0agp27q.fsf@systemreboot.net>

* gnu/services/upnp.scm: New file.
* gnu/local.mk: Add this.
* doc/guix.texi: Document this.

Change-Id: I80b02235ec36b7a1ea85fea98bdc9e08126b09a3
---
Ok, brilliant, thanks Arun.

I'm sending a v4 then where I switch back to non-configurable ReadyMedia user
and group. The patch also fixes the logging mechanism - in the previous versions
the logging file was configurable but the service didn't make use of it.

If you want to give this a last check in a VM, as per my previous messages in
this thread, here's the relevant instructions.

Create a folder, e.g. '/tmp/foo', and populate it with at least one music file,
e.g. '/tmp/foo/foo.mp3'.

Create a system definition that includes the ReadyMedia service:

--8<---------------cut here---------------start------------->8---
  (services (cons*
             (service gnome-desktop-service-type)
             (service readymedia-service-type
                      (readymedia-configuration
                       (media-directories
                        (list
                         (readymedia-media-directory (path "/music")
                                                     (type 'A))))))
             %desktop-services)))
--8<---------------cut here---------------end--------------->8---

From within the Guix repository checkout, once the ReadyMedia service patch has
been applied, build and launch a VM with:

--8<---------------cut here---------------start------------->8---
$(./pre-inst-env guix system vm --share=/tmp/foo=/music CONFIG) -m 2048 -smp 2
--8<---------------cut here---------------end--------------->8---

From the VM, you should be able to verify that the ReadyMedia service is running
with 'sudo herd status'.

If available as a package in the VM, you should be able to use VLC to connect to
the ReadyMedia service and play music from the '/tmp/foo' folder. You may want
to follow these instructions https://www.vlchelp.com/access-media-upnp-dlna/.

Let me know if you spot anything. If either of you are happy with it and want to
gently push it upstream... that'd be fab.

Thanks for all the help. Best, F.


 doc/guix.texi         | 107 ++++++++++++++++++++++
 gnu/local.mk          |   1 +
 gnu/services/upnp.scm | 205 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 313 insertions(+)
 create mode 100644 gnu/services/upnp.scm

diff --git a/doc/guix.texi b/doc/guix.texi
index fcaf6b3fbb..ddc997b6bf 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -129,6 +129,7 @@
 Copyright @copyright{} 2024 Richard Sent@*
 Copyright @copyright{} 2024 Dariqq@*
 Copyright @copyright{} 2024 Denis 'GNUtoo' Carikli@*
+Copyright @copyright{} 2024 Fabio Natali@*
 
 Permission is granted to copy, distribute and/or modify this document
 under the terms of the GNU Free Documentation License, Version 1.3 or
@@ -41605,6 +41606,112 @@ Miscellaneous Services
 
 @end deftp
 
+@c %end of fragment
+
+@cindex DLNA/UPnP
+@subsubheading DLNA/UPnP Services
+
+The @code{(gnu services upnp)} module offers services related to the
+DLNA and UPnP-VA networking protocols.  For now, it provides the
+@code{readymedia-service-type}.
+
+@uref{https://sourceforge.net/projects/minidlna/, ReadyMedia}
+(formerly known as MiniDLNA) is a DLNA/UPnP-AV media server.  The
+project's daemon, @code{minidlnad}, can serve media files (audio,
+pictures, and video) to DLNA/UPnP-AV clients available in the network.
+
+@code{readymedia-service-type} is a Guix service that wraps around
+ReadyMedia's @code{minidlnad}.  For increased security, the service
+makes use of @code{least-authority-wrapper} which limits the resources
+that the daemon has access to.  The daemon runs as the
+@code{readymedia} unprivileged user, which is a member of the
+@code{readymedia} group.
+
+Consider the following configuration:
+
+@lisp
+(use-service-modules upnp @dots{})
+
+(operating-system
+  ;; @dots{}
+  (services
+   (list
+    (service readymedia-service-type
+             (readymedia-configuration
+              (media-directoriess
+               (list
+                (readymedia-media-directory (path "/media/audio")
+                                            (type 'A))
+                (readymedia-media-directory (path "/media/video")
+                                            (type 'V))
+                (readymedia-media-directory (path "/media/misc"))))
+              (extra-config '(("notify_interval" . 60)))))
+    ;; @dots{}
+    )))
+@end lisp
+
+This sets up the ReadyMedia daemon to serve files from the media
+folders specified in @code{media-directories}.  The
+@code{media-directories} field is mandatory.  All other fields (such
+as network ports and the server name) come with a predefined default
+and can be omitted.
+
+@c %start of fragment
+
+@deftp {Data Type} readymedia-configuration
+Available @code{readymedia-configuration} fields are:
+
+@table @asis
+@item @code{readymedia} (default: @code{readymedia}) (type: package)
+The ReadyMedia package to be used for the service.
+
+@item @code{friendly-name} (default: @code{#f}) (type: maybe-string)
+A custom name that will be displayed on connected clients.
+
+@item @code{media-directories} (type: list)
+The list of media folders to serve content from.  Each item is a
+@code{readymedia-media-directory}.
+
+@item @code{cache-directory} (default: @code{"/var/cache/readymedia"}) (type: string)
+A folder for ReadyMedia's cache files.  If not existing already, the
+folder will be created as part of the service activation and the
+ReadyMedia user will be assigned ownership.
+
+@item @code{log-directory} (default: @code{"/var/log/readymedia"}) (type: string)
+A folder for ReadyMedia's log files.  If not existing already, the
+folder will be created as part of the service activation and the
+ReadyMedia user will be assigned ownership.
+
+@item @code{port} (default: @code{#f}) (type: maybe-integer)
+A custom port that the service will be listening on.
+
+@item @code{extra-config} (default: @code{'()}) (type: alist)
+An association list of further options, as accepted by ReadyMedia.
+
+@end table
+
+@end deftp
+
+@c %end of fragment
+
+@c %start of fragment
+
+@deftp {Data Type} readymedia-media-directory
+A @code{media-directories} entry includes a @code{path} and,
+optionally, a media type string.
+
+@table @asis
+@item @code{path} (type: string)
+The media folder location.
+
+@item @code{type} (default: @code{#f}) (type: maybe-symbol)
+Valid media types are @code{'A} for audio, @code{'P} for pictures,
+@code{'V} for video, and a combination of those individual symbols for
+mixed types.  False means no type specified.
+
+@end table
+
+@end deftp
 
 @c %end of fragment
 
diff --git a/gnu/local.mk b/gnu/local.mk
index ad5494fe95..ef4e6d006f 100644
--- a/gnu/local.mk
+++ b/gnu/local.mk
@@ -752,6 +752,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				\
diff --git a/gnu/services/upnp.scm b/gnu/services/upnp.scm
new file mode 100644
index 0000000000..779da27837
--- /dev/null
+++ b/gnu/services/upnp.scm
@@ -0,0 +1,205 @@
+;;; 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 records)
+  #:use-module (ice-9 match)
+  #:export (%readymedia-log-file
+            %readymedia-user-account
+            %readymedia-user-group
+            readymedia-configuration
+            readymedia-configuration-cache-directory
+            readymedia-configuration-extra-config
+            readymedia-configuration-friendly-name
+            readymedia-configuration-log-directory
+            readymedia-configuration-media-directories
+            readymedia-configuration-port
+            readymedia-configuration-readymedia
+            readymedia-configuration?
+            readymedia-media-directory
+            readymedia-media-directory-path
+            readymedia-media-directory-type
+            readymedia-media-directory?
+            readymedia-service-type))
+
+;;; Commentary:
+;;;
+;;; UPnP services.
+;;;
+;;; Code:
+
+(define %readymedia-user-group "readymedia")
+(define %readymedia-user-account "readymedia")
+(define %readymedia-log-file "minidlna.log")
+
+(define-record-type* <readymedia-configuration>
+  readymedia-configuration make-readymedia-configuration
+  readymedia-configuration?
+  (readymedia readymedia-configuration-readymedia
+              (default readymedia))
+  (cache-directory readymedia-configuration-cache-directory
+             (default "/var/cache/readymedia"))
+  (log-directory readymedia-configuration-log-directory
+           (default "/var/log/readymedia"))
+  (friendly-name readymedia-configuration-friendly-name
+                 (default #f))
+  (media-directories readymedia-configuration-media-directories)
+  (port readymedia-configuration-port
+        (default #f))
+  (extra-config readymedia-configuration-extra-config
+                (default '())))
+
+;; READYMEDIA-MEDIA-DIR is a record that indicates path and media type of a
+;; media folder. Type can be false (no media type specified) or a symbol
+;; (e.g. 'A' for audio, 'V' for video, 'AV' for audio and video). The allowed
+;; individual types are 'A' for audio, 'P' for pictures, 'V' for video.
+(define-record-type* <readymedia-media-directory>
+  readymedia-media-directory make-readymedia-media-directory
+  readymedia-media-directory?
+  (path readymedia-media-directory-path)
+  (type readymedia-media-directory-type (default #f)))
+
+(define (readymedia-media-directory-type->string type)
+  "Convert a media-directory TYPE to a string."
+  (match type
+    (#f "")
+    (symbol (symbol->string type))))
+
+(define (readymedia-media-directory->string entry)
+  "Convert a media-directory ENTRY to a ReadyMedia/MiniDLNA media dir string."
+  (let ((type (readymedia-media-directory-type entry)))
+    (format #f
+            "media_dir=~a,~a"
+            (readymedia-media-directory-type->string type)
+            (readymedia-media-directory-path entry))))
+
+(define (readymedia-extra-config-entry->string entry)
+  "Convert a extra-config ENTRY to a ReadyMedia/MiniDLNA configuration string."
+  (let ((key (car entry))
+        (value (cdr entry)))
+    (format #f "~a=~a" key value)))
+
+(define (readymedia-configuration->config-file config)
+  "Return the ReadyMedia/MiniDLNA configuration file corresponding to CONFIG."
+  (let ((friendly-name (readymedia-configuration-friendly-name config))
+        (media-directories (readymedia-configuration-media-directories config))
+        (cache-directory (readymedia-configuration-cache-directory config))
+        (log-directory (readymedia-configuration-log-directory config))
+        (port (readymedia-configuration-port config))
+        (extra-config (readymedia-configuration-extra-config config)))
+    (mixed-text-file
+     "minidlna.conf"
+     "db_dir=" cache-directory "\n"
+     "log_dir=" log-directory "\n"
+     (if friendly-name (format #f "friendly_name=~a\n" friendly-name) "")
+     (if port (format #f "port=~a\n" port) "")
+     (string-join
+      (map readymedia-media-directory->string media-directories) "\n" 'suffix)
+     (string-join
+      (map readymedia-extra-config-entry->string extra-config) "\n" 'suffix))))
+
+(define (readymedia-shepherd-service config)
+  "Return a least-authority ReadyMedia/MiniDLNA Shepherd service."
+  (let* ((minidlna-conf (readymedia-configuration->config-file config))
+         (media-directories (readymedia-configuration-media-directories config))
+         (cache-directory (readymedia-configuration-cache-directory config))
+         (log-directory (readymedia-configuration-log-directory config))
+         (log-file (string-append log-directory "/" %readymedia-log-file))
+         (readymedia (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 (e)
+                                (file-system-mapping
+                                 (source (readymedia-media-directory-path e))
+                                 (target source)
+                                 (writable? #f)))
+                              media-directories))
+                      #:namespaces (delq 'net %namespaces))))
+    (list (shepherd-service
+           (documentation "Run the ReadyMedia/MiniDLNA daemon.")
+           (provision '(readymedia))
+           (requirement '(networking user-processes))
+           (start
+            #~(begin
+                (use-modules (gnu build activation))
+                (let* ((user (getpw #$%readymedia-user-account))
+                       (dirs (list
+                              #$cache-directory
+                              #$log-directory
+                              #$@(map (lambda (e)
+                                        (readymedia-media-directory-path e))
+                                      media-directories)))
+                       (init-directory (lambda (d)
+                                         (unless (file-exists? d)
+                                           (mkdir-p/perms d user #o755)))))
+                  (for-each init-directory dirs))
+                (make-forkexec-constructor
+                 ;; "-S" is to daemonise minidlnad.
+                 (list #$readymedia "-f" #$minidlna-conf "-S")
+                 #:log-file #$log-file
+                 #:user #$%readymedia-user-account
+                 #:group #$%readymedia-user-group)))
+           (stop #~(make-kill-destructor))))))
+
+(define readymedia-accounts
+  (list (user-group
+         (name "readymedia")
+         (system? #t))
+        (user-account
+         (name "readymedia")
+         (group "readymedia")
+         (system? #t)
+         (comment "ReadyMedia/MiniDLNA daemon user")
+         (home-directory "/var/empty")
+         (shell (file-append shadow "/sbin/nologin")))))
+
+(define readymedia-service-type
+  (service-type
+   (name 'readymedia)
+   (extensions
+    (list
+     (service-extension shepherd-root-service-type readymedia-shepherd-service)
+     (service-extension account-service-type (const readymedia-accounts))))
+   (description
+    "Run @command{minidlnad}, the ReadyMedia/MiniDLNA media server.")))

base-commit: ed4e0b48f16530def08862657301178b5cf00a9a
-- 
2.45.2





  reply	other threads:[~2024-08-23 11:07 UTC|newest]

Thread overview: 29+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-07-31 10:27 [bug#72398] [PATCH] services: Add readymedia-service-type Fabio Natali via Guix-patches via
2024-08-12 23:19 ` Arun Isaac
2024-08-19  0:27   ` Fabio Natali via Guix-patches via
2024-08-20  2:14     ` [bug#72398] [PATCH v2] " Bruno Victal
2024-08-22 10:13       ` Fabio Natali via Guix-patches via
2024-08-22 23:28         ` Arun Isaac
2024-08-23 11:04           ` Fabio Natali via Guix-patches via [this message]
2024-08-23 15:35             ` [bug#72398] [PATCH v4] " Bruno Victal
2024-08-26 10:11               ` [bug#72398] [PATCH v5] " Fabio Natali via Guix-patches via
2024-09-06 22:17                 ` Ludovic Courtès
2024-09-08 20:04                   ` [bug#72398] [PATCH v6] " Fabio Natali via Guix-patches via
2024-10-13 17:34                     ` Fabio Natali via Guix-patches via
2024-10-13 22:57                       ` Arun Isaac
2024-10-14 21:57                         ` [bug#72398] [PATCH] " Arun Isaac
2024-10-15 15:42                           ` Fabio Natali via Guix-patches via
2024-08-23 15:25           ` [bug#72398] [PATCH v2] " Bruno Victal
2024-08-28 22:51             ` Arun Isaac
2024-08-29 14:37               ` Fabio Natali via Guix-patches via
2024-08-22 23:22       ` Arun Isaac
2024-08-22 10:17 ` [bug#72398] [PATCH v3] " Fabio Natali via Guix-patches via
2024-10-15 15:31 ` [bug#72398] [PATCH v7] " Fabio Natali via Guix-patches via
2024-10-15 20:36   ` Arun Isaac
2024-10-15 20:42     ` Fabio Natali via Guix-patches via
2024-10-18  1:19       ` [bug#72398] [PATCH] " Arun Isaac
2024-10-18 17:50         ` Fabio Natali via Guix-patches via
2024-10-18 19:02           ` Fabio Natali via Guix-patches via
2024-10-18 20:04             ` Arun Isaac
2024-10-18 20:08           ` bug#72398: " Arun Isaac
2024-10-18  1:19 ` [bug#72398] [PATCH v8] " Arun Isaac

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://guix.gnu.org/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=d24b715465fe88c32c2543ff7e42ff8e1339c21a.1724411076.git.me@fabionatali.com \
    --to=guix-patches@gnu.org \
    --cc=72398@debbugs.gnu.org \
    --cc=arunisaac@systemreboot.net \
    --cc=ludo@gnu.org \
    --cc=matt@excalamus.com \
    --cc=maxim.cournoyer@gmail.com \
    --cc=me@fabionatali.com \
    --cc=mirai@makinata.eu \
    --cc=pelzflorian@pelzflorian.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/guix.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).