parent
58902f4565
commit
1dcfd1e329
@ -0,0 +1,100 @@ |
||||
{ 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; |
||||
} |
@ -0,0 +1,65 @@ |
||||
<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> |
@ -0,0 +1,93 @@ |
||||
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" |
||||
) |
||||
''; |
||||
}) |
Loading…
Reference in new issue