litestream: 0.3.14 -> 0.5.10 (#500605)
This commit is contained in:
+77
-48
@@ -1,4 +1,8 @@
|
||||
{ pkgs, ... }:
|
||||
{ pkgs, lib, ... }:
|
||||
let
|
||||
dbPath = "/var/lib/grafana/data/grafana.db";
|
||||
socketPath = "/run/litestream/litestream.sock";
|
||||
in
|
||||
{
|
||||
name = "litestream";
|
||||
meta = with pkgs.lib.maintainers; {
|
||||
@@ -11,38 +15,53 @@
|
||||
services.litestream = {
|
||||
enable = true;
|
||||
settings = {
|
||||
socket = {
|
||||
enabled = true;
|
||||
path = socketPath;
|
||||
};
|
||||
dbs = [
|
||||
{
|
||||
path = "/var/lib/grafana/data/grafana.db";
|
||||
path = dbPath;
|
||||
replicas = [
|
||||
{
|
||||
url = "sftp://foo:bar@127.0.0.1:22/home/foo/grafana";
|
||||
auto-recover = true;
|
||||
}
|
||||
];
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
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 {} \;
|
||||
'';
|
||||
# Litestream 0.5.x writes to the database (_litestream_seq table),
|
||||
# so grafana's data directory must be group-writable.
|
||||
systemd.tmpfiles.settings."10-litestream" = {
|
||||
"/var/lib/grafana".d = {
|
||||
mode = "0750";
|
||||
user = "grafana";
|
||||
group = "grafana";
|
||||
};
|
||||
"/var/lib/grafana/data".d = {
|
||||
mode = "2770";
|
||||
user = "grafana";
|
||||
group = "grafana";
|
||||
};
|
||||
};
|
||||
systemd.services.grafana.serviceConfig = {
|
||||
ExecStartPre = lib.mkAfter "+${pkgs.sqlite}/bin/sqlite3 ${dbPath} 'PRAGMA journal_mode=WAL;'";
|
||||
UMask = lib.mkForce "0007";
|
||||
};
|
||||
systemd.services.litestream = {
|
||||
after = [
|
||||
"grafana.service"
|
||||
"sshd.service"
|
||||
];
|
||||
requires = [ "grafana.service" ];
|
||||
wants = [ "sshd.service" ];
|
||||
serviceConfig = {
|
||||
RuntimeDirectory = "litestream";
|
||||
ExecStartPre = "+/bin/sh -c 'chmod g+rw ${dbPath}*'";
|
||||
};
|
||||
};
|
||||
services.openssh = {
|
||||
enable = true;
|
||||
allowSFTP = true;
|
||||
@@ -59,6 +78,7 @@
|
||||
security = {
|
||||
admin_user = "admin";
|
||||
admin_password = "admin";
|
||||
secret_key = "SW2YcwTIb9zpOOhoPsMm";
|
||||
};
|
||||
|
||||
server = {
|
||||
@@ -68,7 +88,7 @@
|
||||
|
||||
database = {
|
||||
type = "sqlite3";
|
||||
path = "/var/lib/grafana/data/grafana.db";
|
||||
path = dbPath;
|
||||
wal = true;
|
||||
};
|
||||
};
|
||||
@@ -78,35 +98,44 @@
|
||||
password = "bar";
|
||||
};
|
||||
users.users.litestream.extraGroups = [ "grafana" ];
|
||||
environment.systemPackages = [ pkgs.sqlite ];
|
||||
};
|
||||
|
||||
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"
|
||||
)
|
||||
with subtest("Verify litestream replicates changes"):
|
||||
machine.succeed("""
|
||||
curl -sSfN -X PUT --json '{
|
||||
"name": "LitestreamTest",
|
||||
"login": "admin",
|
||||
"email": "admin@localhost"
|
||||
}' http://admin:admin@127.0.0.1:3000/api/user
|
||||
""")
|
||||
machine.succeed("litestream sync -wait -socket ${socketPath} ${dbPath}")
|
||||
machine.succeed(
|
||||
"litestream restore -o /tmp/restored.db ${dbPath} && "
|
||||
"sqlite3 /tmp/restored.db '.dump' | grep -q LitestreamTest"
|
||||
)
|
||||
|
||||
with subtest("Simulate disaster recovery"):
|
||||
# https://litestream.io/guides/systemd/#simulating-a-disaster
|
||||
machine.systemctl("stop litestream.service grafana.service")
|
||||
machine.succeed(
|
||||
"rm -f ${dbPath} "
|
||||
"${dbPath}-shm "
|
||||
"${dbPath}-wal"
|
||||
)
|
||||
machine.succeed(
|
||||
"litestream restore ${dbPath} "
|
||||
"&& chown grafana:grafana ${dbPath} "
|
||||
"&& chmod 660 ${dbPath}"
|
||||
)
|
||||
machine.systemctl("restart grafana.service litestream.service")
|
||||
machine.wait_for_open_port(3000)
|
||||
machine.wait_for_unit("litestream.service")
|
||||
machine.succeed(
|
||||
"curl -sSfN -u admin:admin http://127.0.0.1:3000/api/user | grep LitestreamTest"
|
||||
)
|
||||
'';
|
||||
}
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
diff --git a/cmd/litestream/main.go b/cmd/litestream/main.go
|
||||
index 1234567..abcdefg 100644
|
||||
--- a/cmd/litestream/main.go
|
||||
+++ b/cmd/litestream/main.go
|
||||
@@ -362,6 +362,7 @@ type ReplicaConfig struct {
|
||||
Host string `yaml:"host"`
|
||||
User string `yaml:"user"`
|
||||
Password string `yaml:"password"`
|
||||
KeyPath string `yaml:"key-path"`
|
||||
+ HostKeyPath string `yaml:"host-key-path"`
|
||||
|
||||
// Encryption identities and recipients
|
||||
@@ -664,6 +665,7 @@ func NewReplicaFromConfig(c *ReplicaConfig, dbc *DBConfig) (_ litestream.Replic
|
||||
client.Password = password
|
||||
client.Path = path
|
||||
client.KeyPath = c.KeyPath
|
||||
+ client.HostKeyPath = c.HostKeyPath
|
||||
return client, nil
|
||||
}
|
||||
|
||||
diff --git a/sftp/replica_client.go b/sftp/replica_client.go
|
||||
index 30d8fa87..8b651e97 100644
|
||||
--- a/sftp/replica_client.go
|
||||
+++ b/sftp/replica_client.go
|
||||
@@ -41,6 +41,7 @@ type ReplicaClient struct {
|
||||
Password string
|
||||
Path string
|
||||
KeyPath string
|
||||
+ HostKeyPath string
|
||||
DialTimeout time.Duration
|
||||
}
|
||||
|
||||
@@ -71,14 +72,28 @@ func (c *ReplicaClient) Init(ctx context.Context) (_ *sftp.Client, err error) {
|
||||
|
||||
// Build SSH configuration & auth methods
|
||||
config := &ssh.ClientConfig{
|
||||
- User: c.User,
|
||||
- HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
- BannerCallback: ssh.BannerDisplayStderr(),
|
||||
+ User: c.User,
|
||||
+ BannerCallback: ssh.BannerDisplayStderr(),
|
||||
}
|
||||
if c.Password != "" {
|
||||
config.Auth = append(config.Auth, ssh.Password(c.Password))
|
||||
}
|
||||
|
||||
+ if c.HostKeyPath == "" {
|
||||
+ config.HostKeyCallback = ssh.InsecureIgnoreHostKey()
|
||||
+ } else {
|
||||
+ buf, err := os.ReadFile(c.HostKeyPath)
|
||||
+ if err != nil {
|
||||
+ return nil, fmt.Errorf("cannot read sftp host key path: %w", err)
|
||||
+ }
|
||||
+
|
||||
+ key, _, _, _, err := ssh.ParseAuthorizedKey(buf)
|
||||
+ if err != nil {
|
||||
+ return nil, fmt.Errorf("cannot parse sftp host key path: path=%s len=%d err=%w", c.HostKeyPath, len(buf), err)
|
||||
+ }
|
||||
+ config.HostKeyCallback = ssh.FixedHostKey(key)
|
||||
+ }
|
||||
+
|
||||
if c.KeyPath != "" {
|
||||
buf, err := os.ReadFile(c.KeyPath)
|
||||
if err != nil {
|
||||
@@ -6,13 +6,13 @@
|
||||
}:
|
||||
buildGoModule (finalAttrs: {
|
||||
pname = "litestream";
|
||||
version = "0.3.14";
|
||||
version = "0.5.10";
|
||||
|
||||
src = fetchFromGitHub {
|
||||
owner = "benbjohnson";
|
||||
repo = "litestream";
|
||||
rev = "v${finalAttrs.version}";
|
||||
sha256 = "sha256-rTPw1BnZPtcWzhwR9mCzoE4LXdlaFfRCIDMHlN/SRzk=";
|
||||
hash = "sha256-xp1Ic7sF3yzpR4FgMOfx/uRp/jv/qzTgSlItOIrl2pI=";
|
||||
};
|
||||
|
||||
ldflags = [
|
||||
@@ -21,9 +21,7 @@ buildGoModule (finalAttrs: {
|
||||
"-X main.Version=${finalAttrs.version}"
|
||||
];
|
||||
|
||||
vendorHash = "sha256-sYIY3Z3VrCqbjEbQtEY7q6Jljg8jMoa2qWEB/IkDjzM=";
|
||||
|
||||
patches = [ ./fix-cve-2024-41254.patch ];
|
||||
vendorHash = "sha256-e2fsgK/fZNIos5W/Gc3u72uzoT2igs6BgzYtz1PyI10=";
|
||||
|
||||
passthru.tests = { inherit (nixosTests) litestream; };
|
||||
|
||||
|
||||
Reference in New Issue
Block a user