415 lines
14 KiB
Scheme
415 lines
14 KiB
Scheme
|
(define-module (weblate-service)
|
||
|
#:use-module (weblate)
|
||
|
#:use-module (guix gexp)
|
||
|
#:use-module (guix utils)
|
||
|
#:use-module (guix build python-build-system)
|
||
|
#:use-module (guix packages)
|
||
|
#:use-module (gnu services)
|
||
|
#:use-module (gnu services web)
|
||
|
#:use-module (gnu packages version-control)
|
||
|
#:use-module (gnu packages web)
|
||
|
#:use-module (gnu packages python)
|
||
|
#:use-module (gnu packages ssh)
|
||
|
#:use-module (gnu packages databases)
|
||
|
#:use-module (gnu services shepherd)
|
||
|
#:use-module (guix records)
|
||
|
#:use-module (ice-9 match)
|
||
|
#:use-module (gnu system shadow)
|
||
|
#:export (weblate-configuration
|
||
|
weblate-configuration?
|
||
|
weblate-service-type))
|
||
|
|
||
|
(define-record-type* <weblate-configuration>
|
||
|
weblate-configuration make-weblate-configuration
|
||
|
weblate-configuration?
|
||
|
(settings-file weblate-configuration-settings-file)
|
||
|
(user weblate-configuration-user)
|
||
|
(group weblate-configuration-group)
|
||
|
(listen weblate-configuration-listen)
|
||
|
(root weblate-configuration-root)
|
||
|
(uwsgi-listen weblate-configuration-uwsgi-listen))
|
||
|
|
||
|
|
||
|
;; (define %weblate-accounts
|
||
|
;; (list (user-group (name "weblate") (system? #t))
|
||
|
;; (user-account
|
||
|
;; (name "weblate")
|
||
|
;; (group "weblate")
|
||
|
;; (system? #t)
|
||
|
;; (comment "Weblate server user")
|
||
|
;; (home-directory "/var/empty")
|
||
|
;; (shell (file-append shadow "/sbin/nologin")))))
|
||
|
|
||
|
(define (weblate-accounts config)
|
||
|
(let ((user (weblate-configuration-user config))
|
||
|
(group (weblate-configuration-group config)))
|
||
|
(list (user-group (name group) (system? #t))
|
||
|
;; FIXME: change default group in uwsgi.ini
|
||
|
(user-group (name "weblate") (system? #t))
|
||
|
(user-account
|
||
|
(name user)
|
||
|
(group group)
|
||
|
(system? #t)
|
||
|
(comment "Weblate service user")
|
||
|
(home-directory "/var/empty")
|
||
|
;; (shell (file-append shadow "/sbin/nologin"))
|
||
|
))))
|
||
|
|
||
|
(define weblate-service-type
|
||
|
(service-type (name 'weblate)
|
||
|
(description "Run WEBLATE")
|
||
|
(extensions
|
||
|
(list
|
||
|
(service-extension nginx-service-type weblate-nginx-service)
|
||
|
(service-extension account-service-type
|
||
|
weblate-accounts)
|
||
|
(service-extension activation-service-type
|
||
|
weblate-activation)
|
||
|
(service-extension shepherd-root-service-type
|
||
|
weblate-shepherd-services)))))
|
||
|
|
||
|
(define (weblate-shepherd-services config)
|
||
|
(list (weblate-initial-database-setup-service config)
|
||
|
(uwsgi-weblate-service config)
|
||
|
(celery-weblate-service config)))
|
||
|
|
||
|
;; TODO:
|
||
|
;; - Failed to find git-http-backend, the git exporter will not work.
|
||
|
;; - /var/lib/weblate/cache/fonts/
|
||
|
;; - /var/lib/weblate/home/.gitconfig
|
||
|
;; https://develop.sentry.dev/self-hosted/
|
||
|
|
||
|
(define (weblate-initial-database-setup-service config)
|
||
|
(define start-gexp
|
||
|
#~(lambda ()
|
||
|
(let ((pid (primitive-fork))
|
||
|
(postgres (getpwnam "postgres")))
|
||
|
(if (eq? pid 0)
|
||
|
(dynamic-wind
|
||
|
(const #t)
|
||
|
(lambda ()
|
||
|
(setgid (passwd:gid postgres))
|
||
|
(setuid (passwd:uid postgres))
|
||
|
(primitive-exit
|
||
|
(if (and
|
||
|
(zero?
|
||
|
(system* #$(file-append postgresql "/bin/createuser")
|
||
|
"--superuser" "weblate"))
|
||
|
(zero?
|
||
|
(system* #$(file-append postgresql "/bin/createdb")
|
||
|
"-O" "weblate" "weblate")))
|
||
|
0
|
||
|
1)))
|
||
|
(lambda ()
|
||
|
(primitive-exit 1)))
|
||
|
(zero? (cdr (waitpid pid)))))
|
||
|
(let ((pid (primitive-fork))
|
||
|
(weblate (getpwnam "weblate")))
|
||
|
(if (eq? pid 0)
|
||
|
(dynamic-wind
|
||
|
(const #t)
|
||
|
(lambda ()
|
||
|
(setgid (passwd:gid weblate))
|
||
|
(setuid (passwd:uid weblate))
|
||
|
(primitive-exit
|
||
|
(begin
|
||
|
(setenv "PYTHONPATH" #$(weblate-configuration-root config))
|
||
|
(setenv "DJANGO_SETTINGS_MODULE" "guix.settings")
|
||
|
(setenv "PATH" (string-append #$(file-append git) "/bin/"))
|
||
|
(if (and
|
||
|
(zero?
|
||
|
(system* #$(file-append python-weblate "/bin/weblate")
|
||
|
"migrate" "--no-input"))
|
||
|
(zero?
|
||
|
(system* #$(file-append python-weblate "/bin/weblate")
|
||
|
"createadmin" "--password" "default-password"
|
||
|
;; graceful manage existing admin
|
||
|
;; "--update"
|
||
|
)))
|
||
|
0
|
||
|
1))))
|
||
|
(lambda ()
|
||
|
(primitive-exit 1)))
|
||
|
(zero? (cdr (waitpid pid)))))))
|
||
|
(shepherd-service
|
||
|
(requirement '(postgres))
|
||
|
(provision '(weblate-initial-database-setup))
|
||
|
(start start-gexp)
|
||
|
(stop #~(const #f))
|
||
|
(respawn? #f)
|
||
|
(one-shot? #t)
|
||
|
(documentation "Setup Weblate database.")))
|
||
|
|
||
|
(define (weblate-activation config)
|
||
|
;; Activation gexp.
|
||
|
#~(begin
|
||
|
(use-modules (guix build utils))
|
||
|
(let* ((user #$(weblate-configuration-user config))
|
||
|
(group #$(weblate-configuration-group config))
|
||
|
(root #$(weblate-configuration-root config))
|
||
|
(settings-file #$(weblate-configuration-settings-file config)))
|
||
|
(when (not (file-exists? root))
|
||
|
(mkdir-p root)
|
||
|
(let* ((user (getpwnam user))
|
||
|
(group (getpwnam group)))
|
||
|
(for-each
|
||
|
(lambda (dir)
|
||
|
(mkdir-p (string-append root "/" dir)))
|
||
|
'("guix" "ssh" "home" "celery" "backups" "cache" "cache/fonts"))
|
||
|
(for-each
|
||
|
(lambda (file)
|
||
|
;; nginx needs to serve static files
|
||
|
(chmod file #o750)
|
||
|
(chown file (passwd:uid user) (passwd:gid group)))
|
||
|
(find-files root #:directories? #t))))
|
||
|
;; Delete stale pid files
|
||
|
(let ((pid-dir (string-append root "/celery/pids"))
|
||
|
(user (getpwnam user))
|
||
|
(group (getpwnam group)))
|
||
|
(when (file-exists? pid-dir)
|
||
|
(delete-file-recursively pid-dir))
|
||
|
(mkdir pid-dir)
|
||
|
(chmod pid-dir #o750)
|
||
|
(chown pid-dir (passwd:uid user) (passwd:gid group)))
|
||
|
(let ((pid-dir (string-append root "/home"))
|
||
|
(user (getpwnam user))
|
||
|
(group (getpwnam group)))
|
||
|
(when (file-exists? pid-dir)
|
||
|
(delete-file-recursively pid-dir))
|
||
|
(mkdir pid-dir)
|
||
|
(chmod pid-dir #o750)
|
||
|
(chown pid-dir (passwd:uid user) (passwd:gid group)))
|
||
|
(setenv "DATA_DIR" root)
|
||
|
(let ((guix-dir (string-append root "/guix"))
|
||
|
(user (getpwnam user))
|
||
|
(group (getpwnam group)))
|
||
|
(when (file-exists? guix-dir)
|
||
|
(delete-file-recursively guix-dir))
|
||
|
(mkdir guix-dir)
|
||
|
(call-with-output-file (string-append root "/guix/__init__.py")
|
||
|
(const #t))
|
||
|
(chmod guix-dir #o750)
|
||
|
(chmod (string-append root "/guix/__init__.py") #o750)
|
||
|
(chown guix-dir (passwd:uid user) (passwd:gid group))
|
||
|
;; TODO: This is copied from the touch implementation somewhere
|
||
|
(copy-file settings-file (string-append root "/guix/settings.py")))
|
||
|
(copy-file
|
||
|
#$(file-append python-weblate "/lib/python"
|
||
|
(version-major+minor (package-version python))
|
||
|
"/site-packages/weblate/examples/weblate.uwsgi.ini")
|
||
|
#$(string-append (weblate-configuration-root config) "/uwsgi.ini"))
|
||
|
(substitute* #$(string-append (weblate-configuration-root config) "/uwsgi.ini")
|
||
|
;; TODO: Change this too
|
||
|
;; uid = weblate
|
||
|
;; gid = weblate
|
||
|
(("socket\\s+=.*" all)
|
||
|
(string-append "socket = "
|
||
|
#$(weblate-configuration-uwsgi-listen config)
|
||
|
"\n")))
|
||
|
(delete-file (string-append root "/guix/settings.py"))
|
||
|
(copy-file #$(weblate-configuration-settings-file config)
|
||
|
(string-append root "/guix/settings.py"))
|
||
|
(let ((user (getpwnam user))
|
||
|
(group (getpwnam group)))
|
||
|
(for-each
|
||
|
(lambda (file)
|
||
|
(let ((file (string-append
|
||
|
#$(weblate-configuration-root config)
|
||
|
file)))
|
||
|
(chmod file #o750)
|
||
|
(chown file (passwd:uid user) (passwd:gid group))))
|
||
|
'("/guix/settings.py" "/guix/__init__.py" "/uwsgi.ini")))
|
||
|
(setenv "PYTHONPATH" #$(weblate-configuration-root config))
|
||
|
(setenv "DJANGO_SETTINGS_MODULE" "guix.settings")
|
||
|
(setenv "GI_TYPELIB_PATH"
|
||
|
;; FIXE: use the correct path
|
||
|
"/run/current-system/profile/lib/girepository-1.0")
|
||
|
(let ((pid (primitive-fork))
|
||
|
(weblate (getpwnam "weblate")))
|
||
|
(if (eq? pid 0)
|
||
|
(dynamic-wind
|
||
|
(const #t)
|
||
|
(lambda ()
|
||
|
(setgid (passwd:gid weblate))
|
||
|
(setuid (passwd:uid weblate))
|
||
|
(primitive-exit
|
||
|
(if (system*
|
||
|
#$(file-append python-weblate "/bin/weblate")
|
||
|
"collectstatic" "--noinput")
|
||
|
0
|
||
|
1)))
|
||
|
(lambda ()
|
||
|
(primitive-exit 1)))
|
||
|
(zero? (cdr (waitpid pid)))))
|
||
|
(let ((user (getpwnam user))
|
||
|
(group (getpwnam group)))
|
||
|
(for-each
|
||
|
(lambda (file)
|
||
|
;; nginx needs to serve static files
|
||
|
(chmod file #o750)
|
||
|
(chown file (passwd:uid user) (passwd:gid group)))
|
||
|
(find-files "static" #:directories? #t))
|
||
|
;; Be sure that private key permissions are right
|
||
|
(let ((private-key (string-append root "/ssh/id_rsa")))
|
||
|
(chmod private-key #o600)
|
||
|
(chown private-key (passwd:uid user) (passwd:gid group)))))))
|
||
|
|
||
|
(define (weblate-nginx-service config)
|
||
|
(let ((listen (weblate-configuration-listen config))
|
||
|
(root (weblate-configuration-root config)))
|
||
|
(list
|
||
|
(nginx-server-configuration
|
||
|
(listen listen)
|
||
|
(server-name '("weblate"))
|
||
|
(root root)
|
||
|
(locations
|
||
|
(list
|
||
|
(nginx-location-configuration
|
||
|
(uri "~ ^/favicon.ico$")
|
||
|
(body `(,(string-append "alias " root "/static/favicon.ico;")
|
||
|
"expires 30d;")))
|
||
|
(nginx-location-configuration
|
||
|
(uri "/static/")
|
||
|
(body `(,(string-append "alias " root "/static/;")
|
||
|
"expires 30d;")))
|
||
|
(nginx-location-configuration
|
||
|
(uri "/media/")
|
||
|
(body `(,(string-append "alias " root "/media/;")
|
||
|
"expires 30d;")))
|
||
|
(nginx-location-configuration
|
||
|
(uri "/")
|
||
|
(body
|
||
|
`(,#~(string-append
|
||
|
"include " '#$nginx
|
||
|
"/share/nginx/conf/uwsgi_params;")
|
||
|
"# Needed for long running operations in"
|
||
|
"# admin interface"
|
||
|
"uwsgi_read_timeout 3600;"
|
||
|
"# Adjust based to uwsgi configuration:"
|
||
|
"# uwsgi_pass unix:///run/uwsgi/app/weblate/socket;"
|
||
|
,(string-append "uwsgi_pass "
|
||
|
(weblate-configuration-uwsgi-listen config)
|
||
|
";"))))))
|
||
|
(try-files (list "$uri" "$uri/index.html"))))))
|
||
|
|
||
|
(define (uwsgi-weblate-service config)
|
||
|
(shepherd-service
|
||
|
(documentation "Run uwsgi weblate service.")
|
||
|
(provision '(weblate-uwsgi))
|
||
|
(requirement '(networking))
|
||
|
(start #~(make-forkexec-constructor
|
||
|
'(#$(file-append uwsgi "/bin/uwsgi")
|
||
|
#$(string-append (weblate-configuration-root config) "/uwsgi.ini"))
|
||
|
#:environment-variables
|
||
|
`(,(string-append "PYTHONPATH="
|
||
|
#$(weblate-configuration-root config))
|
||
|
"DJANGO_SETTINGS_MODULE=guix.settings"
|
||
|
"GIT_SSL_CAINFO=/etc/ssl/certs/ca-certificates.crt"
|
||
|
"LC_ALL=en_US.utf8" "LANG=en_US.utf8"
|
||
|
"GI_TYPELIB_PATH=/run/current-system/profile/lib/girepository-1.0")
|
||
|
;; ;; #:environment-variables
|
||
|
;; (list (string-append "PYTHON_PATH=" root))
|
||
|
#:user #$(weblate-configuration-user config)))
|
||
|
(stop #~(make-kill-destructor))))
|
||
|
|
||
|
;; FIXME: this is an hack to get a celery program that knows where to
|
||
|
;; find weblate dependencies
|
||
|
(define python-celery-for-weblate
|
||
|
(package
|
||
|
(inherit python-celery)
|
||
|
(propagated-inputs
|
||
|
(list
|
||
|
(append
|
||
|
(package-inputs python-celery)
|
||
|
`("python-weblate" ,python-weblate))))))
|
||
|
|
||
|
(define (celery-weblate-service config)
|
||
|
(shepherd-service
|
||
|
(documentation "Run celery weblate service. Restart with RESTART.")
|
||
|
(provision '(weblate-celery))
|
||
|
;; TODO: abstract the celery call and put it here according to
|
||
|
;; https://docs.weblate.org/en/latest/admin/install.html#background-tasks-using-celery
|
||
|
;; (actions '((shepherd-action
|
||
|
;; (name 'reststart)
|
||
|
;; (documentation "Restart celery runners")
|
||
|
;; (procedure #~(lambda (running . args)
|
||
|
;; (format #t "Hello, friend! arguments: ~s\n"
|
||
|
;; args)
|
||
|
;; #t)))))
|
||
|
(requirement '(networking)) ;redis?
|
||
|
(auto-start? #t)
|
||
|
(start #~(lambda _ ;; celery is forking
|
||
|
(let ((pid (primitive-fork))
|
||
|
(weblate (getpwnam "weblate")))
|
||
|
(if (eq? pid 0)
|
||
|
(dynamic-wind
|
||
|
(const #t)
|
||
|
(lambda ()
|
||
|
(setgid (passwd:gid weblate))
|
||
|
(setuid (passwd:uid weblate))
|
||
|
(setenv "GIT_SSL_CAINFO" "/etc/ssl/certs/ca-certificates.crt")
|
||
|
(primitive-exit
|
||
|
(if (zero?
|
||
|
(system*
|
||
|
#$(file-append python-celery-for-weblate "/bin/celery")
|
||
|
"multi" "start"
|
||
|
;; Processes to start
|
||
|
"celery" "notify" "backup" "memory" "translate"
|
||
|
;; must be under a folder which is writable by weblate!
|
||
|
(string-append
|
||
|
"--pidfile="
|
||
|
#$(weblate-configuration-root config)
|
||
|
"/celery/pids/weblate-%n.pid")
|
||
|
(string-append
|
||
|
"--logfile="
|
||
|
#$(weblate-configuration-root config)
|
||
|
"/celery/weblate-%n%I.log")
|
||
|
;; FIXME: pass as param!
|
||
|
(string-append "--loglevel=" "INFO")
|
||
|
"-A" "weblate.utils"
|
||
|
"--beat:celery"
|
||
|
"--queues:celery=celery"
|
||
|
"--prefetch-multiplier:celery=4"
|
||
|
"--queues:notify=notify"
|
||
|
"--prefetch-multiplier:notify=10"
|
||
|
"--queues:memory=memory"
|
||
|
"--prefetch-multiplier:memory=10"
|
||
|
"--queues:translate=translate"
|
||
|
"--prefetch-multiplier:translate=4"
|
||
|
"--concurrency:backup=1"
|
||
|
"--queues:backup=backup"
|
||
|
"--prefetch-multiplier:backup=2"))
|
||
|
0
|
||
|
1)))
|
||
|
(lambda ()
|
||
|
(primitive-exit 1)))
|
||
|
(zero? (cdr (waitpid pid)))))
|
||
|
#:environment-variables
|
||
|
;; FIXME: use python-weblate store path instead of this
|
||
|
`(,(string-append
|
||
|
"PYTHONPATH="
|
||
|
(string-join
|
||
|
(list
|
||
|
;; for settings path
|
||
|
#$(weblate-configuration-root config)
|
||
|
;; FIXME: Replace the hack above
|
||
|
;; (python-celery-for-weblate) with the right list
|
||
|
;; of search paths. This include weblate path and
|
||
|
;; all weblate dependencies
|
||
|
)
|
||
|
":"))
|
||
|
"GIT_SSL_CAINFO=/etc/ssl/certs/ca-certificates.crt"
|
||
|
"DJANGO_SETTINGS_MODULE=guix.settings"
|
||
|
;; Internal Weblate variable to indicate we're running inside Celery
|
||
|
"CELERY_WORKER_RUNNING=1")
|
||
|
;; #:environment-variables
|
||
|
;; '(
|
||
|
;;
|
||
|
;; "LC_ALL=en_US.utf8" "LANG=en_US.utf8"
|
||
|
;; "GI_TYPELIB_PATH=/run/current-system/profile/lib/girepository-1.0")
|
||
|
;; ;; #:environment-variables
|
||
|
;; (list (string-append "PYTHON_PATH=" root))
|
||
|
#:user #$(weblate-configuration-user config)
|
||
|
#:group #$(weblate-configuration-group config)))
|
||
|
(stop #~(make-kill-destructor))))
|