livebook: add systemd user service, test, and docs

Co-authored-by: Yt <happysalada@tuta.io>

authored by Alexandru Scvortov Yt and committed by Yt fa54eeea 0fa36ea3

+181 -1
+2
nixos/doc/manual/release-notes/rl-2311.section.md
··· 72 72 73 73 - [LibreNMS](https://www.librenms.org), a auto-discovering PHP/MySQL/SNMP based network monitoring. Available as [services.librenms](#opt-services.librenms.enable). 74 74 75 + - [Livebook](https://livebook.dev/), an interactive notebook with support for Elixir, graphs, machine learning, and more. 76 + 75 77 - [sitespeed-io](https://sitespeed.io), a tool that can generate metrics (timings, diagnostics) for websites. Available as [services.sitespeed-io](#opt-services.sitespeed-io.enable). 76 78 77 79 - [stalwart-mail](https://stalw.art), an all-in-one email server (SMTP, IMAP, JMAP). Available as [services.stalwart-mail](#opt-services.stalwart-mail.enable).
+1
nixos/modules/module-list.nix
··· 485 485 ./services/development/hoogle.nix 486 486 ./services/development/jupyter/default.nix 487 487 ./services/development/jupyterhub/default.nix 488 + ./services/development/livebook.nix 488 489 ./services/development/lorri.nix 489 490 ./services/development/rstudio-server/default.nix 490 491 ./services/development/zammad.nix
+39
nixos/modules/services/development/livebook.md
··· 1 + # Livebook {#module-services-livebook} 2 + 3 + [Livebook](https://livebook.dev/) is a web application for writing 4 + interactive and collaborative code notebooks. 5 + 6 + ## Basic Usage {#module-services-livebook-basic-usage} 7 + 8 + Enabling the `livebook` service creates a user 9 + [`systemd`](https://www.freedesktop.org/wiki/Software/systemd/) unit 10 + which runs the server. 11 + 12 + ``` 13 + { ... }: 14 + 15 + { 16 + services.livebook = { 17 + enableUserService = true; 18 + port = 20123; 19 + # See note below about security 20 + environmentFile = pkgs.writeText "livebook.env" '' 21 + LIVEBOOK_PASSWORD = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; 22 + ''; 23 + }; 24 + } 25 + ``` 26 + 27 + ::: {.note} 28 + 29 + The Livebook server has the ability to run any command as the user it 30 + is running under, so securing access to it with a password is highly 31 + recommended. 32 + 33 + Putting the password in the Nix configuration like above is an easy 34 + way to get started but it is not recommended in the real world because 35 + the `livebook.env` file will be added to the world-readable Nix store. 36 + A better approach would be to put the password in some secure 37 + user-readable location and set `environmentFile = /home/user/secure/livebook.env`. 38 + 39 + :::
+90
nixos/modules/services/development/livebook.nix
··· 1 + { config, lib, pkgs, ... }: 2 + 3 + with lib; 4 + let 5 + cfg = config.services.livebook; 6 + in 7 + { 8 + options.services.livebook = { 9 + # Since livebook doesn't have a granular permission system (a user 10 + # either has access to all the data or none at all), the decision 11 + # was made to run this as a user service. If that changes in the 12 + # future, this can be changed to a system service. 13 + enableUserService = mkEnableOption "a user service for Livebook"; 14 + 15 + environmentFile = mkOption { 16 + type = types.path; 17 + description = lib.mdDoc '' 18 + Environment file as defined in {manpage}`systemd.exec(5)` passed to the service. 19 + 20 + This must contain at least `LIVEBOOK_PASSWORD` or 21 + `LIVEBOOK_TOKEN_ENABLED=false`. See `livebook server --help` 22 + for other options.''; 23 + }; 24 + 25 + erlang_node_short_name = mkOption { 26 + type = with types; nullOr str; 27 + default = null; 28 + example = "livebook"; 29 + description = "A short name for the distributed node."; 30 + }; 31 + 32 + erlang_node_name = mkOption { 33 + type = with types; nullOr str; 34 + default = null; 35 + example = "livebook@127.0.0.1"; 36 + description = "The name for the app distributed node."; 37 + }; 38 + 39 + port = mkOption { 40 + type = types.port; 41 + default = 8080; 42 + description = "The port to start the web application on."; 43 + }; 44 + 45 + address = mkOption { 46 + type = types.str; 47 + default = "127.0.0.1"; 48 + description = lib.mdDoc '' 49 + The address to start the web application on. Must be a valid IPv4 or 50 + IPv6 address. 51 + ''; 52 + }; 53 + 54 + options = mkOption { 55 + type = with types; attrsOf str; 56 + default = { }; 57 + description = lib.mdDoc '' 58 + Additional options to pass as command-line arguments to the server. 59 + ''; 60 + example = literalExpression '' 61 + { 62 + cookie = "a value shared by all nodes in this cluster"; 63 + } 64 + ''; 65 + }; 66 + }; 67 + 68 + config = mkIf cfg.enableUserService { 69 + systemd.user.services.livebook = { 70 + serviceConfig = { 71 + Restart = "always"; 72 + EnvironmentFile = cfg.environmentFile; 73 + ExecStart = 74 + let 75 + args = lib.cli.toGNUCommandLineShell { } ({ 76 + inherit (cfg) port; 77 + ip = cfg.address; 78 + name = cfg.erlang_node_name; 79 + sname = cfg.erlang_node_short_name; 80 + } // cfg.options); 81 + in 82 + "${pkgs.livebook}/bin/livebook server ${args}"; 83 + }; 84 + path = [ pkgs.bash ]; 85 + wantedBy = [ "default.target" ]; 86 + }; 87 + }; 88 + 89 + meta.doc = ./livebook.md; 90 + }
+1
nixos/tests/all-tests.nix
··· 371 371 honk = runTest ./honk.nix; 372 372 installed-tests = pkgs.recurseIntoAttrs (handleTest ./installed-tests {}); 373 373 invidious = handleTest ./invidious.nix {}; 374 + livebook-service = handleTest ./livebook-service.nix {}; 374 375 oci-containers = handleTestOn ["aarch64-linux" "x86_64-linux"] ./oci-containers.nix {}; 375 376 odoo = handleTest ./odoo.nix {}; 376 377 odoo15 = handleTest ./odoo.nix { package = pkgs.odoo15; };
+43
nixos/tests/livebook-service.nix
··· 1 + import ./make-test-python.nix ({ lib, pkgs, ... }: { 2 + name = "livebook-service"; 3 + 4 + nodes = { 5 + machine = { config, pkgs, ... }: { 6 + imports = [ 7 + ./common/user-account.nix 8 + ]; 9 + 10 + services.livebook = { 11 + enableUserService = true; 12 + port = 20123; 13 + environmentFile = pkgs.writeText "livebook.env" '' 14 + LIVEBOOK_PASSWORD = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; 15 + ''; 16 + options = { 17 + cookie = "chocolate chip"; 18 + }; 19 + }; 20 + }; 21 + }; 22 + 23 + testScript = { nodes, ... }: 24 + let 25 + user = nodes.machine.config.users.users.alice; 26 + sudo = lib.concatStringsSep " " [ 27 + "XDG_RUNTIME_DIR=/run/user/${toString user.uid}" 28 + "sudo" 29 + "--preserve-env=XDG_RUNTIME_DIR" 30 + "-u" 31 + "alice" 32 + ]; 33 + in 34 + '' 35 + machine.wait_for_unit("multi-user.target") 36 + 37 + machine.succeed("loginctl enable-linger alice") 38 + machine.wait_until_succeeds("${sudo} systemctl --user is-active livebook.service") 39 + machine.wait_for_open_port(20123) 40 + 41 + machine.succeed("curl -L localhost:20123 | grep 'Type password'") 42 + ''; 43 + })
+5 -1
pkgs/servers/web-apps/livebook/default.nix
··· 1 - { lib, beamPackages, makeWrapper, rebar3, elixir, erlang, fetchFromGitHub }: 1 + { lib, beamPackages, makeWrapper, rebar3, elixir, erlang, fetchFromGitHub, nixosTests }: 2 2 beamPackages.mixRelease rec { 3 3 pname = "livebook"; 4 4 version = "0.11.3"; ··· 31 31 --prefix PATH : ${lib.makeBinPath [ elixir ]} \ 32 32 --set MIX_REBAR3 ${rebar3}/bin/rebar3 33 33 ''; 34 + 35 + passthru.tests = { 36 + livebook-service = nixosTests.livebook-service; 37 + }; 34 38 35 39 meta = with lib; { 36 40 license = licenses.asl20;