;; (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 .
;; File: balg02.scm
;; Author: Collin J. Doering
;; 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 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 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
;; ;; .
;; (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 .
"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"
;;
"~[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
;; , but also the nginx docs mention that
;; "Without the limit, one fast connection may seize the worker
;; process entirely."
;;
"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"))))
(define (balg02 efi-boot-uuid)
(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"))))
(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")))
%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"
"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 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/cache/guix/publish"))))
(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 "3AF8-9E67"))