livebook: add systemd user service, test, and docs

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

Changed files
+181 -1
nixos
doc
manual
release-notes
modules
tests
pkgs
servers
web-apps
livebook
+2
nixos/doc/manual/release-notes/rl-2311.section.md
···
- [LibreNMS](https://www.librenms.org), a auto-discovering PHP/MySQL/SNMP based network monitoring. Available as [services.librenms](#opt-services.librenms.enable).
+
- [Livebook](https://livebook.dev/), an interactive notebook with support for Elixir, graphs, machine learning, and more.
+
- [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).
- [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
···
./services/development/hoogle.nix
./services/development/jupyter/default.nix
./services/development/jupyterhub/default.nix
+
./services/development/livebook.nix
./services/development/lorri.nix
./services/development/rstudio-server/default.nix
./services/development/zammad.nix
+39
nixos/modules/services/development/livebook.md
···
+
# Livebook {#module-services-livebook}
+
+
[Livebook](https://livebook.dev/) is a web application for writing
+
interactive and collaborative code notebooks.
+
+
## Basic Usage {#module-services-livebook-basic-usage}
+
+
Enabling the `livebook` service creates a user
+
[`systemd`](https://www.freedesktop.org/wiki/Software/systemd/) unit
+
which runs the server.
+
+
```
+
{ ... }:
+
+
{
+
services.livebook = {
+
enableUserService = true;
+
port = 20123;
+
# See note below about security
+
environmentFile = pkgs.writeText "livebook.env" ''
+
LIVEBOOK_PASSWORD = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
+
'';
+
};
+
}
+
```
+
+
::: {.note}
+
+
The Livebook server has the ability to run any command as the user it
+
is running under, so securing access to it with a password is highly
+
recommended.
+
+
Putting the password in the Nix configuration like above is an easy
+
way to get started but it is not recommended in the real world because
+
the `livebook.env` file will be added to the world-readable Nix store.
+
A better approach would be to put the password in some secure
+
user-readable location and set `environmentFile = /home/user/secure/livebook.env`.
+
+
:::
+90
nixos/modules/services/development/livebook.nix
···
+
{ config, lib, pkgs, ... }:
+
+
with lib;
+
let
+
cfg = config.services.livebook;
+
in
+
{
+
options.services.livebook = {
+
# Since livebook doesn't have a granular permission system (a user
+
# either has access to all the data or none at all), the decision
+
# was made to run this as a user service. If that changes in the
+
# future, this can be changed to a system service.
+
enableUserService = mkEnableOption "a user service for Livebook";
+
+
environmentFile = mkOption {
+
type = types.path;
+
description = lib.mdDoc ''
+
Environment file as defined in {manpage}`systemd.exec(5)` passed to the service.
+
+
This must contain at least `LIVEBOOK_PASSWORD` or
+
`LIVEBOOK_TOKEN_ENABLED=false`. See `livebook server --help`
+
for other options.'';
+
};
+
+
erlang_node_short_name = mkOption {
+
type = with types; nullOr str;
+
default = null;
+
example = "livebook";
+
description = "A short name for the distributed node.";
+
};
+
+
erlang_node_name = mkOption {
+
type = with types; nullOr str;
+
default = null;
+
example = "livebook@127.0.0.1";
+
description = "The name for the app distributed node.";
+
};
+
+
port = mkOption {
+
type = types.port;
+
default = 8080;
+
description = "The port to start the web application on.";
+
};
+
+
address = mkOption {
+
type = types.str;
+
default = "127.0.0.1";
+
description = lib.mdDoc ''
+
The address to start the web application on. Must be a valid IPv4 or
+
IPv6 address.
+
'';
+
};
+
+
options = mkOption {
+
type = with types; attrsOf str;
+
default = { };
+
description = lib.mdDoc ''
+
Additional options to pass as command-line arguments to the server.
+
'';
+
example = literalExpression ''
+
{
+
cookie = "a value shared by all nodes in this cluster";
+
}
+
'';
+
};
+
};
+
+
config = mkIf cfg.enableUserService {
+
systemd.user.services.livebook = {
+
serviceConfig = {
+
Restart = "always";
+
EnvironmentFile = cfg.environmentFile;
+
ExecStart =
+
let
+
args = lib.cli.toGNUCommandLineShell { } ({
+
inherit (cfg) port;
+
ip = cfg.address;
+
name = cfg.erlang_node_name;
+
sname = cfg.erlang_node_short_name;
+
} // cfg.options);
+
in
+
"${pkgs.livebook}/bin/livebook server ${args}";
+
};
+
path = [ pkgs.bash ];
+
wantedBy = [ "default.target" ];
+
};
+
};
+
+
meta.doc = ./livebook.md;
+
}
+1
nixos/tests/all-tests.nix
···
honk = runTest ./honk.nix;
installed-tests = pkgs.recurseIntoAttrs (handleTest ./installed-tests {});
invidious = handleTest ./invidious.nix {};
+
livebook-service = handleTest ./livebook-service.nix {};
oci-containers = handleTestOn ["aarch64-linux" "x86_64-linux"] ./oci-containers.nix {};
odoo = handleTest ./odoo.nix {};
odoo15 = handleTest ./odoo.nix { package = pkgs.odoo15; };
+43
nixos/tests/livebook-service.nix
···
+
import ./make-test-python.nix ({ lib, pkgs, ... }: {
+
name = "livebook-service";
+
+
nodes = {
+
machine = { config, pkgs, ... }: {
+
imports = [
+
./common/user-account.nix
+
];
+
+
services.livebook = {
+
enableUserService = true;
+
port = 20123;
+
environmentFile = pkgs.writeText "livebook.env" ''
+
LIVEBOOK_PASSWORD = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
+
'';
+
options = {
+
cookie = "chocolate chip";
+
};
+
};
+
};
+
};
+
+
testScript = { nodes, ... }:
+
let
+
user = nodes.machine.config.users.users.alice;
+
sudo = lib.concatStringsSep " " [
+
"XDG_RUNTIME_DIR=/run/user/${toString user.uid}"
+
"sudo"
+
"--preserve-env=XDG_RUNTIME_DIR"
+
"-u"
+
"alice"
+
];
+
in
+
''
+
machine.wait_for_unit("multi-user.target")
+
+
machine.succeed("loginctl enable-linger alice")
+
machine.wait_until_succeeds("${sudo} systemctl --user is-active livebook.service")
+
machine.wait_for_open_port(20123)
+
+
machine.succeed("curl -L localhost:20123 | grep 'Type password'")
+
'';
+
})
+5 -1
pkgs/servers/web-apps/livebook/default.nix
···
-
{ lib, beamPackages, makeWrapper, rebar3, elixir, erlang, fetchFromGitHub }:
+
{ lib, beamPackages, makeWrapper, rebar3, elixir, erlang, fetchFromGitHub, nixosTests }:
beamPackages.mixRelease rec {
pname = "livebook";
version = "0.11.3";
···
--prefix PATH : ${lib.makeBinPath [ elixir ]} \
--set MIX_REBAR3 ${rebar3}/bin/rebar3
'';
+
+
passthru.tests = {
+
livebook-service = nixosTests.livebook-service;
+
};
meta = with lib; {
license = licenses.asl20;