;;; genenetwork-machines --- Guix configuration for genenetwork machines ;;; Copyright © 2024 Arun Isaac ;;; ;;; This file is part of genenetwork-machines. ;;; ;;; genenetwork-machines 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. ;;; ;;; genenetwork-machines 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 genenetwork-machines. If not, see ;;; . (define-module (genenetwork services genenetwork) #:use-module ((gn packages genenetwork) #:select (genenetwork2 genenetwork3 gn-auth)) #:use-module ((gnu packages admin) #:select (shadow)) #:use-module (gnu services) #:use-module (gnu services web) #:use-module (gnu system file-systems) #:use-module (gnu system shadow) #:use-module (guix gexp) #:use-module (guix profiles) #:use-module (guix records) #:use-module (forge environment) #:use-module (forge nginx) #:use-module (forge gunicorn) #:use-module (forge socket) #:use-module (srfi srfi-1) #:use-module (ice-9 match) #:export (genenetwork-service-type genenetwork-configuration genenetwork-configuration? genenetwork-configuration-genenetwork2 genenetwork-configuration-genenetwork3 genenetwork-configuration-server-name genenetwork-configuration-port genenetwork-configuration-gn2-port genenetwork-configuration-gn3-port genenetwork-configuration-auth-db genenetwork-configuration-xapian-db genenetwork-configuration-genotype-files genenetwork-configuration-sparql-endpoint genenetwork-configuration-gn3-data-directory genenetwork-configuration-gn2-secrets genenetwork-configuration-gn3-secrets)) (define-record-type* genenetwork-configuration make-genenetwork-configuration genenetwork-configuration? (genenetwork2 genenetwork-configuration-genenetwork2 (default genenetwork2)) (genenetwork3 genenetwork-configuration-genenetwork3 (default genenetwork3)) (gn-auth genenetwork-configuration-gn-auth (default gn-auth)) (server-name genenetwork-configuration-server-name (default "genenetwork.org")) (gn-auth-server-name genenetwork-configuration-gn-auth-server-name (default "auth.genenetwork.org")) (gn2-port genenetwork-configuration-gn2-port (default 8082)) (gn3-port genenetwork-configuration-gn3-port (default 8083)) (gn-auth-port genenetwork-configuration-gn-auth-port (default 8084)) (sql-uri genenetwork-configuration-sql-uri (default "mysql://username:password@localhost/database")) (auth-db genenetwork-configuration-auth-db (default "/var/genenetwork/auth.db")) (xapian-db genenetwork-configuration-xapian-db (default "/var/genenetwork/xapian")) (genotype-files genenetwork-configuration-genotype-files (default "/var/genenetwork/genotype-files")) (sparql-endpoint genenetwork-configuration-sparql-endpoint (default "http://localhost:8081/sparql")) (gn3-data-directory genenetwork-configuration-gn3-data-directory (default "/var/genenetwork")) (gn2-secrets genenetwork-configuration-gn2-secrets (default "/etc/genenetwork/gn2-secrets.py")) (gn3-secrets genenetwork-configuration-gn3-secrets (default "/etc/genenetwork/gn3-secrets.py")) (gn-auth-secrets genenetwork-configuration-gn-auth-secrets (default "/etc/genenetwork/gn-auth-secrets.py"))) (define %genenetwork-accounts (list (user-group (name "genenetwork") (system? #t)) (user-account (name "genenetwork") (group "genenetwork") (system? #t) (comment "GeneNetwork user") (home-directory "/var/empty") (shell (file-append shadow "/sbin/nologin"))))) (define (genenetwork-activation config) (match-record config (gn2-secrets gn3-secrets gn-auth-secrets auth-db) (with-imported-modules '((guix build utils)) #~(begin (use-modules (guix build utils)) (for-each (lambda (file) (chown file (passwd:uid (getpw "genenetwork")) (passwd:gid (getpw "genenetwork")))) (find-files #$(dirname auth-db) #:directories? #t)) ;; Let each service user own their own secrets files. (chown #$gn2-secrets (passwd:uid (getpw "gunicorn-genenetwork2")) (passwd:gid (getpw "gunicorn-genenetwork2"))) (chown #$gn3-secrets (passwd:uid (getpw "gunicorn-genenetwork3")) (passwd:gid (getpw "gunicorn-genenetwork3"))) (chown #$gn-auth-secrets (passwd:uid (getpw "gunicorn-gn-auth")) (passwd:gid (getpw "gunicorn-gn-auth"))) ;; Set owner-only permissions on secrets files. (for-each (lambda (file) (chmod file #o600)) (list #$gn2-secrets #$gn3-secrets #$gn-auth-secrets)))))) (define (configuration-file-gexp alist) "Return a G-expression that constructs a configuration file of key-value pairs. @var{alist} is an association list mapping keys to their values. Keys must be strings. Values may be strings, G-expressions or numbers." #~(begin (use-modules (ice-9 match)) (call-with-output-file #$output (lambda (port) (for-each (match-lambda ((key value) (display key port) (display " = " port) (cond ((number? value) (display value port)) (else (display "\"" port) (display value port) (display "\"" port))) (newline port))) '#$alist))))) (define (genenetwork-gunicorn-apps config) "Return a list of gunicorn apps to run the genenetwork server described by @var{config}, a @code{} object." (match-record config (genenetwork2 genenetwork3 gn-auth server-name gn-auth-server-name gn2-port gn3-port gn-auth-port sql-uri auth-db xapian-db genotype-files sparql-endpoint gn3-data-directory gn2-secrets gn3-secrets gn-auth-secrets) ;; If we mapped only the mysqld.sock socket file, it would break ;; when the external mysqld server is restarted. (let* ((database-mapping (file-system-mapping (source "/run/mysqld") (target source) (writable? #t))) (gn2-profile (profile (content (package->development-manifest genenetwork2)) (allow-collisions? #t))) (gn2-conf (computed-file "gn2.conf" (configuration-file-gexp `(("GEMMA_COMMAND" ,(file-append gn2-profile "/bin/gemma")) ("GEMMA_WRAPPER_COMMAND" ,(file-append gn2-profile "/bin/gemma-wrapper")) ("GENENETWORK_FILES" ,genotype-files) ("GN2_SECRETS" ,gn2-secrets) ("GN3_LOCAL_URL" ,(string-append "http://localhost:" (number->string gn3-port))) ("GN_SERVER_URL" ,(string-append "https://" server-name "/api3/")) ("AUTH_SERVER_URL" ,(string-append "https://" gn-auth-server-name "/")) ("JS_GUIX_PATH" ,(file-append gn2-profile "/share/genenetwork2/javascript")) ("PLINK_COMMAND" ,(file-append gn2-profile "/bin/plink2")) ("SQL_URI" ,sql-uri))))) (gn3-conf (computed-file "gn3.conf" (configuration-file-gexp `(("AUTH_DB" ,auth-db) ("DATA_DIR" ,gn3-data-directory) ("SPARQL_ENDPOINT" ,sparql-endpoint) ("SQL_URI" ,sql-uri) ("XAPIAN_DB_PATH" ,xapian-db))))) (gn-auth-conf (computed-file "gn-auth.conf" (configuration-file-gexp `(("AUTH_DB" ,auth-db) ("GN_AUTH_SECRETS" ,gn-auth-secrets)))))) (list (gunicorn-app (name "genenetwork2") (package genenetwork2) (sockets (list (forge-ip-socket (port gn2-port)))) (wsgi-app-module "gn2.wsgi") (workers 20) (environment-variables (list (environment-variable (name "GN2_PROFILE") (value gn2-profile)) (environment-variable (name "GN2_SETTINGS") (value gn2-conf)) (environment-variable (name "HOME") (value "/tmp")))) (mappings (list database-mapping (file-system-mapping (source genotype-files) (target source)) (file-system-mapping (source gn2-conf) (target source)) (file-system-mapping (source gn2-profile) (target source)) (file-system-mapping (source gn2-secrets) (target source))))) (gunicorn-app (name "genenetwork3") (package genenetwork3) (sockets (list (forge-ip-socket (port gn3-port)))) (wsgi-app-module "gn3.app:create_app()") (workers 20) ;; gunicorn's default 30 second timeout is insufficient ;; for Fahamu AI endpoints and results in worker timeout ;; errors. (timeout 1200) (environment-variables (list (environment-variable (name "GN3_CONF") (value gn3-conf)) (environment-variable (name "GN3_SECRETS") (value gn3-secrets)) (environment-variable (name "HOME") (value "/tmp")))) (mappings (list database-mapping (file-system-mapping (source gn3-conf) (target source)) (file-system-mapping (source gn3-secrets) (target source)) (file-system-mapping (source gn3-data-directory) (target source)) (file-system-mapping (source xapian-db) (target source)) (file-system-mapping (source auth-db) (target source) (writable? #t))))) (gunicorn-app (name "gn-auth") (package gn-auth) (sockets (list (forge-ip-socket (port gn-auth-port)))) (wsgi-app-module "gn_auth:create_app()") (workers 20) (environment-variables (list (environment-variable (name "GN_AUTH_CONF") (value gn-auth-conf)) (environment-variable (name "HOME") (value "/tmp")) (environment-variable (name "AUTHLIB_INSECURE_TRANSPORT") (value "true")))) (mappings (list database-mapping (file-system-mapping (source gn-auth-conf) (target source)) (file-system-mapping (source auth-db) (target source) (writable? #t)) (file-system-mapping (source gn-auth-secrets) (target source))))))))) (define (genenetwork-nginx-server-blocks config) "Return a list of @code{} records specifying reverse proxies for the genenetwork service described by @var{config}, a @code{} record." (match-record config (server-name gn-auth-server-name gn2-port gn3-port gn-auth-port) (list (nginx-server-configuration (server-name (list server-name)) (locations (list (nginx-location-configuration (uri "/") (body (list (string-append "proxy_pass http://localhost:" (number->string gn2-port) ";") "proxy_set_header Host $host;"))) (nginx-location-configuration (uri "/api3/") (body (list "rewrite /api3/(.*) /api/$1 break;" (string-append "proxy_pass http://localhost:" (number->string gn3-port) ";") "proxy_set_header Host $host;")))))) (nginx-server-configuration (server-name (list gn-auth-server-name)) (locations (list (nginx-location-configuration (uri "/") (body (list (string-append "proxy_pass http://localhost:" (number->string gn-auth-port) ";") "proxy_set_header Host $host;"))))))))) (define genenetwork-service-type (service-type (name 'genenetwork) (description "Run GeneNetwork") (extensions (list (service-extension account-service-type (const %genenetwork-accounts)) (service-extension activation-service-type genenetwork-activation) (service-extension gunicorn-service-type genenetwork-gunicorn-apps) (service-extension forge-nginx-service-type genenetwork-nginx-server-blocks))) (default-value (genenetwork-configuration))))