1234 lines
56 KiB
Scheme
1234 lines
56 KiB
Scheme
(define-module (fruix system freebsd render)
|
|
#:use-module (fruix system freebsd model)
|
|
#:use-module (ice-9 format)
|
|
#:use-module (ice-9 match)
|
|
#:use-module (srfi srfi-1)
|
|
#:use-module (srfi srfi-13)
|
|
#:use-module (rnrs io ports)
|
|
#:export (operating-system-generated-files
|
|
render-activation-rc-script
|
|
render-rc-script))
|
|
|
|
(define (render-loader-conf os)
|
|
(string-append
|
|
(string-join (map (lambda (entry)
|
|
(format #f "~a=\"~a\"" (car entry) (cdr entry)))
|
|
(effective-loader-entries os))
|
|
"\n")
|
|
"\n"))
|
|
|
|
(define (render-rc.conf os)
|
|
(let* ((entries (append `(("hostname" . ,(operating-system-host-name os))
|
|
("fruix_activate_enable" . "YES")
|
|
("fruix_shepherd_enable" . "YES"))
|
|
(operating-system-rc-conf-entries os))))
|
|
(string-append
|
|
(string-join (map (lambda (entry)
|
|
(format #f "~a=\"~a\"" (car entry) (cdr entry)))
|
|
entries)
|
|
"\n")
|
|
"\n")))
|
|
|
|
(define (group-name->gid groups name)
|
|
(let ((group (find (lambda (item)
|
|
(string=? (user-group-name item) name))
|
|
groups)))
|
|
(and group (user-group-gid group))))
|
|
|
|
(define (render-passwd os)
|
|
(let ((groups (operating-system-groups os)))
|
|
(string-append
|
|
(string-join
|
|
(map (lambda (account)
|
|
(format #f "~a:*:~a:~a:~a:~a:~a"
|
|
(user-account-name account)
|
|
(user-account-uid account)
|
|
(or (group-name->gid groups (user-account-group account))
|
|
(error "unknown primary group" (user-account-group account)))
|
|
(user-account-comment account)
|
|
(user-account-home account)
|
|
(user-account-shell account)))
|
|
(operating-system-users os))
|
|
"\n")
|
|
"\n")))
|
|
|
|
(define (render-master-passwd os)
|
|
(let ((groups (operating-system-groups os)))
|
|
(string-append
|
|
(string-join
|
|
(map (lambda (account)
|
|
(format #f "~a:*:~a:~a::0:0:~a:~a:~a"
|
|
(user-account-name account)
|
|
(user-account-uid account)
|
|
(or (group-name->gid groups (user-account-group account))
|
|
(error "unknown primary group" (user-account-group account)))
|
|
(user-account-comment account)
|
|
(user-account-home account)
|
|
(user-account-shell account)))
|
|
(operating-system-users os))
|
|
"\n")
|
|
"\n")))
|
|
|
|
(define (render-group os)
|
|
(let ((users (operating-system-users os)))
|
|
(string-append
|
|
(string-join
|
|
(map (lambda (group)
|
|
(let ((members (filter-map (lambda (account)
|
|
(and (member (user-group-name group)
|
|
(user-account-supplementary-groups account))
|
|
(user-account-name account)))
|
|
users)))
|
|
(format #f "~a:*:~a:~a"
|
|
(user-group-name group)
|
|
(user-group-gid group)
|
|
(string-join members ","))))
|
|
(operating-system-groups os))
|
|
"\n")
|
|
"\n")))
|
|
|
|
(define (fstab-fsck-fields fs)
|
|
(if (string=? (file-system-type fs) "ufs")
|
|
(if (string=? (file-system-mount-point fs) "/")
|
|
'(1 1)
|
|
'(2 2))
|
|
'(0 0)))
|
|
|
|
(define (render-fstab os)
|
|
(string-append
|
|
(string-join
|
|
(map (lambda (fs)
|
|
(let ((checks (fstab-fsck-fields fs)))
|
|
(format #f "~a\t~a\t~a\t~a\t~a\t~a"
|
|
(file-system-device fs)
|
|
(file-system-mount-point fs)
|
|
(file-system-type fs)
|
|
(file-system-options fs)
|
|
(first checks)
|
|
(second checks))))
|
|
(operating-system-file-systems os))
|
|
"\n")
|
|
"\n"))
|
|
|
|
(define (render-hosts os)
|
|
(string-append
|
|
"127.0.0.1\tlocalhost " (operating-system-host-name os) "\n"
|
|
"::1\tlocalhost\n"))
|
|
|
|
(define (render-shells os)
|
|
(let ((shells (delete-duplicates (map user-account-shell (operating-system-users os)))))
|
|
(string-append (string-join shells "\n") "\n")))
|
|
|
|
(define (render-motd os)
|
|
(string-append "Welcome to Fruix on FreeBSD (" (operating-system-host-name os) ")\n"))
|
|
|
|
(define (render-login-conf)
|
|
(string-append
|
|
"default:\\\n"
|
|
"\t:path=/sbin /bin /usr/sbin /usr/bin /usr/local/sbin /usr/local/bin:\\\n"
|
|
"\t:umask=022:\\\n"
|
|
"\t:charset=UTF-8:\\\n"
|
|
"\t:lang=C.UTF-8:\n"
|
|
"daemon:\\\n"
|
|
"\t:path=/sbin /bin /usr/sbin /usr/bin /usr/local/sbin /usr/local/bin:\\\n"
|
|
"\t:tc=default:\n"
|
|
"root:\\\n"
|
|
"\t:ignorenologin:\\\n"
|
|
"\t:tc=default:\n"))
|
|
|
|
(define (render-ttys)
|
|
(string-append
|
|
"console\tnone\tunknown\toff secure\n"
|
|
"ttyu0\tnone\tvt100\toff secure\n"
|
|
"xc0\tnone\txterm\toff secure\n"))
|
|
|
|
(define (render-root-authorized-keys os)
|
|
(if (null? (operating-system-root-authorized-keys os))
|
|
""
|
|
(string-append
|
|
(string-join (operating-system-root-authorized-keys os) "\n")
|
|
"\n")))
|
|
|
|
(define (render-sshd-config os)
|
|
(string-append
|
|
"Port 22\n"
|
|
"PermitRootLogin yes\n"
|
|
"PasswordAuthentication no\n"
|
|
"KbdInteractiveAuthentication no\n"
|
|
"ChallengeResponseAuthentication no\n"
|
|
"UsePAM no\n"
|
|
"PubkeyAuthentication yes\n"
|
|
"AuthorizedKeysFile .ssh/authorized_keys\n"
|
|
"PidFile /var/run/sshd.pid\n"
|
|
"UseDNS no\n"))
|
|
|
|
(define* (render-activation-script os #:key guile-store guile-extra-store shepherd-store)
|
|
(let* ((users (operating-system-users os))
|
|
(groups (operating-system-groups os))
|
|
(home-setup
|
|
(string-join
|
|
(map (lambda (account)
|
|
(let ((name (user-account-name account))
|
|
(uid (user-account-uid account))
|
|
(gid (or (group-name->gid groups (user-account-group account))
|
|
(error "unknown primary group" (user-account-group account))))
|
|
(home (user-account-home account))
|
|
(system? (user-account-system? account)))
|
|
(string-append
|
|
"mkdir -p " home "\n"
|
|
(if (or (string=? name "root") system?)
|
|
""
|
|
(format #f "if [ -x /usr/sbin/chown ]; then /usr/sbin/chown ~a:~a ~a 2>/dev/null || true; fi\n"
|
|
uid gid home)))))
|
|
users)
|
|
""))
|
|
(refresh-db-input-files
|
|
(string-join
|
|
(map (lambda (entry)
|
|
(match entry
|
|
((name mode)
|
|
(string-append
|
|
"if [ -f /run/current-system/etc/" name " ]; then rm -f /etc/" name "; cp /run/current-system/etc/" name " /etc/" name "; chmod " mode " /etc/" name "; fi\n"))))
|
|
'(("passwd" "0644")
|
|
("master.passwd" "0600")
|
|
("group" "0644")
|
|
("login.conf" "0644")))
|
|
""))
|
|
(ssh-section
|
|
(string-append
|
|
"mkdir -p /var/empty /etc/ssh /root/.ssh\n"
|
|
"chmod 700 /root/.ssh\n"
|
|
(if (null? (operating-system-root-authorized-keys os))
|
|
""
|
|
"if [ -f /run/current-system/root/.ssh/authorized_keys ]; then cp /run/current-system/root/.ssh/authorized_keys /root/.ssh/authorized_keys; chmod 600 /root/.ssh/authorized_keys; fi\n")
|
|
(if (sshd-enabled? os)
|
|
"if [ -x /usr/bin/ssh-keygen ]; then /usr/bin/ssh-keygen -A; fi\n"
|
|
""))))
|
|
(string-append
|
|
"#!/bin/sh\n"
|
|
"set -eu\n"
|
|
"logfile=/var/log/fruix-activate.log\n"
|
|
"mkdir -p /var/cron /var/db /var/lib/fruix /var/log /var/run /root /home /tmp\n"
|
|
": >> \"$logfile\"\n"
|
|
"trap 'status=$?; echo \"fruix-activate:exit status=$status\" >> \"$logfile\"' EXIT\n"
|
|
"echo \"fruix-activate:start\" >> \"$logfile\"\n"
|
|
"chmod 1777 /tmp\n"
|
|
refresh-db-input-files
|
|
"if [ -x /usr/bin/cap_mkdb ] && [ -f /etc/login.conf ]; then\n"
|
|
" if /usr/bin/cap_mkdb /etc/login.conf; then echo \"fruix-activate:cap_mkdb=ok\" >> \"$logfile\"; else echo \"fruix-activate:cap_mkdb=failed\" >> \"$logfile\"; fi\n"
|
|
"fi\n"
|
|
"if [ -x /usr/sbin/pwd_mkdb ] && [ -f /etc/master.passwd ]; then\n"
|
|
" if /usr/sbin/pwd_mkdb -p /etc/master.passwd; then echo \"fruix-activate:pwd_mkdb=ok\" >> \"$logfile\"; else echo \"fruix-activate:pwd_mkdb=failed\" >> \"$logfile\"; fi\n"
|
|
"fi\n"
|
|
home-setup
|
|
ssh-section
|
|
"echo \"fruix-activate:done\" >> \"$logfile\"\n")))
|
|
|
|
(define (pid1-mount-commands os)
|
|
(string-join
|
|
(filter-map (lambda (fs)
|
|
(and (not (string=? "/" (file-system-mount-point fs)))
|
|
(string-append
|
|
"mkdir -p '" (file-system-mount-point fs) "'\n"
|
|
"/sbin/mount -t '" (file-system-type fs)
|
|
"' -o '" (file-system-options fs)
|
|
"' '" (file-system-device fs)
|
|
"' '" (file-system-mount-point fs)
|
|
"' >/dev/null 2>&1 || true\n")))
|
|
(operating-system-file-systems os))
|
|
""))
|
|
|
|
(define (render-pid1-script os shepherd-store guile-store guile-extra-store)
|
|
(let ((ld-library-path (string-append guile-extra-store "/lib:"
|
|
guile-store "/lib:/usr/local/lib"))
|
|
(guile-system-path
|
|
(string-append guile-store "/share/guile/3.0:"
|
|
guile-store "/share/guile/site/3.0:"
|
|
guile-store "/share/guile/site:"
|
|
guile-store "/share/guile"))
|
|
(guile-load-path (string-append shepherd-store "/share/guile/site/3.0:"
|
|
guile-extra-store "/share/guile/site/3.0"))
|
|
(guile-system-compiled-path
|
|
(string-append guile-store "/lib/guile/3.0/ccache:"
|
|
guile-store "/lib/guile/3.0/site-ccache"))
|
|
(guile-load-compiled-path
|
|
(string-append shepherd-store "/lib/guile/3.0/site-ccache:"
|
|
guile-extra-store "/lib/guile/3.0/site-ccache"))
|
|
(guile-system-extensions-path (string-append guile-store "/lib/guile/3.0/extensions"))
|
|
(guile-extensions-path (string-append guile-extra-store "/lib/guile/3.0/extensions")))
|
|
(string-append
|
|
"#!/bin/sh\n"
|
|
"set -eu\n"
|
|
"PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin\n"
|
|
"/sbin/mount -u -o rw / >/dev/null 2>&1 || true\n"
|
|
(pid1-mount-commands os)
|
|
"/bin/hostname '" (operating-system-host-name os) "' >/dev/null 2>&1 || true\n"
|
|
"/run/current-system/activate\n"
|
|
"export GUILE_AUTO_COMPILE=0\n"
|
|
"export LANG='C.UTF-8'\n"
|
|
"export LC_ALL='C.UTF-8'\n"
|
|
"export LD_LIBRARY_PATH='" ld-library-path "'\n"
|
|
"export GUILE_SYSTEM_PATH='" guile-system-path "'\n"
|
|
"export GUILE_LOAD_PATH='" guile-load-path "'\n"
|
|
"export GUILE_SYSTEM_COMPILED_PATH='" guile-system-compiled-path "'\n"
|
|
"export GUILE_LOAD_COMPILED_PATH='" guile-load-compiled-path "'\n"
|
|
"export GUILE_SYSTEM_EXTENSIONS_PATH='" guile-system-extensions-path "'\n"
|
|
"export GUILE_EXTENSIONS_PATH='" guile-extensions-path "'\n"
|
|
"exec " guile-store "/bin/guile --no-auto-compile " shepherd-store "/bin/shepherd -I -s /var/run/shepherd.sock -c /run/current-system/shepherd/init.scm --pid=/var/run/shepherd.pid -l /var/log/shepherd.log\n")))
|
|
|
|
(define (render-shepherd-config os)
|
|
(let* ((ready-marker (operating-system-ready-marker os))
|
|
(pid1? (pid1-init-mode? os))
|
|
(start-sshd? (and pid1? (or (sshd-enabled? os)
|
|
(member 'sshd (operating-system-services os)))))
|
|
(ready-requirements (if start-sshd?
|
|
"'(fruix-logger sshd)"
|
|
"'(fruix-logger)"))
|
|
(pid1-helpers
|
|
(if pid1?
|
|
(string-append
|
|
"(define (run-command program . args)\n"
|
|
" (let ((status (apply system* program args)))\n"
|
|
" (unless (zero? status)\n"
|
|
" (error \"command failed\" (cons program args) status))\n"
|
|
" #t))\n\n"
|
|
"(define* (freebsd-rc-service provision script-name\n"
|
|
" #:key\n"
|
|
" (requirement '())\n"
|
|
" (documentation\n"
|
|
" \"Manage a FreeBSD rc.d service through 'service'.\"))\n"
|
|
" (service provision\n"
|
|
" #:documentation documentation\n"
|
|
" #:requirement requirement\n"
|
|
" #:start (lambda _\n"
|
|
" (run-command \"/usr/sbin/service\" script-name \"onestart\")\n"
|
|
" #t)\n"
|
|
" #:stop (lambda _\n"
|
|
" (run-command \"/usr/sbin/service\" script-name \"onestop\")\n"
|
|
" #f)\n"
|
|
" #:respawn? #f))\n\n")
|
|
""))
|
|
(pid1-services
|
|
(if pid1?
|
|
(string-append
|
|
(if start-sshd?
|
|
" (freebsd-rc-service '(netif) \"netif\"\n"
|
|
"")
|
|
(if start-sshd?
|
|
" #:requirement '(fruix-logger)\n"
|
|
"")
|
|
(if start-sshd?
|
|
" #:documentation \"Bring up FreeBSD networking from rc.conf.\")\n"
|
|
"")
|
|
(if start-sshd?
|
|
" (freebsd-rc-service '(sshd) \"sshd\"\n"
|
|
"")
|
|
(if start-sshd?
|
|
" #:requirement '(netif)\n"
|
|
"")
|
|
(if start-sshd?
|
|
" #:documentation \"Start OpenSSH under Shepherd PID 1.\")\n"
|
|
""))
|
|
"")))
|
|
(string-append
|
|
"(use-modules (shepherd service)\n"
|
|
" (ice-9 ftw)\n"
|
|
" (ice-9 popen))\n\n"
|
|
"(define ready-marker \"" ready-marker "\")\n\n"
|
|
"(define (mkdir-p* dir)\n"
|
|
" (unless (or (string=? dir \"\")\n"
|
|
" (string=? dir \"/\")\n"
|
|
" (file-exists? dir))\n"
|
|
" (mkdir-p* (dirname dir))\n"
|
|
" (mkdir dir)))\n\n"
|
|
"(define (ensure-parent-directory file)\n"
|
|
" (mkdir-p* (dirname file)))\n\n"
|
|
pid1-helpers
|
|
"(register-services\n"
|
|
" (list\n"
|
|
" (service '(fruix-logger)\n"
|
|
" #:documentation \"Append a boot trace line for Fruix.\"\n"
|
|
" #:start (lambda _\n"
|
|
" (ensure-parent-directory \"/var/log/fruix-shepherd.log\")\n"
|
|
" (let ((port (open-file \"/var/log/fruix-shepherd.log\" \"a\")))\n"
|
|
" (display \"fruix-shepherd-started\\n\" port)\n"
|
|
" (close-port port))\n"
|
|
" #t)\n"
|
|
" #:stop (lambda _ #f)\n"
|
|
" #:respawn? #f)\n"
|
|
pid1-services
|
|
" (service '(fruix-ready)\n"
|
|
" #:documentation \"Write the Fruix ready marker.\"\n"
|
|
" #:requirement " ready-requirements "\n"
|
|
" #:start (lambda _\n"
|
|
" (ensure-parent-directory ready-marker)\n"
|
|
" (call-with-output-file ready-marker\n"
|
|
" (lambda (port) (display \"ready\" port)))\n"
|
|
" #t)\n"
|
|
" #:stop (lambda _ #f)\n"
|
|
" #:respawn? #f)))\n\n"
|
|
"(start-service (lookup-service 'fruix-ready))\n")))
|
|
|
|
(define (render-activation-rc-script)
|
|
(string-append
|
|
"#!/bin/sh\n"
|
|
"# PROVIDE: fruix_activate\n"
|
|
"# REQUIRE: FILESYSTEMS\n"
|
|
"# BEFORE: LOGIN sshd fruix_shepherd\n"
|
|
"# KEYWORD: shutdown\n\n"
|
|
". /etc/rc.subr\n\n"
|
|
"name=fruix_activate\n"
|
|
"rcvar=fruix_activate_enable\n"
|
|
": ${fruix_activate_enable:=YES}\n"
|
|
"start_cmd=fruix_activate_start\n"
|
|
"stop_cmd=:\n\n"
|
|
"fruix_activate_start()\n"
|
|
"{\n"
|
|
" /run/current-system/activate\n"
|
|
"}\n\n"
|
|
"load_rc_config $name\n"
|
|
"run_rc_command \"$1\"\n"))
|
|
|
|
(define (render-rc-script shepherd-store guile-store guile-extra-store)
|
|
(let ((ld-library-path (string-append guile-extra-store "/lib:"
|
|
guile-store "/lib:/usr/local/lib"))
|
|
(guile-system-path
|
|
(string-append guile-store "/share/guile/3.0:"
|
|
guile-store "/share/guile/site/3.0:"
|
|
guile-store "/share/guile/site:"
|
|
guile-store "/share/guile"))
|
|
(guile-load-path (string-append shepherd-store "/share/guile/site/3.0:"
|
|
guile-extra-store "/share/guile/site/3.0"))
|
|
(guile-system-compiled-path
|
|
(string-append guile-store "/lib/guile/3.0/ccache:"
|
|
guile-store "/lib/guile/3.0/site-ccache"))
|
|
(guile-load-compiled-path
|
|
(string-append shepherd-store "/lib/guile/3.0/site-ccache:"
|
|
guile-extra-store "/lib/guile/3.0/site-ccache"))
|
|
(guile-system-extensions-path (string-append guile-store "/lib/guile/3.0/extensions"))
|
|
(guile-extensions-path (string-append guile-extra-store "/lib/guile/3.0/extensions")))
|
|
(string-append
|
|
"#!/bin/sh\n"
|
|
"# PROVIDE: fruix_shepherd\n"
|
|
"# REQUIRE: FILESYSTEMS fruix_activate\n"
|
|
"# BEFORE: LOGIN\n"
|
|
"# KEYWORD: shutdown\n\n"
|
|
". /etc/rc.subr\n\n"
|
|
"name=fruix_shepherd\n"
|
|
"rcvar=fruix_shepherd_enable\n"
|
|
": ${fruix_shepherd_enable:=YES}\n"
|
|
"pidfile=/var/run/shepherd.pid\n"
|
|
"socket=/var/run/shepherd.sock\n"
|
|
"config=/run/current-system/shepherd/init.scm\n"
|
|
"logfile=/var/log/shepherd.log\n"
|
|
"command=" shepherd-store "/bin/shepherd\n"
|
|
"start_cmd=fruix_shepherd_start\n"
|
|
"stop_cmd=fruix_shepherd_stop\n"
|
|
"status_cmd=fruix_shepherd_status\n\n"
|
|
"fruix_shepherd_start()\n"
|
|
"{\n"
|
|
" /usr/sbin/daemon -c -f -p \"$pidfile\" -o /var/log/shepherd-bootstrap.out /usr/bin/env \\\n"
|
|
" LANG='C.UTF-8' LC_ALL='C.UTF-8' \\\n"
|
|
" LD_LIBRARY_PATH='" ld-library-path "' \\\n"
|
|
" GUILE_SYSTEM_PATH='" guile-system-path "' \\\n"
|
|
" GUILE_LOAD_PATH='" guile-load-path "' \\\n"
|
|
" GUILE_SYSTEM_COMPILED_PATH='" guile-system-compiled-path "' \\\n"
|
|
" GUILE_LOAD_COMPILED_PATH='" guile-load-compiled-path "' \\\n"
|
|
" GUILE_SYSTEM_EXTENSIONS_PATH='" guile-system-extensions-path "' \\\n"
|
|
" GUILE_EXTENSIONS_PATH='" guile-extensions-path "' \\\n"
|
|
" " guile-store "/bin/guile --no-auto-compile " shepherd-store "/bin/shepherd -I -s \"$socket\" -c \"$config\" -l \"$logfile\"\n"
|
|
" for _try in 1 2 3 4 5 6 7 8 9 10; do\n"
|
|
" [ -f \"$pidfile\" ] && [ -S \"$socket\" ] && return 0\n"
|
|
" sleep 1\n"
|
|
" done\n"
|
|
" return 1\n"
|
|
"}\n\n"
|
|
"fruix_shepherd_stop()\n"
|
|
"{\n"
|
|
" env LANG='C.UTF-8' LC_ALL='C.UTF-8' \\\n"
|
|
" LD_LIBRARY_PATH='" ld-library-path "' \\\n"
|
|
" GUILE_SYSTEM_PATH='" guile-system-path "' \\\n"
|
|
" GUILE_LOAD_PATH='" guile-load-path "' \\\n"
|
|
" GUILE_SYSTEM_COMPILED_PATH='" guile-system-compiled-path "' \\\n"
|
|
" GUILE_LOAD_COMPILED_PATH='" guile-load-compiled-path "' \\\n"
|
|
" GUILE_SYSTEM_EXTENSIONS_PATH='" guile-system-extensions-path "' \\\n"
|
|
" GUILE_EXTENSIONS_PATH='" guile-extensions-path "' \\\n"
|
|
" " guile-store "/bin/guile --no-auto-compile " shepherd-store "/bin/herd -s \"$socket\" stop root >/dev/null 2>&1 || true\n"
|
|
" for _try in 1 2 3 4 5 6 7 8 9 10; do\n"
|
|
" [ ! -f \"$pidfile\" ] && return 0\n"
|
|
" sleep 1\n"
|
|
" done\n"
|
|
" kill \"$(cat \"$pidfile\")\" >/dev/null 2>&1 || true\n"
|
|
" rm -f \"$pidfile\"\n"
|
|
" return 0\n"
|
|
"}\n\n"
|
|
"fruix_shepherd_status()\n"
|
|
"{\n"
|
|
" [ -f \"$pidfile\" ] && kill -0 \"$(cat \"$pidfile\")\" >/dev/null 2>&1\n"
|
|
"}\n\n"
|
|
"load_rc_config $name\n"
|
|
"run_rc_command \"$1\"\n")))
|
|
|
|
(define (path-parent path)
|
|
(let ((index (string-rindex path #\/)))
|
|
(cond
|
|
((not index) ".")
|
|
((zero? index) "/")
|
|
(else (substring path 0 index)))))
|
|
|
|
(define (read-source-file-string path)
|
|
(call-with-input-file path get-string-all))
|
|
|
|
(define (bundled-fruix-node-files)
|
|
(let* ((repo-root (or (getenv "FRUIX_PROJECT_ROOT")
|
|
(let ((render-file (current-filename)))
|
|
(and render-file
|
|
(path-parent
|
|
(path-parent
|
|
(path-parent
|
|
(path-parent
|
|
(path-parent render-file)))))))
|
|
(getcwd)))
|
|
(guix-root (or (getenv "GUIX_SOURCE_DIR")
|
|
(string-append (getenv "HOME") "/repos/guix")))
|
|
(specs `((,(string-append repo-root "/scripts/fruix.scm")
|
|
. "share/fruix/node/scripts/fruix.scm")
|
|
(,(string-append repo-root "/modules/fruix/packages/freebsd.scm")
|
|
. "share/fruix/node/modules/fruix/packages/freebsd.scm")
|
|
(,(string-append repo-root "/modules/fruix/system/freebsd.scm")
|
|
. "share/fruix/node/modules/fruix/system/freebsd.scm")
|
|
(,(string-append repo-root "/modules/fruix/system/freebsd/build.scm")
|
|
. "share/fruix/node/modules/fruix/system/freebsd/build.scm")
|
|
(,(string-append repo-root "/modules/fruix/system/freebsd/executor.scm")
|
|
. "share/fruix/node/modules/fruix/system/freebsd/executor.scm")
|
|
(,(string-append repo-root "/modules/fruix/system/freebsd/media.scm")
|
|
. "share/fruix/node/modules/fruix/system/freebsd/media.scm")
|
|
(,(string-append repo-root "/modules/fruix/system/freebsd/model.scm")
|
|
. "share/fruix/node/modules/fruix/system/freebsd/model.scm")
|
|
(,(string-append repo-root "/modules/fruix/system/freebsd/render.scm")
|
|
. "share/fruix/node/modules/fruix/system/freebsd/render.scm")
|
|
(,(string-append repo-root "/modules/fruix/system/freebsd/source.scm")
|
|
. "share/fruix/node/modules/fruix/system/freebsd/source.scm")
|
|
(,(string-append repo-root "/modules/fruix/system/freebsd/utils.scm")
|
|
. "share/fruix/node/modules/fruix/system/freebsd/utils.scm")
|
|
(,(string-append guix-root "/guix/build/utils.scm")
|
|
. "share/fruix/node/guix/guix/build/utils.scm"))))
|
|
(map (lambda (entry)
|
|
(cons (cdr entry)
|
|
(read-source-file-string (car entry))))
|
|
specs)))
|
|
|
|
(define (render-installed-system-fruix os guile-store guile-extra-store shepherd-store)
|
|
(string-append
|
|
"#!/bin/sh\n"
|
|
"set -eu\n"
|
|
"PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin\n"
|
|
"tool_closure=$(readlink /run/current-system 2>/dev/null || true)\n"
|
|
"if [ -n \"$tool_closure\" ]; then\n"
|
|
" PATH=\"$tool_closure/profile/bin:$tool_closure/profile/sbin:$tool_closure/profile/usr/bin:$tool_closure/profile/usr/sbin:$PATH\"\n"
|
|
"fi\n"
|
|
"export PATH\n\n"
|
|
"system_root=/var/lib/fruix/system\n"
|
|
"generations_root=\"$system_root/generations\"\n"
|
|
"current_link=\"$system_root/current\"\n"
|
|
"current_generation_file=\"$system_root/current-generation\"\n"
|
|
"rollback_link=\"$system_root/rollback\"\n"
|
|
"rollback_generation_file=\"$system_root/rollback-generation\"\n"
|
|
"gcroots_root=/frx/var/fruix/gcroots\n"
|
|
"run_current_link=/run/current-system\n"
|
|
"node_root=/run/current-system/share/fruix/node\n"
|
|
"node_script=\"$node_root/scripts/fruix.scm\"\n"
|
|
"node_module_root=\"$node_root/modules\"\n"
|
|
"node_guix_root=\"$node_root/guix\"\n"
|
|
"declaration_file=/run/current-system/metadata/system-declaration.scm\n"
|
|
"declaration_info_file=/run/current-system/metadata/system-declaration-info.scm\n"
|
|
"declaration_system_file=/run/current-system/metadata/system-declaration-system\n"
|
|
"default_store_dir=/frx/store\n"
|
|
"guile_store='" guile-store "'\n"
|
|
"guile_extra_store='" guile-extra-store "'\n"
|
|
"shepherd_store='" shepherd-store "'\n"
|
|
"layout_version=2\n"
|
|
"host_name='" (operating-system-host-name os) "'\n"
|
|
"ready_marker='" (operating-system-ready-marker os) "'\n"
|
|
"init_mode='" (symbol->string (operating-system-init-mode os)) "'\n\n"
|
|
"usage()\n"
|
|
"{\n"
|
|
" cat <<'EOF'\n"
|
|
"Usage: fruix system status\n"
|
|
" fruix system build [DECLARATION [--system NAME] ...]\n"
|
|
" fruix system reconfigure [DECLARATION [--system NAME] ...]\n"
|
|
" fruix system switch /frx/store/...-fruix-system-...\n"
|
|
" fruix system rollback\n"
|
|
"EOF\n"
|
|
"}\n\n"
|
|
"die()\n"
|
|
"{\n"
|
|
" echo \"fruix: $*\" >&2\n"
|
|
" exit 1\n"
|
|
"}\n\n"
|
|
"read_link_maybe()\n"
|
|
"{\n"
|
|
" if [ -L \"$1\" ]; then\n"
|
|
" readlink \"$1\"\n"
|
|
" fi\n"
|
|
"}\n\n"
|
|
"read_file_maybe()\n"
|
|
"{\n"
|
|
" if [ -f \"$1\" ]; then\n"
|
|
" tr -d '\\n' < \"$1\"\n"
|
|
" fi\n"
|
|
"}\n\n"
|
|
"default_system_name()\n"
|
|
"{\n"
|
|
" read_file_maybe \"$declaration_system_file\"\n"
|
|
"}\n\n"
|
|
"symlink_force()\n"
|
|
"{\n"
|
|
" target=$1\n"
|
|
" link_name=$2\n"
|
|
" tmp_link=\"${link_name}.new.$$\"\n"
|
|
" mkdir -p \"$(dirname \"$link_name\")\"\n"
|
|
" if [ \"$link_name\" = \"$run_current_link\" ]; then\n"
|
|
" rm -f \"$tmp_link\"\n"
|
|
" ln -s \"$target\" \"$tmp_link\"\n"
|
|
" mv -h -f \"$tmp_link\" \"$link_name\"\n"
|
|
" else\n"
|
|
" rm -f \"$link_name\"\n"
|
|
" ln -s \"$target\" \"$link_name\"\n"
|
|
" fi\n"
|
|
"}\n\n"
|
|
"validate_closure()\n"
|
|
"{\n"
|
|
" closure=$1\n"
|
|
" [ -d \"$closure\" ] || die \"missing closure directory: $closure\"\n"
|
|
" [ -f \"$closure/activate\" ] || die \"closure is missing activate script: $closure\"\n"
|
|
" [ -f \"$closure/shepherd/init.scm\" ] || die \"closure is missing shepherd config: $closure\"\n"
|
|
" [ -f \"$closure/boot/loader.efi\" ] || die \"closure is missing loader.efi: $closure\"\n"
|
|
"}\n\n"
|
|
"ensure_default_declaration()\n"
|
|
"{\n"
|
|
" [ -f \"$declaration_file\" ] || die \"current declaration file is missing: $declaration_file\"\n"
|
|
" [ -f \"$declaration_info_file\" ] || die \"current declaration info file is missing: $declaration_info_file\"\n"
|
|
" current_system_name=$(default_system_name)\n"
|
|
" [ -n \"$current_system_name\" ] || die \"current declaration is missing a system variable name\"\n"
|
|
"}\n\n"
|
|
"run_node_cli()\n"
|
|
"{\n"
|
|
" [ -x \"$guile_store/bin/guile\" ] || die \"missing Guile runtime: $guile_store/bin/guile\"\n"
|
|
" [ -f \"$node_script\" ] || die \"missing bundled Fruix node CLI: $node_script\"\n"
|
|
" [ -d \"$node_module_root\" ] || die \"missing bundled Fruix modules: $node_module_root\"\n"
|
|
" [ -d \"$node_guix_root\" ] || die \"missing bundled Guix modules: $node_guix_root\"\n"
|
|
" guile_load_path=\"$node_module_root:$node_guix_root:$shepherd_store/share/guile/site/3.0:$guile_extra_store/share/guile/site/3.0\"\n"
|
|
" guile_system_path=\"$guile_store/share/guile/3.0:$guile_store/share/guile/site/3.0:$guile_store/share/guile/site:$guile_store/share/guile\"\n"
|
|
" guile_system_compiled_path=\"$guile_store/lib/guile/3.0/ccache:$guile_store/lib/guile/3.0/site-ccache\"\n"
|
|
" guile_load_compiled_path=\"$shepherd_store/lib/guile/3.0/site-ccache:$guile_extra_store/lib/guile/3.0/site-ccache\"\n"
|
|
" guile_system_extensions_path=\"$guile_store/lib/guile/3.0/extensions\"\n"
|
|
" guile_extensions_path=\"$guile_extra_store/lib/guile/3.0/extensions\"\n"
|
|
" ld_library_path=\"$guile_extra_store/lib:$guile_store/lib:/usr/local/lib\"\n"
|
|
" env \\\n"
|
|
" GUILE_AUTO_COMPILE=0 \\\n"
|
|
" GUILE_SYSTEM_PATH=\"$guile_system_path\" \\\n"
|
|
" GUILE_LOAD_PATH=\"$guile_load_path\" \\\n"
|
|
" GUILE_SYSTEM_COMPILED_PATH=\"$guile_system_compiled_path\" \\\n"
|
|
" GUILE_LOAD_COMPILED_PATH=\"$guile_load_compiled_path\" \\\n"
|
|
" GUILE_SYSTEM_EXTENSIONS_PATH=\"$guile_system_extensions_path\" \\\n"
|
|
" GUILE_EXTENSIONS_PATH=\"$guile_extensions_path\" \\\n"
|
|
" LD_LIBRARY_PATH=\"$ld_library_path\" \\\n"
|
|
" GUILE_PREFIX=\"$guile_store\" \\\n"
|
|
" GUILE_EXTRA_PREFIX=\"$guile_extra_store\" \\\n"
|
|
" SHEPHERD_PREFIX=\"$shepherd_store\" \\\n"
|
|
" FRUIX_GUILE_STORE=\"$guile_store\" \\\n"
|
|
" FRUIX_GUILE_EXTRA_STORE=\"$guile_extra_store\" \\\n"
|
|
" FRUIX_SHEPHERD_STORE=\"$shepherd_store\" \\\n"
|
|
" GUIX_SOURCE_DIR=\"$node_guix_root\" \\\n"
|
|
" FRUIX_PROJECT_ROOT=\"$node_root\" \\\n"
|
|
" \"$guile_store/bin/guile\" --no-auto-compile -s \"$node_script\" \"$@\"\n"
|
|
"}\n\n"
|
|
"system_build()\n"
|
|
"{\n"
|
|
" if [ $# -eq 0 ]; then\n"
|
|
" ensure_default_declaration\n"
|
|
" run_node_cli system build \"$declaration_file\" --system \"$current_system_name\" --store \"$default_store_dir\"\n"
|
|
" else\n"
|
|
" run_node_cli system build \"$@\"\n"
|
|
" fi\n"
|
|
"}\n\n"
|
|
"reconfigure_system()\n"
|
|
"{\n"
|
|
" build_output=$(mktemp /tmp/fruix-system-reconfigure.XXXXXX)\n"
|
|
" if [ $# -eq 0 ]; then\n"
|
|
" ensure_default_declaration\n"
|
|
" if ! run_node_cli system build \"$declaration_file\" --system \"$current_system_name\" --store \"$default_store_dir\" > \"$build_output\"; then\n"
|
|
" cat \"$build_output\" >&2 || true\n"
|
|
" rm -f \"$build_output\"\n"
|
|
" exit 1\n"
|
|
" fi\n"
|
|
" else\n"
|
|
" if ! run_node_cli system build \"$@\" > \"$build_output\"; then\n"
|
|
" cat \"$build_output\" >&2 || true\n"
|
|
" rm -f \"$build_output\"\n"
|
|
" exit 1\n"
|
|
" fi\n"
|
|
" fi\n"
|
|
" closure=$(sed -n 's/^closure_path=//p' \"$build_output\" | tail -n 1)\n"
|
|
" [ -n \"$closure\" ] || die \"failed to recover closure_path from in-system build output\"\n"
|
|
" cat \"$build_output\"\n"
|
|
" rm -f \"$build_output\"\n"
|
|
" switch_to_closure \"$closure\"\n"
|
|
" printf 'reconfigure_closure=%s\\n' \"$closure\"\n"
|
|
" printf 'reboot_required=true\\n'\n"
|
|
"}\n\n"
|
|
"max_generation_number()\n"
|
|
"{\n"
|
|
" max=0\n"
|
|
" if [ -d \"$generations_root\" ]; then\n"
|
|
" for path in \"$generations_root\"/*; do\n"
|
|
" [ -d \"$path\" ] || continue\n"
|
|
" base=$(basename \"$path\")\n"
|
|
" case \"$base\" in\n"
|
|
" ''|*[!0-9]*)\n"
|
|
" continue\n"
|
|
" ;;\n"
|
|
" esac\n"
|
|
" if [ \"$base\" -gt \"$max\" ]; then\n"
|
|
" max=$base\n"
|
|
" fi\n"
|
|
" done\n"
|
|
" fi\n"
|
|
" printf '%s\\n' \"$max\"\n"
|
|
"}\n\n"
|
|
"next_generation_number()\n"
|
|
"{\n"
|
|
" max=$(max_generation_number)\n"
|
|
" printf '%s\\n' $((max + 1))\n"
|
|
"}\n\n"
|
|
"write_generation_metadata()\n"
|
|
"{\n"
|
|
" generation=$1\n"
|
|
" closure=$2\n"
|
|
" action=$3\n"
|
|
" previous_generation=$4\n"
|
|
" previous_closure=$5\n"
|
|
" generation_dir=\"$generations_root/$generation\"\n"
|
|
" install_metadata_path=\"/var/lib/fruix/system/generations/$generation/install.scm\"\n"
|
|
" cat > \"$generation_dir/metadata.scm\" <<EOF\n"
|
|
"((system-generation-version . \"$layout_version\")\n"
|
|
" (generation-number . $generation)\n"
|
|
" (host-name . \"$host_name\")\n"
|
|
" (ready-marker . \"$ready_marker\")\n"
|
|
" (init-mode . $init_mode)\n"
|
|
" (closure-path . \"$closure\")\n"
|
|
" (parameters-file . \"$closure/parameters.scm\")\n"
|
|
" (freebsd-base-file . \"$closure/metadata/freebsd-base.scm\")\n"
|
|
" (freebsd-source-file . \"$closure/metadata/freebsd-source.scm\")\n"
|
|
" (freebsd-source-materializations-file . \"$closure/metadata/freebsd-source-materializations.scm\")\n"
|
|
" (host-base-provenance-file . \"$closure/metadata/host-base-provenance.scm\")\n"
|
|
" (store-layout-file . \"$closure/metadata/store-layout.scm\")\n"
|
|
" (install-metadata-path . \"$install_metadata_path\")\n"
|
|
" (deployment-action . \"$action\")\n"
|
|
" (previous-generation-number . \"$previous_generation\")\n"
|
|
" (previous-closure-path . \"$previous_closure\"))\n"
|
|
"EOF\n"
|
|
" chmod 644 \"$generation_dir/metadata.scm\"\n"
|
|
"}\n\n"
|
|
"write_generation_provenance()\n"
|
|
"{\n"
|
|
" generation=$1\n"
|
|
" closure=$2\n"
|
|
" generation_dir=\"$generations_root/$generation\"\n"
|
|
" cat > \"$generation_dir/provenance.scm\" <<EOF\n"
|
|
"((closure-path . \"$closure\")\n"
|
|
" (parameters-file . \"$closure/parameters.scm\")\n"
|
|
" (freebsd-base-file . \"$closure/metadata/freebsd-base.scm\")\n"
|
|
" (freebsd-source-file . \"$closure/metadata/freebsd-source.scm\")\n"
|
|
" (freebsd-source-materializations-file . \"$closure/metadata/freebsd-source-materializations.scm\")\n"
|
|
" (host-base-provenance-file . \"$closure/metadata/host-base-provenance.scm\")\n"
|
|
" (store-layout-file . \"$closure/metadata/store-layout.scm\"))\n"
|
|
"EOF\n"
|
|
" chmod 644 \"$generation_dir/provenance.scm\"\n"
|
|
"}\n\n"
|
|
"write_generation_install_metadata()\n"
|
|
"{\n"
|
|
" generation=$1\n"
|
|
" closure=$2\n"
|
|
" action=$3\n"
|
|
" previous_generation=$4\n"
|
|
" previous_closure=$5\n"
|
|
" generation_dir=\"$generations_root/$generation\"\n"
|
|
" cat > \"$generation_dir/install.scm\" <<EOF\n"
|
|
"((deployment-kind . \"$action\")\n"
|
|
" (generation-number . $generation)\n"
|
|
" (closure-path . \"$closure\")\n"
|
|
" (previous-generation-number . \"$previous_generation\")\n"
|
|
" (previous-closure-path . \"$previous_closure\")\n"
|
|
" (freebsd-base-file . \"$closure/metadata/freebsd-base.scm\")\n"
|
|
" (freebsd-source-file . \"$closure/metadata/freebsd-source.scm\")\n"
|
|
" (freebsd-source-materializations-file . \"$closure/metadata/freebsd-source-materializations.scm\")\n"
|
|
" (store-layout-file . \"$closure/metadata/store-layout.scm\"))\n"
|
|
"EOF\n"
|
|
" chmod 644 \"$generation_dir/install.scm\"\n"
|
|
"}\n\n"
|
|
"prepare_generation()\n"
|
|
"{\n"
|
|
" generation=$1\n"
|
|
" closure=$2\n"
|
|
" action=$3\n"
|
|
" previous_generation=$4\n"
|
|
" previous_closure=$5\n"
|
|
" generation_dir=\"$generations_root/$generation\"\n"
|
|
" mkdir -p \"$generation_dir\"\n"
|
|
" symlink_force \"$closure\" \"$generation_dir/closure\"\n"
|
|
" write_generation_metadata \"$generation\" \"$closure\" \"$action\" \"$previous_generation\" \"$previous_closure\"\n"
|
|
" write_generation_provenance \"$generation\" \"$closure\"\n"
|
|
" write_generation_install_metadata \"$generation\" \"$closure\" \"$action\" \"$previous_generation\" \"$previous_closure\"\n"
|
|
"}\n\n"
|
|
"update_efi_loader()\n"
|
|
"{\n"
|
|
" closure=$1\n"
|
|
" [ -e /dev/gpt/efiboot ] || return 0\n"
|
|
" esp_mount=$(mktemp -d /tmp/fruix-efiboot.XXXXXX)\n"
|
|
" if /sbin/mount -t msdosfs /dev/gpt/efiboot \"$esp_mount\" >/dev/null 2>&1; then\n"
|
|
" mkdir -p \"$esp_mount/EFI/BOOT\"\n"
|
|
" cp \"$closure/boot/loader.efi\" \"$esp_mount/EFI/BOOT/BOOTX64.EFI\"\n"
|
|
" sync\n"
|
|
" /sbin/umount \"$esp_mount\" >/dev/null 2>&1 || true\n"
|
|
" fi\n"
|
|
" rmdir \"$esp_mount\" >/dev/null 2>&1 || true\n"
|
|
"}\n\n"
|
|
"status()\n"
|
|
"{\n"
|
|
" current_generation=$(read_file_maybe \"$current_generation_file\")\n"
|
|
" current_generation_link=$(read_link_maybe \"$current_link\")\n"
|
|
" current_closure=$(read_link_maybe \"$run_current_link\")\n"
|
|
" rollback_generation=$(read_file_maybe \"$rollback_generation_file\")\n"
|
|
" rollback_generation_link=$(read_link_maybe \"$rollback_link\")\n"
|
|
" rollback_closure=\"\"\n"
|
|
" if [ -n \"$rollback_generation_link\" ] && [ -L \"$system_root/$rollback_generation_link/closure\" ]; then\n"
|
|
" rollback_closure=$(readlink \"$system_root/$rollback_generation_link/closure\")\n"
|
|
" fi\n"
|
|
" printf 'current_generation=%s\\n' \"$current_generation\"\n"
|
|
" printf 'current_link=%s\\n' \"$current_generation_link\"\n"
|
|
" printf 'current_closure=%s\\n' \"$current_closure\"\n"
|
|
" printf 'rollback_generation=%s\\n' \"$rollback_generation\"\n"
|
|
" printf 'rollback_link=%s\\n' \"$rollback_generation_link\"\n"
|
|
" printf 'rollback_closure=%s\\n' \"$rollback_closure\"\n"
|
|
"}\n\n"
|
|
"switch_to_closure()\n"
|
|
"{\n"
|
|
" target_closure=$1\n"
|
|
" validate_closure \"$target_closure\"\n"
|
|
" current_generation=$(read_file_maybe \"$current_generation_file\")\n"
|
|
" current_closure=$(read_link_maybe \"$run_current_link\")\n"
|
|
" [ -n \"$current_generation\" ] || die \"missing current generation metadata\"\n"
|
|
" [ -n \"$current_closure\" ] || die \"missing /run/current-system target\"\n"
|
|
" if [ \"$target_closure\" = \"$current_closure\" ]; then\n"
|
|
" status\n"
|
|
" return 0\n"
|
|
" fi\n"
|
|
" new_generation=$(next_generation_number)\n"
|
|
" prepare_generation \"$new_generation\" \"$target_closure\" switch \"$current_generation\" \"$current_closure\"\n"
|
|
" symlink_force \"generations/$current_generation\" \"$rollback_link\"\n"
|
|
" printf '%s\\n' \"$current_generation\" > \"$rollback_generation_file\"\n"
|
|
" symlink_force \"$current_closure\" \"$gcroots_root/rollback-system\"\n"
|
|
" symlink_force \"generations/$new_generation\" \"$current_link\"\n"
|
|
" printf '%s\\n' \"$new_generation\" > \"$current_generation_file\"\n"
|
|
" symlink_force \"$target_closure\" \"$gcroots_root/system-$new_generation\"\n"
|
|
" symlink_force \"$target_closure\" \"$gcroots_root/current-system\"\n"
|
|
" symlink_force \"$target_closure\" \"$run_current_link\"\n"
|
|
" update_efi_loader \"$target_closure\"\n"
|
|
" status\n"
|
|
"}\n\n"
|
|
"rollback_current_generation()\n"
|
|
"{\n"
|
|
" rollback_generation=$(read_file_maybe \"$rollback_generation_file\")\n"
|
|
" rollback_generation_link=$(read_link_maybe \"$rollback_link\")\n"
|
|
" [ -n \"$rollback_generation\" ] || die \"no rollback generation is recorded\"\n"
|
|
" [ -n \"$rollback_generation_link\" ] || die \"no rollback link is recorded\"\n"
|
|
" rollback_closure=$(read_link_maybe \"$system_root/$rollback_generation_link/closure\")\n"
|
|
" [ -n \"$rollback_closure\" ] || die \"rollback generation has no closure link\"\n"
|
|
" current_generation=$(read_file_maybe \"$current_generation_file\")\n"
|
|
" current_closure=$(read_link_maybe \"$run_current_link\")\n"
|
|
" [ -n \"$current_generation\" ] || die \"missing current generation metadata\"\n"
|
|
" [ -n \"$current_closure\" ] || die \"missing current closure link\"\n"
|
|
" symlink_force \"generations/$current_generation\" \"$rollback_link\"\n"
|
|
" printf '%s\\n' \"$current_generation\" > \"$rollback_generation_file\"\n"
|
|
" symlink_force \"$current_closure\" \"$gcroots_root/rollback-system\"\n"
|
|
" symlink_force \"$rollback_generation_link\" \"$current_link\"\n"
|
|
" printf '%s\\n' \"$rollback_generation\" > \"$current_generation_file\"\n"
|
|
" symlink_force \"$rollback_closure\" \"$gcroots_root/current-system\"\n"
|
|
" symlink_force \"$rollback_closure\" \"$run_current_link\"\n"
|
|
" update_efi_loader \"$rollback_closure\"\n"
|
|
" status\n"
|
|
"}\n\n"
|
|
"case \"${1:-}\" in\n"
|
|
" system)\n"
|
|
" case \"${2:-}\" in\n"
|
|
" status)\n"
|
|
" [ $# -eq 2 ] || { usage >&2; exit 1; }\n"
|
|
" status\n"
|
|
" ;;\n"
|
|
" build)\n"
|
|
" shift 2\n"
|
|
" system_build \"$@\"\n"
|
|
" ;;\n"
|
|
" reconfigure)\n"
|
|
" shift 2\n"
|
|
" reconfigure_system \"$@\"\n"
|
|
" ;;\n"
|
|
" switch)\n"
|
|
" [ $# -eq 3 ] || { usage >&2; exit 1; }\n"
|
|
" switch_to_closure \"$3\"\n"
|
|
" ;;\n"
|
|
" rollback)\n"
|
|
" [ $# -eq 2 ] || { usage >&2; exit 1; }\n"
|
|
" rollback_current_generation\n"
|
|
" ;;\n"
|
|
" --help|-h|'')\n"
|
|
" usage\n"
|
|
" ;;\n"
|
|
" *)\n"
|
|
" usage >&2\n"
|
|
" exit 1\n"
|
|
" ;;\n"
|
|
" esac\n"
|
|
" ;;\n"
|
|
" --help|-h|'')\n"
|
|
" usage\n"
|
|
" ;;\n"
|
|
" *)\n"
|
|
" usage >&2\n"
|
|
" exit 1\n"
|
|
" ;;\n"
|
|
"esac\n"))
|
|
(define (render-development-environment-script os)
|
|
(string-append
|
|
"#!/bin/sh\n"
|
|
"set -eu\n"
|
|
"profile=/run/current-system/development-profile\n"
|
|
"[ -d \"$profile\" ] || {\n"
|
|
" echo \"fruix-development-environment: development profile is not available\" >&2\n"
|
|
" exit 1\n"
|
|
"}\n"
|
|
"cat <<EOF\n"
|
|
"export FRUIX_DEVELOPMENT_PROFILE=\"$profile\"\n"
|
|
"export FRUIX_DEVELOPMENT_INCLUDE=\"$profile/usr/include\"\n"
|
|
"export FRUIX_DEVELOPMENT_LIB=\"$profile/lib\"\n"
|
|
"export FRUIX_DEVELOPMENT_SHARE_MK=\"$profile/usr/share/mk\"\n"
|
|
"export FRUIX_DEVELOPMENT_BIN=\"$profile/bin\"\n"
|
|
"export FRUIX_DEVELOPMENT_USR_BIN=\"$profile/usr/bin\"\n"
|
|
"export FRUIX_CC=\"$profile/bin/cc\"\n"
|
|
"export FRUIX_CXX=\"$profile/bin/c++\"\n"
|
|
"export FRUIX_AR=\"$profile/bin/ar\"\n"
|
|
"export FRUIX_RANLIB=\"$profile/bin/ranlib\"\n"
|
|
"export FRUIX_NM=\"$profile/bin/nm\"\n"
|
|
"export FRUIX_BMAKE=\"/usr/bin/make\"\n"
|
|
"export CC=\"$profile/bin/cc\"\n"
|
|
"export CXX=\"$profile/bin/c++\"\n"
|
|
"export AR=\"$profile/bin/ar\"\n"
|
|
"export RANLIB=\"$profile/bin/ranlib\"\n"
|
|
"export NM=\"$profile/bin/nm\"\n"
|
|
"export CPPFLAGS=\"-I$profile/usr/include\"\n"
|
|
"export CFLAGS=\"-I$profile/usr/include\"\n"
|
|
"export CXXFLAGS=\"-I$profile/usr/include\"\n"
|
|
"export LDFLAGS=\"-L$profile/lib\"\n"
|
|
"export MAKEFLAGS=\"-m $profile/usr/share/mk\"\n"
|
|
"export PATH=\"/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:$profile/bin:$profile/sbin:$profile/usr/bin:$profile/usr/sbin\"\n"
|
|
"EOF\n"))
|
|
(define (render-build-environment-script os)
|
|
(string-append
|
|
"#!/bin/sh\n"
|
|
"set -eu\n"
|
|
"profile=/run/current-system/build-profile\n"
|
|
"[ -d \"$profile\" ] || {\n"
|
|
" echo \"fruix-build-environment: build profile is not available\" >&2\n"
|
|
" exit 1\n"
|
|
"}\n"
|
|
"cat <<EOF\n"
|
|
"unset MAKEOBJDIRPREFIX MAKEFLAGS CC CXX AR RANLIB NM CPPFLAGS CFLAGS CXXFLAGS LDFLAGS\n"
|
|
"unset FRUIX_DEVELOPMENT_PROFILE FRUIX_DEVELOPMENT_INCLUDE FRUIX_DEVELOPMENT_LIB FRUIX_DEVELOPMENT_SHARE_MK\n"
|
|
"unset FRUIX_DEVELOPMENT_BIN FRUIX_DEVELOPMENT_USR_BIN FRUIX_CC FRUIX_CXX FRUIX_AR FRUIX_RANLIB FRUIX_NM FRUIX_BMAKE\n"
|
|
"export FRUIX_BUILD_PROFILE=\"$profile\"\n"
|
|
"export FRUIX_BUILD_INCLUDE=\"$profile/usr/include\"\n"
|
|
"export FRUIX_BUILD_LIB=\"$profile/lib\"\n"
|
|
"export FRUIX_BUILD_SHARE_MK=\"$profile/usr/share/mk\"\n"
|
|
"export FRUIX_BUILD_BIN=\"$profile/bin\"\n"
|
|
"export FRUIX_BUILD_USR_BIN=\"$profile/usr/bin\"\n"
|
|
"export FRUIX_BUILD_CC=\"$profile/bin/cc\"\n"
|
|
"export FRUIX_BUILD_CXX=\"$profile/bin/c++\"\n"
|
|
"export FRUIX_BUILD_AR=\"$profile/bin/ar\"\n"
|
|
"export FRUIX_BUILD_RANLIB=\"$profile/bin/ranlib\"\n"
|
|
"export FRUIX_BUILD_NM=\"$profile/bin/nm\"\n"
|
|
"export FRUIX_BMAKE=\"make\"\n"
|
|
"export PATH=\"/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:$profile/bin:$profile/sbin:$profile/usr/bin:$profile/usr/sbin\"\n"
|
|
"EOF\n"))
|
|
(define (render-self-hosted-native-build-script os)
|
|
(let* ((base-spec (freebsd-base-spec (operating-system-freebsd-base os)))
|
|
(base-name (assoc-ref base-spec 'name))
|
|
(version-label (assoc-ref base-spec 'version-label))
|
|
(release (assoc-ref base-spec 'release))
|
|
(branch (assoc-ref base-spec 'branch))
|
|
(declared-source-root (assoc-ref base-spec 'source-root))
|
|
(target (assoc-ref base-spec 'target))
|
|
(target-arch (assoc-ref base-spec 'target-arch))
|
|
(kernconf (assoc-ref base-spec 'kernconf))
|
|
(make-flags (or (assoc-ref base-spec 'make-flags) '()))
|
|
(build-common (string-join
|
|
(append (list (format #f "TARGET=~a" target)
|
|
(format #f "TARGET_ARCH=~a" target-arch)
|
|
(format #f "KERNCONF=~a" kernconf))
|
|
make-flags)
|
|
" "))
|
|
(install-common (string-append build-common " DB_FROM_SRC=yes")))
|
|
(string-append
|
|
"#!/bin/sh\n"
|
|
"set -eu\n"
|
|
"umask 022\n"
|
|
"profile=/run/current-system/build-profile\n"
|
|
"guest_host_name='" (operating-system-host-name os) "'\n"
|
|
"[ -d \"$profile\" ] || {\n"
|
|
" echo \"fruix-self-hosted-native-build: build profile is not available\" >&2\n"
|
|
" exit 1\n"
|
|
"}\n"
|
|
"[ -x /usr/local/bin/fruix-build-environment ] || {\n"
|
|
" echo \"fruix-self-hosted-native-build: build environment helper is missing\" >&2\n"
|
|
" exit 1\n"
|
|
"}\n"
|
|
"eval \"$(/usr/local/bin/fruix-build-environment)\"\n"
|
|
"[ \"${FRUIX_BUILD_PROFILE:-}\" = \"$profile\" ] || {\n"
|
|
" echo \"fruix-self-hosted-native-build: build environment helper exported an unexpected profile\" >&2\n"
|
|
" exit 1\n"
|
|
"}\n"
|
|
"[ -L /usr/include ] || {\n"
|
|
" echo \"fruix-self-hosted-native-build: /usr/include compatibility link is missing\" >&2\n"
|
|
" exit 1\n"
|
|
"}\n"
|
|
"[ \"$(readlink /usr/include)\" = \"/run/current-system/build-profile/usr/include\" ] || {\n"
|
|
" echo \"fruix-self-hosted-native-build: /usr/include points at the wrong target\" >&2\n"
|
|
" exit 1\n"
|
|
"}\n"
|
|
"[ -L /usr/share/mk ] || {\n"
|
|
" echo \"fruix-self-hosted-native-build: /usr/share/mk compatibility link is missing\" >&2\n"
|
|
" exit 1\n"
|
|
"}\n"
|
|
"[ \"$(readlink /usr/share/mk)\" = \"/run/current-system/build-profile/usr/share/mk\" ] || {\n"
|
|
" echo \"fruix-self-hosted-native-build: /usr/share/mk points at the wrong target\" >&2\n"
|
|
" exit 1\n"
|
|
"}\n"
|
|
"make_cmd=${FRUIX_BMAKE:-make}\n"
|
|
"jobs=${FRUIX_SELF_HOSTED_NATIVE_BUILD_JOBS:-$(sysctl -n hw.ncpu)}\n"
|
|
"case \"$jobs\" in\n"
|
|
" ''|*[!0-9]*)\n"
|
|
" echo \"fruix-self-hosted-native-build: invalid job count: $jobs\" >&2\n"
|
|
" exit 1\n"
|
|
" ;;\n"
|
|
"esac\n"
|
|
"run_id=${FRUIX_SELF_HOSTED_NATIVE_BUILD_ID:-$(date -u +%Y%m%dT%H%M%SZ)}\n"
|
|
"build_root_base=${FRUIX_SELF_HOSTED_NATIVE_BUILD_ROOT_BASE:-/var/tmp/fruix-self-hosted-native-builds}\n"
|
|
"result_root_base=${FRUIX_SELF_HOSTED_NATIVE_BUILD_OUTPUT_BASE:-/var/lib/fruix/native-builds}\n"
|
|
"build_root=$build_root_base/$run_id\n"
|
|
"result_root=$result_root_base/$run_id\n"
|
|
"logdir=$result_root/logs\n"
|
|
"status_file=$result_root/status\n"
|
|
"metadata_file=$result_root/metadata.txt\n"
|
|
"promotion_file=$result_root/promotion.scm\n"
|
|
"world_stage=$build_root/stage-world\n"
|
|
"kernel_stage=$build_root/stage-kernel\n"
|
|
"world_artifact=$result_root/artifacts/world\n"
|
|
"headers_artifact=$result_root/artifacts/headers\n"
|
|
"kernel_artifact=$result_root/artifacts/kernel\n"
|
|
"bootloader_artifact=$result_root/artifacts/bootloader\n"
|
|
"latest_link=$result_root_base/latest\n"
|
|
"mkdir -p \"$build_root\" \"$result_root\" \"$logdir\"\n"
|
|
"printf 'running\\n' > \"$status_file\"\n"
|
|
"fail_mark() {\n"
|
|
" rc=$?\n"
|
|
" if [ \"$rc\" -ne 0 ]; then\n"
|
|
" printf 'failed\\n' > \"$status_file\"\n"
|
|
" fi\n"
|
|
"}\n"
|
|
"trap fail_mark EXIT HUP INT TERM\n"
|
|
"closure=$(readlink /run/current-system)\n"
|
|
"store_layout=$closure/metadata/store-layout.scm\n"
|
|
"[ -f \"$store_layout\" ] || {\n"
|
|
" echo \"fruix-self-hosted-native-build: store layout metadata is missing\" >&2\n"
|
|
" exit 1\n"
|
|
"}\n"
|
|
"source_store=$(sed -n 's/.*\"\\(\\/frx\\/store\\/[^\"]*-freebsd-source-[^\"]*\\)\".*/\\1/p' \"$store_layout\" | head -n 1)\n"
|
|
"[ -n \"$source_store\" ] || {\n"
|
|
" echo \"fruix-self-hosted-native-build: failed to recover source store from store-layout.scm\" >&2\n"
|
|
" exit 1\n"
|
|
"}\n"
|
|
"source_root=$source_store/tree\n"
|
|
"[ -d \"$source_root\" ] || {\n"
|
|
" echo \"fruix-self-hosted-native-build: source root is missing: $source_root\" >&2\n"
|
|
" exit 1\n"
|
|
"}\n"
|
|
"mkdir -p \"$world_artifact\" \"$headers_artifact/usr\" \"$kernel_artifact/boot\" \"$bootloader_artifact/boot\"\n"
|
|
"export MAKEOBJDIRPREFIX=\"$build_root/obj\"\n"
|
|
"\"$make_cmd\" -j\"$jobs\" -C \"$source_root\" " build-common " buildworld > \"$logdir/buildworld.log\" 2>&1\n"
|
|
"\"$make_cmd\" -j\"$jobs\" -C \"$source_root\" " build-common " buildkernel > \"$logdir/buildkernel.log\" 2>&1\n"
|
|
"\"$make_cmd\" -C \"$source_root\" " install-common " DESTDIR=\"$world_stage\" installworld > \"$logdir/installworld.log\" 2>&1\n"
|
|
"\"$make_cmd\" -C \"$source_root\" " install-common " DESTDIR=\"$world_stage\" distribution > \"$logdir/distribution.log\" 2>&1\n"
|
|
"\"$make_cmd\" -C \"$source_root\" " install-common " DESTDIR=\"$kernel_stage\" installkernel > \"$logdir/installkernel.log\" 2>&1\n"
|
|
"cp -a \"$world_stage/.\" \"$world_artifact/\"\n"
|
|
"cp -a \"$kernel_stage/boot/kernel\" \"$kernel_artifact/boot/kernel\"\n"
|
|
"cp -a \"$world_stage/usr/include\" \"$headers_artifact/usr/include\"\n"
|
|
"mkdir -p \"$headers_artifact/usr/share\"\n"
|
|
"cp -a \"$world_stage/usr/share/mk\" \"$headers_artifact/usr/share/mk\"\n"
|
|
"cp -a \"$world_stage/boot/loader\" \"$bootloader_artifact/boot/loader\"\n"
|
|
"cp -a \"$world_stage/boot/loader.efi\" \"$bootloader_artifact/boot/loader.efi\"\n"
|
|
"cp -a \"$world_stage/boot/device.hints\" \"$bootloader_artifact/boot/device.hints\"\n"
|
|
"cp -a \"$world_stage/boot/defaults\" \"$bootloader_artifact/boot/defaults\"\n"
|
|
"cp -a \"$world_stage/boot/lua\" \"$bootloader_artifact/boot/lua\"\n"
|
|
"[ -f \"$world_artifact/bin/sh\" ]\n"
|
|
"[ -f \"$kernel_artifact/boot/kernel/kernel\" ]\n"
|
|
"[ -f \"$headers_artifact/usr/include/sys/param.h\" ]\n"
|
|
"[ -f \"$headers_artifact/usr/share/mk/bsd.prog.mk\" ]\n"
|
|
"[ -f \"$bootloader_artifact/boot/loader.efi\" ]\n"
|
|
"[ -f \"$bootloader_artifact/boot/defaults/loader.conf\" ]\n"
|
|
"[ -f \"$bootloader_artifact/boot/lua/loader.lua\" ]\n"
|
|
"sha_kernel=$(sha256 -q \"$kernel_artifact/boot/kernel/kernel\")\n"
|
|
"sha_loader=$(sha256 -q \"$bootloader_artifact/boot/loader.efi\")\n"
|
|
"sha_param=$(sha256 -q \"$headers_artifact/usr/include/sys/param.h\")\n"
|
|
"buildworld_tail=$(tail -n 20 \"$logdir/buildworld.log\" | tr '\\n' ' ')\n"
|
|
"buildkernel_tail=$(tail -n 20 \"$logdir/buildkernel.log\" | tr '\\n' ' ')\n"
|
|
"installworld_tail=$(tail -n 20 \"$logdir/installworld.log\" | tr '\\n' ' ')\n"
|
|
"distribution_tail=$(tail -n 20 \"$logdir/distribution.log\" | tr '\\n' ' ')\n"
|
|
"installkernel_tail=$(tail -n 20 \"$logdir/installkernel.log\" | tr '\\n' ' ')\n"
|
|
"root_df=$(df -h / | tail -n 1 | tr -s ' ' | tr '\\t' ' ')\n"
|
|
"build_root_size=$(du -sh \"$build_root\" | awk '{print $1}')\n"
|
|
"result_root_size=$(du -sh \"$result_root\" | awk '{print $1}')\n"
|
|
"world_artifact_size=$(du -sh \"$world_artifact\" | awk '{print $1}')\n"
|
|
"kernel_artifact_size=$(du -sh \"$kernel_artifact\" | awk '{print $1}')\n"
|
|
"headers_artifact_size=$(du -sh \"$headers_artifact\" | awk '{print $1}')\n"
|
|
"bootloader_artifact_size=$(du -sh \"$bootloader_artifact\" | awk '{print $1}')\n"
|
|
"rm -f \"$latest_link\"\n"
|
|
"ln -s \"$result_root\" \"$latest_link\"\n"
|
|
"cat >\"$promotion_file\" <<EOF\n"
|
|
"((native-build-result-version . \"1\")\n"
|
|
" (executor . ((kind . self-hosted)\n"
|
|
" (name . \"guest-self-hosted\")\n"
|
|
" (version . \"5\")\n"
|
|
" (properties . ((helper-path . \"/usr/local/bin/fruix-self-hosted-native-build\")\n"
|
|
" (guest-host-name . \"$guest_host_name\")\n"
|
|
" (build-root-base . \"$build_root_base\")\n"
|
|
" (result-root-base . \"$result_root_base\")))))\n"
|
|
" (run-id . \"$run_id\")\n"
|
|
" (guest-host-name . \"$guest_host_name\")\n"
|
|
" (closure-path . \"$closure\")\n"
|
|
" (build-profile . \"$profile\")\n"
|
|
" (freebsd-base . ((name . \"" base-name "\")\n"
|
|
" (version-label . \"" version-label "\")\n"
|
|
" (release . \"" release "\")\n"
|
|
" (branch . \"" branch "\")\n"
|
|
" (source-root . \"" declared-source-root "\")\n"
|
|
" (target . \"" target "\")\n"
|
|
" (target-arch . \"" target-arch "\")\n"
|
|
" (kernconf . \"" kernconf "\")))\n"
|
|
" (source . ((store-path . \"$source_store\")\n"
|
|
" (source-root . \"$source_root\")))\n"
|
|
" (build-policy . ((jobs . \"$jobs\")\n"
|
|
" (build-common . \"" build-common "\")\n"
|
|
" (install-common . \"" install-common "\")))\n"
|
|
" (artifacts . ((world . ((path . \"artifacts/world\")\n"
|
|
" (required-file . \"bin/sh\")))\n"
|
|
" (kernel . ((path . \"artifacts/kernel\")\n"
|
|
" (required-file . \"boot/kernel/kernel\")\n"
|
|
" (recorded-sha256 . \"$sha_kernel\")))\n"
|
|
" (headers . ((path . \"artifacts/headers\")\n"
|
|
" (required-file . \"usr/include/sys/param.h\")\n"
|
|
" (recorded-sha256 . \"$sha_param\")))\n"
|
|
" (bootloader . ((path . \"artifacts/bootloader\")\n"
|
|
" (required-file . \"boot/loader.efi\")\n"
|
|
" (recorded-sha256 . \"$sha_loader\"))))))\n"
|
|
"EOF\n"
|
|
"cat >\"$metadata_file\" <<EOF\n"
|
|
"run_id=$run_id\n"
|
|
"helper_version=5\n"
|
|
"executor_kind=self-hosted\n"
|
|
"executor_name=guest-self-hosted\n"
|
|
"executor_version=5\n"
|
|
"closure_path=$closure\n"
|
|
"guest_host_name=$guest_host_name\n"
|
|
"build_profile=$profile\n"
|
|
"source_store=$source_store\n"
|
|
"source_root=$source_root\n"
|
|
"build_jobs=$jobs\n"
|
|
"build_common=" build-common "\n"
|
|
"install_common=" install-common "\n"
|
|
"build_root=$build_root\n"
|
|
"result_root=$result_root\n"
|
|
"logdir=$logdir\n"
|
|
"status_file=$status_file\n"
|
|
"metadata_file=$metadata_file\n"
|
|
"promotion_file=$promotion_file\n"
|
|
"world_stage=$world_stage\n"
|
|
"kernel_stage=$kernel_stage\n"
|
|
"world_artifact=$world_artifact\n"
|
|
"kernel_artifact=$kernel_artifact\n"
|
|
"headers_artifact=$headers_artifact\n"
|
|
"bootloader_artifact=$bootloader_artifact\n"
|
|
"latest_link=$latest_link\n"
|
|
"root_df=$root_df\n"
|
|
"build_root_size=$build_root_size\n"
|
|
"result_root_size=$result_root_size\n"
|
|
"world_artifact_size=$world_artifact_size\n"
|
|
"kernel_artifact_size=$kernel_artifact_size\n"
|
|
"headers_artifact_size=$headers_artifact_size\n"
|
|
"bootloader_artifact_size=$bootloader_artifact_size\n"
|
|
"sha_kernel=$sha_kernel\n"
|
|
"sha_loader=$sha_loader\n"
|
|
"sha_param=$sha_param\n"
|
|
"buildworld_tail=$buildworld_tail\n"
|
|
"buildkernel_tail=$buildkernel_tail\n"
|
|
"installworld_tail=$installworld_tail\n"
|
|
"distribution_tail=$distribution_tail\n"
|
|
"installkernel_tail=$installkernel_tail\n"
|
|
"self_hosted_native_build=ok\n"
|
|
"EOF\n"
|
|
"printf 'ok\\n' > \"$status_file\"\n"
|
|
"cat \"$metadata_file\"\n")))
|
|
|
|
|
|
(define* (operating-system-generated-files os #:key guile-store guile-extra-store shepherd-store)
|
|
(append
|
|
`(("boot/loader.conf" . ,(render-loader-conf os))
|
|
("etc/rc.conf" . ,(render-rc.conf os))
|
|
("etc/fstab" . ,(render-fstab os))
|
|
("etc/hosts" . ,(render-hosts os))
|
|
("etc/passwd" . ,(render-passwd os))
|
|
("etc/master.passwd" . ,(render-master-passwd os))
|
|
("etc/group" . ,(render-group os))
|
|
("etc/login.conf" . ,(render-login-conf))
|
|
("etc/shells" . ,(render-shells os))
|
|
("etc/motd" . ,(render-motd os))
|
|
("etc/ttys" . ,(render-ttys))
|
|
("activate" . ,(render-activation-script os
|
|
#:guile-store guile-store
|
|
#:guile-extra-store guile-extra-store
|
|
#:shepherd-store shepherd-store))
|
|
("shepherd/init.scm" . ,(render-shepherd-config os))
|
|
("usr/local/bin/fruix"
|
|
. ,(render-installed-system-fruix os guile-store guile-extra-store shepherd-store)))
|
|
(bundled-fruix-node-files)
|
|
(if (null? (operating-system-development-packages os))
|
|
'()
|
|
`(("usr/local/bin/fruix-development-environment"
|
|
. ,(render-development-environment-script os))))
|
|
(if (null? (operating-system-build-packages os))
|
|
'()
|
|
`(("usr/local/bin/fruix-build-environment"
|
|
. ,(render-build-environment-script os))
|
|
("usr/local/bin/fruix-self-hosted-native-build"
|
|
. ,(render-self-hosted-native-build-script os))))
|
|
(if (pid1-init-mode? os)
|
|
`(("boot/fruix-pid1" . ,(render-pid1-script os shepherd-store guile-store guile-extra-store)))
|
|
'())
|
|
(if (sshd-enabled? os)
|
|
`(("etc/ssh/sshd_config" . ,(render-sshd-config os)))
|
|
'())
|
|
(if (null? (operating-system-root-authorized-keys os))
|
|
'()
|
|
`(("root/.ssh/authorized_keys" . ,(render-root-authorized-keys os))))))
|
|
|