(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) #: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 (render-installed-system-fruix os) (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" "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 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" "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" "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\" < \"$generation_dir/provenance.scm\" < \"$generation_dir/install.scm\" </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" " 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 <&2\n" " exit 1\n" "}\n" "[ -x /usr/local/bin/fruix-development-environment ] || {\n" " echo \"fruix-self-hosted-native-build: development environment helper is missing\" >&2\n" " exit 1\n" "}\n" "PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin\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" "[ -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/development-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/development-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" "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 -j\"$jobs\" -C \"$source_root\" " build-common " buildworld > \"$logdir/buildworld.log\" 2>&1\n" "make -j\"$jobs\" -C \"$source_root\" " build-common " buildkernel > \"$logdir/buildkernel.log\" 2>&1\n" "make -C \"$source_root\" " install-common " DESTDIR=\"$world_stage\" installworld > \"$logdir/installworld.log\" 2>&1\n" "make -C \"$source_root\" " install-common " DESTDIR=\"$world_stage\" distribution > \"$logdir/distribution.log\" 2>&1\n" "make -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\" <\"$metadata_file\" < \"$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))) (if (null? (operating-system-development-packages os)) '() `(("usr/local/bin/fruix-development-environment" . ,(render-development-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))))))