;; (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"))