summaryrefslogtreecommitdiff
path: root/tw/services/media.scm
blob: 5fd5b2b52b16948e1e4792b9a6e08a945b3ee6a3 (about) (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
(define-module (tw services media)
  #:use-module (gnu)
  #:use-module (gnu packages video)
  #:use-module (gnu services)
  #:use-module (gnu services configuration)
  #:use-module (gnu services mcron)
  #:use-module (gnu services shepherd)
  #:use-module (guix gexp)
  #:use-module (guix packages)
  #:export (yt-dlp-service-type
            yt-dlp-configuration))

(define (package-or-false? value)
  (or (package? value) (eq? #f value)))
(define (string-or-gexp? value)
  (or (string? value) (gexp? value)))

(define (random-time-every-second-night day-predicate)
  #~(lambda (now)
      (let* ((even-day
              (if (#$day-predicate (tm:yday (localtime now)))
                  now
                  (next-day-from now)))
             (deterministic-value
              (number->string (tm:yday (localtime even-day)))))
        ;; Between 1 and 6 a.m., randomly, but deterministically for each
        ;; day (to avoid runs being skipped in case mcron is restarted).
        ;; (random 6 (seed->random-state yday)) isn't chaotic enough.
        (next-minute-from
         (next-hour-from even-day (list (1+ (string-hash deterministic-value 6))))
         (list (string-hash deterministic-value 60))))))



(define-configuration/no-serialization yt-dlp-configuration
  (media-directory string "The directory in which to store downloaded media.
The service expects a @code{.yt-dlp} config directory inside this one.")
  (yt-dlp (package-or-false yt-dlp) "The yt-dlp package to use; or
@code{#false} to use the yt-dlp script inside the config directory.")
  (schedule (string-or-gexp (random-time-every-second-night 'even?)) "The mcron
schedule on which to run the download script.  By default, picks a random time
between 01:00 and 06:00 every second night.")
  (user (string "root") "The Unix user name to run the script as.")
  (group (string "root") "The Unix group name to run the script as."))

(define (yt-dlp-cronjob config)
  (list
   (let* ((yt-dlp-package (yt-dlp-configuration-yt-dlp config))
          (yt-dlp-executable
           (if yt-dlp-package
               (file-append yt-dlp-package "/bin/yt-dlp")
               (string-append (yt-dlp-configuration-media-directory config)
                              "/.yt-dlp/yt-dlp"))))
     #~(job #$(yt-dlp-configuration-schedule config)
            (lambda ()
              (chdir #$(yt-dlp-configuration-media-directory config))
              ;; `setgid' first while we're still root
              (setgid (group:gid (getgr #$(yt-dlp-configuration-group config))))
              (setuid (passwd:uid (getpw #$(yt-dlp-configuration-user config))))
              #$@(if yt-dlp-package '()
                     (list #~(system* #$yt-dlp-executable "--ignore-config" "--update")))
              (execl #$yt-dlp-executable
                     "yt-dlp" "--ignore-config" "--config-location" ".yt-dlp"))
            ;; Human-readable string for `herd schedule mcron'.
            (format #f "~a --ignore-config --config-location ~a/.yt-dlp"
                    #$yt-dlp-executable   ; this may be a `<file-append>', so #$ it directly
                    #$(yt-dlp-configuration-media-directory config))))))

(define yt-dlp-service-type
  (service-type
   (name 'yt-dlp)
   (extensions
    (list (service-extension mcron-service-type yt-dlp-cronjob)))
   (description
    "Trigger yt-dlp on a schedule to download videos from YouTube.")))