Unverified Commit 404cd360 authored by Aaron Andersen's avatar Aaron Andersen Committed by GitHub
Browse files

Merge pull request #129468 from jwygoda/litestream-service

nixos/litestream: init
parents 099015b2 1dcfd1e3
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -634,6 +634,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
@@ -227,6 +227,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"
    )
  '';
})