livebook: add systemd user service, test, and docs

Co-authored-by: Yt <happysalada@tuta.io>
This commit is contained in:
Alexandru Scvortov 2023-10-24 17:53:12 +01:00 committed by Yt
parent 0fa36ea34f
commit fa54eeea5c
7 changed files with 181 additions and 1 deletions

View file

@ -72,6 +72,8 @@
- [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).

View file

@ -485,6 +485,7 @@
./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

View file

@ -0,0 +1,39 @@
# 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`.
:::

View file

@ -0,0 +1,90 @@
{ 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;
}

View file

@ -371,6 +371,7 @@ in {
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; };

View file

@ -0,0 +1,43 @@
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'")
'';
})

View file

@ -1,4 +1,4 @@
{ lib, beamPackages, makeWrapper, rebar3, elixir, erlang, fetchFromGitHub }:
{ lib, beamPackages, makeWrapper, rebar3, elixir, erlang, fetchFromGitHub, nixosTests }:
beamPackages.mixRelease rec {
pname = "livebook";
version = "0.11.3";
@ -32,6 +32,10 @@ beamPackages.mixRelease rec {
--set MIX_REBAR3 ${rebar3}/bin/rebar3
'';
passthru.tests = {
livebook-service = nixosTests.livebook-service;
};
meta = with lib; {
license = licenses.asl20;
homepage = "https://livebook.dev/";