Commit 1dcfd1e3 authored by Jarosław Wygoda's avatar Jarosław Wygoda
Browse files

nixos/litestream: init

parent 58902f45
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -631,6 +631,7 @@
  ./services/network-filesystems/glusterfs.nix
  ./services/network-filesystems/kbfs.nix
  ./services/network-filesystems/ipfs.nix
  ./services/network-filesystems/litestream/default.nix
  ./services/network-filesystems/netatalk.nix
  ./services/network-filesystems/nfsd.nix
  ./services/network-filesystems/openafs/client.nix
+100 −0
Original line number Diff line number Diff line
{ config, lib, pkgs, ... }:

with lib;

let
  cfg = config.services.litestream;
  settingsFormat = pkgs.formats.yaml {};
in
{
  options.services.litestream = {
    enable = mkEnableOption "litestream";

    package = mkOption {
      description = "Package to use.";
      default = pkgs.litestream;
      defaultText = "pkgs.litestream";
      type = types.package;
    };

    settings = mkOption {
      description = ''
        See the <link xlink:href="https://litestream.io/reference/config/">documentation</link>.
      '';
      type = settingsFormat.type;
      example = {
        dbs = [
          {
            path = "/var/lib/db1";
            replicas = [
              {
                url = "s3://mybkt.litestream.io/db1";
              }
            ];
          }
        ];
      };
    };

    environmentFile = mkOption {
      type = types.nullOr types.path;
      default = null;
      example = "/run/secrets/litestream";
      description = ''
        Environment file as defined in <citerefentry>
        <refentrytitle>systemd.exec</refentrytitle><manvolnum>5</manvolnum>
        </citerefentry>.

        Secrets may be passed to the service without adding them to the
        world-readable Nix store, by specifying placeholder variables as
        the option value in Nix and setting these variables accordingly in the
        environment file.

        By default, Litestream will perform environment variable expansion
        within the config file before reading it. Any references to ''$VAR or
        ''${VAR} formatted variables will be replaced with their environment
        variable values. If no value is set then it will be replaced with an
        empty string.

        <programlisting>
          # Content of the environment file
          LITESTREAM_ACCESS_KEY_ID=AKIAxxxxxxxxxxxxxxxx
          LITESTREAM_SECRET_ACCESS_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/xxxxxxxxx
        </programlisting>

        Note that this file needs to be available on the host on which
        this exporter is running.
      '';
    };
  };

  config = mkIf cfg.enable {
    environment.systemPackages = [ cfg.package ];
    environment.etc = {
      "litestream.yml" = {
        source = settingsFormat.generate "litestream-config.yaml" cfg.settings;
      };
    };

    systemd.services.litestream = {
      description = "Litestream";
      wantedBy = [ "multi-user.target" ];
      after = [ "networking.target" ];
      serviceConfig = {
        EnvironmentFile = mkIf (cfg.environmentFile != null) cfg.environmentFile;
        ExecStart = "${cfg.package}/bin/litestream replicate";
        Restart = "always";
        User = "litestream";
        Group = "litestream";
      };
    };

    users.users.litestream = {
      description = "Litestream user";
      group = "litestream";
      isSystemUser = true;
    };
    users.groups.litestream = {};
  };
  meta.doc = ./litestream.xml;
}
+65 −0
Original line number Diff line number Diff line
<chapter xmlns="http://docbook.org/ns/docbook"
         xmlns:xlink="http://www.w3.org/1999/xlink"
         xmlns:xi="http://www.w3.org/2001/XInclude"
         version="5.0"
         xml:id="module-services-litestream">
 <title>Litestream</title>
 <para>
  <link xlink:href="https://litestream.io/">Litestream</link> is a standalone streaming
  replication tool for SQLite.
 </para>

 <section xml:id="module-services-litestream-configuration">
  <title>Configuration</title>

  <para>
   Litestream service is managed by a dedicated user named <literal>litestream</literal>
   which needs permission to the database file. Here's an example config which gives
   required permissions to access <link linkend="opt-services.grafana.database.path">
   grafana database</link>:
<programlisting>
{ pkgs, ... }:
{
  users.users.litestream.extraGroups = [ "grafana" ];

  systemd.services.grafana.serviceConfig.ExecStartPost = "+" + pkgs.writeShellScript "grant-grafana-permissions" ''
    timeout=10

    while [ ! -f /var/lib/grafana/data/grafana.db ];
    do
      if [ "$timeout" == 0 ]; then
        echo "ERROR: Timeout while waiting for /var/lib/grafana/data/grafana.db."
        exit 1
      fi

      sleep 1

      ((timeout--))
    done

    find /var/lib/grafana -type d -exec chmod -v 775 {} \;
    find /var/lib/grafana -type f -exec chmod -v 660 {} \;
  '';

  services.litestream = {
    enable = true;

    environmentFile = "/run/secrets/litestream";

    settings = {
      dbs = [
        {
          path = "/var/lib/grafana/data/grafana.db";
          replicas = [{
            url = "s3://mybkt.litestream.io/grafana";
          }];
        }
      ];
    };
  };
}
</programlisting>
  </para>
 </section>

</chapter>
+1 −0
Original line number Diff line number Diff line
@@ -224,6 +224,7 @@ in
  libreswan = handleTest ./libreswan.nix {};
  lightdm = handleTest ./lightdm.nix {};
  limesurvey = handleTest ./limesurvey.nix {};
  litestream = handleTest ./litestream.nix {};
  locate = handleTest ./locate.nix {};
  login = handleTest ./login.nix {};
  loki = handleTest ./loki.nix {};
+93 −0
Original line number Diff line number Diff line
import ./make-test-python.nix ({ pkgs, ...} : {
  name = "litestream";
  meta = with pkgs.lib.maintainers; {
    maintainers = [ jwygoda ];
  };

  machine =
    { pkgs, ... }:
    { services.litestream = {
        enable = true;
        settings = {
          dbs = [
            {
              path = "/var/lib/grafana/data/grafana.db";
              replicas = [{
                url = "sftp://foo:bar@127.0.0.1:22/home/foo/grafana";
              }];
            }
          ];
        };
      };
      systemd.services.grafana.serviceConfig.ExecStartPost = "+" + pkgs.writeShellScript "grant-grafana-permissions" ''
        timeout=10

        while [ ! -f /var/lib/grafana/data/grafana.db ];
        do
          if [ "$timeout" == 0 ]; then
            echo "ERROR: Timeout while waiting for /var/lib/grafana/data/grafana.db."
            exit 1
          fi

          sleep 1

          ((timeout--))
        done

        find /var/lib/grafana -type d -exec chmod -v 775 {} \;
        find /var/lib/grafana -type f -exec chmod -v 660 {} \;
      '';
      services.openssh = {
        enable = true;
        allowSFTP = true;
        listenAddresses = [ { addr = "127.0.0.1"; port = 22; } ];
      };
      services.grafana = {
        enable = true;
        security = {
          adminUser = "admin";
          adminPassword = "admin";
        };
        addr = "localhost";
        port = 3000;
        extraOptions = {
          DATABASE_URL = "sqlite3:///var/lib/grafana/data/grafana.db?cache=private&mode=rwc&_journal_mode=WAL";
        };
      };
      users.users.foo = {
        isNormalUser = true;
        password = "bar";
      };
      users.users.litestream.extraGroups = [ "grafana" ];
    };

  testScript = ''
    start_all()
    machine.wait_until_succeeds("test -d /home/foo/grafana")
    machine.wait_for_open_port(3000)
    machine.succeed("""
        curl -sSfN -X PUT -H "Content-Type: application/json" -d '{
          "oldPassword": "admin",
          "newPassword": "newpass",
          "confirmNew": "newpass"
        }' http://admin:admin@127.0.0.1:3000/api/user/password
    """)
    # https://litestream.io/guides/systemd/#simulating-a-disaster
    machine.systemctl("stop litestream.service")
    machine.succeed(
        "rm -f /var/lib/grafana/data/grafana.db "
        "/var/lib/grafana/data/grafana.db-shm "
        "/var/lib/grafana/data/grafana.db-wal"
    )
    machine.succeed(
        "litestream restore /var/lib/grafana/data/grafana.db "
        "&& chown grafana:grafana /var/lib/grafana/data/grafana.db "
        "&& chmod 660 /var/lib/grafana/data/grafana.db"
    )
    machine.systemctl("restart grafana.service")
    machine.wait_for_open_port(3000)
    machine.succeed(
        "curl -sSfN -u admin:newpass http://127.0.0.1:3000/api/org/users | grep admin\@localhost"
    )
  '';
})