aboutsummaryrefslogtreecommitdiff
;; (C) Copyright Collin J. Doering 2024
;;
;; This program 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.
;;
;; This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.

;; File: balg02.scm
;; Author: Collin J. Doering <collin@rekahsoft.ca>
;; Date: Feb 24, 2024

(define-module (guix-na config balg02)
  #:use-module (gnu)
  #:use-module (gnu system)
  #:use-module (gnu packages bash)
  #:use-module (gnu packages databases)
  #:use-module (gnu packages shells)
  #:use-module (gnu packages web)
  #:use-module (gnu services base)
  #:use-module (gnu services cuirass)
  #:use-module (gnu services certbot)
  #:use-module (gnu services databases)
  #:use-module (gnu services networking)
  #:use-module (gnu services ssh)
  #:use-module (gnu services web)
  #:export (balg02 %system))


(define %cuirass-specs
  #~(list (specification
             (name "guix")
             (priority 0)
             (build '(channels guix))
             (channels %default-channels))
          (specification
           (name "guix-north-america")
           (build '(channels guix-north-america))
           (channels
            (cons* (channel
                    (name 'guix-north-america)
                    (url "https://git.genenetwork.org/guix-north-america/")
                    (introduction
                     (make-channel-introduction
                      "c0979ad86fdf0b403c60d5767328cb862ecc00ef"
                      (openpgp-fingerprint
                       "F8D5 46F3 AF37 EF53 D1B6  48BE 7B4D EB93 212B 3022"))))
                   %default-channels)))))

;; Taken from https://git.savannah.gnu.org/cgit/guix/maintenance.git/tree/hydra/berlin.scm
(define (anonip-service file)
  (service anonip-service-type
           (anonip-configuration
            (input (format #false "/var/run/anonip/~a" file))
            (output (format #false "/var/log/anonip/~a" file)))))

(define %anonip-log-files
  ;; List of files handled by Anonip
  '("http.access.log"
    "https.access.log"))

;; Taken from https://git.savannah.gnu.org/cgit/guix/maintenance.git/tree/hydra/berlin.scm
(define (log-file->anonip-service-name file)
  "Return the name of the Anonip service handling FILE, a log file."
  (symbol-append 'anonip-/var/log/anonip/ (string->symbol file)))

;; Taken from: https://git.savannah.gnu.org/cgit/guix/maintenance.git/tree/hydra/nginx/berlin.scm
(define publish-robots.txt
  ;; Try to prevent good-faith crawlers from downloading substitutes.  Allow
  ;; indexing the root—which is expected to be static or cheap—to remain visible
  ;; in search engine results for, e.g., ‘Guix CI’.
  "\
User-agent: *\r
Disallow: /\r
Allow: /$\r
\r
")

(define (publish-locations url)
  "Return the nginx location blocks for 'guix publish' running on URL."
  (list (nginx-location-configuration
         (uri "/nix-cache-info")
         (body
          (list
           (string-append
            "proxy_pass " url "/nix-cache-info;")
           ;; Cache this file since that's always the first thing we ask
           ;; for.
           "proxy_cache static;"
           "proxy_cache_valid 200 100d;"     ; cache hits for a looong time.
           "proxy_cache_valid any 5m;"       ; cache misses/others for 5 min.
           "proxy_ignore_client_abort on;"

           ;; We need to hide and ignore the Set-Cookie header to enable
           ;; caching.
           "proxy_hide_header    Set-Cookie;"
           "proxy_ignore_headers Set-Cookie;")))

        (nginx-location-configuration
         (uri "/nar/")
         (body
          (list
           (string-append "proxy_pass " url ";")
           "client_body_buffer_size 256k;"

           ;; Be more tolerant of delays when fetching a nar.
           "proxy_read_timeout 60s;"
           "proxy_send_timeout 60s;"

           ;; Enable caching for nar files, to avoid reconstructing and
           ;; recompressing archives.
           "proxy_cache nar;"
           "proxy_cache_valid 200 30d;"           ; cache hits for 1 month
           "proxy_cache_valid 504 3m;" ; timeout, when hydra.gnu.org is overloaded
           "proxy_cache_valid any 1h;" ; cache misses/others for 1h.

           "proxy_ignore_client_abort on;"

           ;; Nars are already compressed.
           "gzip off;"

           ;; We need to hide and ignore the Set-Cookie header to enable
           ;; caching.
           "proxy_hide_header    Set-Cookie;"
           "proxy_ignore_headers Set-Cookie;"

           ;; Provide a 'content-length' header so that 'guix
           ;; substitute-binary' knows upfront how much it is downloading.
           ;; "add_header Content-Length $body_bytes_sent;"
           )))

        (nginx-location-configuration
         (uri "~ \\.narinfo$")
         (body
          (list
           ;; Since 'guix publish' has its own caching, and since it relies
           ;; on the atime of cached narinfos to determine whether a
           ;; narinfo can be removed from the cache, don't do any caching
           ;; here.
           (string-append "proxy_pass " url ";")

           ;; For HTTP pipelining.  This has a dramatic impact on
           ;; performance.
           "client_body_buffer_size 128k;"

           ;; Narinfos requests are short, serve many of them on a
           ;; connection.
           "keepalive_requests 600;"

           ;; Do not tolerate slowness of hydra.gnu.org when fetching
           ;; narinfos: better return 504 quickly than wait forever.
           "proxy_connect_timeout 10s;"
           "proxy_read_timeout 10s;"
           "proxy_send_timeout 10s;"

           ;; 'guix publish --ttl' produces a 'Cache-Control' header for
           ;; use by 'guix substitute'.  Let it through rather than use
           ;; nginx's "expire" directive since the expiration time defined
           ;; by 'guix publish' is the right one.
           "proxy_pass_header Cache-Control;"

           "proxy_ignore_client_abort on;"

           ;; We need to hide and ignore the Set-Cookie header to enable
           ;; caching.
           "proxy_hide_header    Set-Cookie;"
           "proxy_ignore_headers Set-Cookie;")))

        ;; Content-addressed files served by 'guix publish'.
        (nginx-location-configuration
         (uri "/file/")
         (body
          (list
           (string-append "proxy_pass " url ";")

           "proxy_cache cas;"
           "proxy_cache_valid 200 200d;"          ; cache hits
           "proxy_cache_valid any 5m;"            ; cache misses/others

           "proxy_ignore_client_abort on;")))

	;; Try to prevent good-faith crawlers from downloading substitutes.
	(nginx-location-configuration
	 (uri "= /robots.txt")
	 (body
	  (list
	   #~(string-append "try_files "
			    #$(plain-file "robots.txt" publish-robots.txt)
			    " =404;")
	   "root /;")))))

;; TODO
;; (define %ci-onion
;;   ;; Onion service name of ci.guix.
;;   "4zwzi66wwdaalbhgnix55ea3ab4pvvw66ll2ow53kjub6se4q2bclcyd.onion")

(define (balg02-locations publish-url)
  "Return nginx location blocks with 'guix publish' reachable at
PUBLISH-URL."
  (append (publish-locations publish-url)
          (list
           ;; Cuirass.
           (nginx-location-configuration
            (uri "/")
            (body (list "proxy_pass http://localhost:8081;"
                        ;; ;; See
                        ;; ;; <https://community.torproject.org/onion-services/advanced/onion-location/>.
                        ;; (string-append
                        ;;  "add_header Onion-Location http://" %ci-onion
                        ;;  "$request_uri;")
                        )))
           (nginx-location-configuration
            (uri "~ ^/admin")
            (body
             (list "if ($ssl_client_verify != SUCCESS) { return 403; } proxy_pass http://localhost:8081;")))

           (nginx-location-configuration
            (uri "/static")
            (body
             (list
              "proxy_pass http://localhost:8081;"
              ;; Cuirass adds a 'Cache-Control' header, honor it.
              "proxy_cache static;"
              "proxy_cache_valid 200 2d;"
              "proxy_cache_valid any 10m;"
              "proxy_ignore_client_abort on;")))

           (nginx-location-configuration
            (uri "/download")                     ;Cuirass "build products"
            (body
             (list
              "proxy_pass http://localhost:8081;"
              "expires 10d;"                      ;override 'Cache-Control'
              "proxy_cache static;"
              "proxy_cache_valid 200 30d;"
              "proxy_cache_valid any 10m;"
              "proxy_ignore_client_abort on;")))

           (nginx-location-configuration          ;certbot
            (uri "/.well-known")
            (body (list "root /var/www;"))))))

(define %publish-url "http://localhost:3000")

;; Taken from https://git.savannah.gnu.org/cgit/guix/maintenance.git/tree/hydra/modules/sysadmin/nginx.scm
(define %tls-settings
  (list
   ;; Make sure SSL is disabled.
   "ssl_protocols       TLSv1.1 TLSv1.2 TLSv1.3;"
   ;; Disable weak cipher suites.
   "ssl_ciphers         HIGH:!aNULL:!MD5;"
   "ssl_prefer_server_ciphers on;"

   ;; Use our own DH parameters created with:
   ;;    openssl dhparam -out dhparams.pem 2048
   ;; as suggested at <https://weakdh.org/sysadmin.html>.
   "ssl_dhparam         /etc/dhparams.pem;"))

;; Taken from https://git.savannah.gnu.org/cgit/guix/maintenance.git/tree/hydra/modules/sysadmin/nginx.scm
(define* (le host #:optional privkey)
  (string-append "/etc/letsencrypt/live/"
                 host "/"
                 (if privkey "privkey" "fullchain")
                 ".pem"))

;; Taken from https://git.savannah.gnu.org/cgit/guix/maintenance.git/tree/hydra/modules/sysadmin/nginx.scm
(define languages-to-accept
  ;; List of languages for redirection; see 'accept-languages' further
  ;; below.
  '(("en")
    ("de")
    ("eo")
    ("es")
    ("fr")
    ("ja")
    ("ko")
    ("ru")
    ("sk")
    ("tr")
    ("zh-CN" "zh" "zh-Hans" "zh-Hans-CN")
    ("zh-TW" "zh-Hant" "zh-Hant-TW")))

;; Taken from https://git.savannah.gnu.org/cgit/guix/maintenance.git/tree/hydra/modules/sysadmin/nginx.scm
(define* (accept-languages
          #:optional (language-lists
                      languages-to-accept))
  "Returns nginx configuration code to set up the $lang variable
according to the Accept-Language header in the HTTP request.  The
requesting user agent will be served the files at /$lang/some/url.
Each list in LANGUAGE-LISTS starts with the $lang and is followed by
synonymous IETF language tags that should be mapped to the same $lang."
  (define (language-mappings language-list)
    (define (language-mapping language)
      (string-join (list "    "  language (car language-list) ";")))
    (string-join (map language-mapping language-list) "\n"))

  (let ((directives
         `(,(string-join
             `("set_from_accept_language $lang_unmapped"
               ,@(map string-join language-lists)
               ";"))
           "map $lang_unmapped $lang {"
           ,@(map language-mappings language-lists)
           "}")))
    (string-join directives "\n")))


(define %balg02-servers
  (list
   ;; Redirect domains that don't explicitly support HTTP (below) to HTTPS.
   (nginx-server-configuration
    (listen '("80"))
    (raw-content
     (list "return 308 https://$host$request_uri;")))

   ;; Domains that still explicitly support plain HTTP.
   (nginx-server-configuration
    (listen '("80"))
    (server-name `("cuirass.genenetwork.org"
		   ;; <https://logs.guix.gnu.org/guix/2021-11-20.log#155427>
		   "~[0-9]$"
; TODO: onion
;                   ,(regexp-quote %ci-onion)
                   ))
    (locations (balg02-locations %publish-url))
    (raw-content
     (list
      "access_log  /var/run/anonip/http.access.log;"
      "proxy_set_header X-Forwarded-Host $host;"
      "proxy_set_header X-Forwarded-Port $server_port;"
      "proxy_set_header X-Forwarded-For  $proxy_add_x_forwarded_for;")))

   ;; HTTPS servers
   (nginx-server-configuration
    (listen '("443 ssl"))
    (server-name '("cuirass.genenetwork.org"))
    (ssl-certificate (le "cuirass.genenetwork.org"))
    (ssl-certificate-key (le "cuirass.genenetwork.org" 'key))
    (locations (balg02-locations %publish-url))
    (raw-content
     (append
      %tls-settings
      (list
       "access_log  /var/run/anonip/https.access.log;"
       "proxy_set_header X-Forwarded-Host $host;"
       "proxy_set_header X-Forwarded-Port $server_port;"
       "proxy_set_header X-Forwarded-For  $proxy_add_x_forwarded_for;"
       ;; TODO:
       ;; For Cuirass admin interface authentication
       ;; "ssl_client_certificate /etc/ssl-ca/certs/ca.crt;"
       ;; "ssl_verify_client optional;"
       ))))))

(define %extra-content
  (list
   "default_type  application/octet-stream;"
   "sendfile        on;"

   (accept-languages)

   ;; Maximum chunk size to send.  Partly this is a workaround for
   ;; <http://bugs.gnu.org/19939>, but also the nginx docs mention that
   ;; "Without the limit, one fast connection may seize the worker
   ;; process entirely."
   ;;  <http://nginx.org/en/docs/http/ngx_http_core_module#sendfile_max_chunk>
   "sendfile_max_chunk 1m;"

   "keepalive_timeout  65;"

   ;; Use HTTP 1.1 to talk to the backend so we benefit from keep-alive
   ;; connections and chunked transfer encoding.  The latter allows us to
   ;; make sure we do not cache partial downloads.
   "proxy_http_version 1.1;"

   ;; The 'inactive' parameter for caching is not very useful in our
   ;; case: all that matters is that LRU sweeping happens when 'max_size'
   ;; is hit.

   ;; cache for nar files
   "proxy_cache_path /var/cache/nginx/nar"
   "     levels=2"
   "     inactive=8d"	    ; inactive keys removed after 8d
   "     keys_zone=nar:4m"  ; nar cache meta data: ~32K keys
   "     max_size=10g;"     ; total cache data size max

   ;; cache for content-addressed files
   "proxy_cache_path /var/cache/nginx/cas"
   "     levels=2"
   "     inactive=180d"	    ; inactive keys removed after 180d
   "     keys_zone=cas:8m"  ; nar cache meta data: ~64K keys
   "     max_size=50g;"         ; total cache data size max

   ;; cache for build logs
   "proxy_cache_path /var/cache/nginx/logs"
   "     levels=2"
   "     inactive=60d"          ; inactive keys removed after 60d
   "     keys_zone=logs:8m"     ; narinfo meta data: ~64K keys
   "     max_size=4g;"          ; total cache data size max

   ;; cache for static data
   "proxy_cache_path /var/cache/nginx/static"
   "     levels=1"
   "     inactive=10d"	       ; inactive keys removed after 10d
   "     keys_zone=static:1m"   ; nar cache meta data: ~8K keys
   "     max_size=200m;"        ; total cache data size max

   ;; If Hydra cannot honor these delays, then something is wrong and
   ;; we'd better drop the connection and return 504.
   "proxy_connect_timeout 10s;"
   "proxy_read_timeout 10s;"
   "proxy_send_timeout 10s;"

   ;; Cache timeouts for a little while to avoid increasing pressure.
   "proxy_cache_valid 504 30s;"))

(define %nginx-configuration
  (nginx-configuration
   (server-blocks %balg02-servers)
   (server-names-hash-bucket-size 128)
   (modules
    (list
     ;; Module to redirect users to the localized pages of their choice.
     (file-append nginx-accept-language-module
                  "/etc/nginx/modules/ngx_http_accept_language_module.so")))
   (global-directives
    '((worker_processes . 16)
      (pcre_jit . on)
      (events . ((worker_connections . 1024)))))
   (extra-content
    (string-join %extra-content "\n"))
   (shepherd-requirement
    (map log-file->anonip-service-name
         %anonip-log-files))))

(define %nginx-cache-activation
  ;; Make sure /var/cache/nginx exists on the first run.
  (simple-service 'nginx-/var/cache/nginx
                  activation-service-type
                  (with-imported-modules '((guix build utils))
                    #~(begin
                        (use-modules (guix build utils))
                        (mkdir-p "/var/cache/nginx")))))

(define %nginx-deploy-hook
  (program-file
   "nginx-deploy-hook"
   #~(let ((pid (call-with-input-file "/var/run/nginx/pid" read)))
       (kill pid SIGHUP))))

;; Taken from https://git.savannah.gnu.org/cgit/guix/maintenance.git/tree/hydra/modules/sysadmin/services.scm
(define* (guix-daemon-config #:key (max-jobs 5) (cores 4)
                             (build-accounts-to-max-jobs-ratio 4)
                             (authorized-keys '())
                             (substitute-urls '()))
  (guix-configuration
   (substitute-urls substitute-urls)
   (authorized-keys authorized-keys)

   ;; We don't want to let builds get stuck for too long, but we still want
   ;; to allow building things that can take a while (eg. 3h). Adjust as necessary.
   (max-silent-time 3600)
   (timeout (* 6 3600))

   (log-compression 'gzip)               ;be friendly to 'guix publish' users

   (build-accounts (* build-accounts-to-max-jobs-ratio max-jobs))
   (extra-options (list "--max-jobs" (number->string max-jobs)
                        "--cores" (number->string cores)
                        "--gc-keep-derivations"))
   (tmpdir "/var/tmp")))

(define* (balg02 #:key efi-boot-uuid raid1-data-uuid data-raid-members)
  (operating-system
   (host-name "balg02")
   (timezone "US/Central")
   (locale "en_US.utf8")
   (keyboard-layout (keyboard-layout "us"))

   (kernel-arguments
    (cons* "console=ttyS0,115200" "console=tty0"
           %default-kernel-arguments))

   (initrd-modules (cons "megaraid_sas" %base-initrd-modules))

   (bootloader (bootloader-configuration
                (bootloader grub-efi-bootloader)
                (terminal-inputs '(console serial_1))
                (terminal-outputs '(console serial_1))
                (serial-unit 1)
                (serial-speed 115200)
                (targets '("/boot/efi"))))

   (mapped-devices
    (list
     (mapped-device
      (source data-raid-members)
      (target "/dev/md0")
      (type raid-device-mapping))))

   (file-systems (append
                  (list (file-system
                         (device (file-system-label "root"))
                         (mount-point "/")
                         (type "btrfs")
                         (options "subvol=@,compress=zstd"))
                        (file-system
                         (device (file-system-label "root"))
                         (mount-point "/swap")
                         (type "btrfs")
                         (options "subvol=@swap"))
                        (file-system
                         (device (uuid efi-boot-uuid 'fat))
                         (mount-point "/boot/efi")
                         (type "vfat"))
                        (file-system
                         (device "none")
                         (mount-point "/tmp")
                         (type "tmpfs")
                         (check? #f))
                        (file-system
                         (device (uuid raid1-data-uuid))
                         (mount-point "/var/data")
                         (create-mount-point? #t)
                         (type "ext4")
                         (dependencies mapped-devices)))
                  %base-file-systems))

   (swap-devices
    (list (swap-space
           (target "/swap/swapfile")
           (dependencies (filter (file-system-mount-point-predicate "/swap")
                                 file-systems)))))

   (users (cons* (user-account
                  (name "auto")
                  (comment "Automation User")
                  (group "users")
	          (shell #~(string-append #$bash "/bin/bash"))
                  (supplementary-groups
                   '("wheel"))
                  (home-directory "/home/auto"))
                 (user-account
                  (name "arun")
                  (comment "Admin user")
                  (group "users")
	          (shell #~(string-append #$zsh "/bin/zsh"))
                  (supplementary-groups
                   '("wheel"))
                  (home-directory "/home/arun"))
                 (user-account
                  (name "collin")
                  (comment "Admin user")
                  (group "users")
	          (shell #~(string-append #$zsh "/bin/zsh"))
                  (supplementary-groups
                   '("wheel"))
                  (home-directory "/home/collin"))
                 (user-account
                  (name "pjotr")
                  (comment "Admin user")
                  (group "users")
	          (shell #~(string-append #$zsh "/bin/zsh"))
                  (supplementary-groups
                   '("wheel"))
                  (home-directory "/home/pjotr"))
                 %base-user-accounts))

   (packages
    (append
     (map specification->package
          '("btrfs-progs"
            "mdadm"
            "recutils"
            "openssh"
            "tmux"
            "emacs"
            "emacs-guix"))
     %base-packages))

   (services
    (append
     (map anonip-service %anonip-log-files)
     (list
      (service openssh-service-type
	       (openssh-configuration
		(password-authentication? #f)
                (allow-agent-forwarding? #t)
		(authorized-keys
		 `(("auto" ,(local-file "../../../.pubkeys/deploy-key.pub"))
                   ("arun"
                    ,(local-file "../../../.pubkeys/arun.pub")
                    ,(local-file "../../../.pubkeys/arun-ed25519.pub"))
                   ("collin" ,(local-file "../../../.pubkeys/collin.pub"))
                   ("pjotr"
                    ,(local-file "../../../.pubkeys/pjotr-gaeta.pub")
                    ,(local-file "../../../.pubkeys/pjotr-napoli.pub")
                    ,(local-file "../../../.pubkeys/pjotr-stromboli.pub")
                    ,(local-file "../../../.pubkeys/pjotr-tb-arm-01.pub"))))))
      (service static-networking-service-type
               (list (static-networking
                      (addresses
                       (list (network-address
                              (device "eno8303")
                              (value "216.37.76.55/24"))))
                      (routes
                       (list (network-route
                              (destination "default")
                              (gateway "216.37.76.1"))))
                      (name-servers '("216.37.64.2" "216.37.64.3")))))
      (service ntp-service-type)

      (service certbot-service-type
               (certbot-configuration
                (email "collin@rekahsoft.ca")
                (certificates
                 (list
                  (certificate-configuration
                   (domains '("cuirass.genenetwork.org"))
                   (deploy-hook %nginx-deploy-hook))))))

      (service postgresql-service-type
               (postgresql-configuration
                (postgresql postgresql-14)))
      (service cuirass-service-type
               (cuirass-configuration
                (host "localhost")
                (specifications %cuirass-specs)))

      %nginx-cache-activation
      (service nginx-service-type %nginx-configuration)

      (service guix-publish-service-type
               (guix-publish-configuration
                (port 3000)
                (cache "/var/data/guix-publish-cache/")
                (ttl (* 90 24 3600)))))
      (modify-services %base-services
        (guix-service-type config => (guix-daemon-config
                                      #:substitute-urls
                                      '("https://cuirass.genenetwork.org")
                                      #:max-jobs 20
                                      #:cores 4
                                      #:authorized-keys
                                      (cons
                                       (local-file "../../../.pubkeys/guix/cuirass.genenetwork.org.pub")
                                       %default-authorized-guix-keys)
                                      #:build-accounts-to-max-jobs-ratio 5)))))))

(define %system (balg02
                 #:efi-boot-uuid "3AF8-9E67"
                 #:raid1-data-uuid "fdc495ca-6ac6-4a50-9115-8f5e2cc2851f"
                 #:data-raid-members (list "/dev/sdc1" "/dev/sdd1")))