Compare commits
3 commits
6643c471a0
...
7068046c14
Author | SHA1 | Date | |
---|---|---|---|
7068046c14 | |||
35cb6c2267 | |||
63af6a613d |
56
Cargo.lock
generated
56
Cargo.lock
generated
|
@ -218,9 +218,9 @@ checksum = "fbb36e985947064623dbd357f727af08ffd077f93d696782f3c56365fa2e2799"
|
|||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.73"
|
||||
version = "0.1.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc00ceb34980c03614e35a3a4e218276a0a824e911d07651cd0d858a51e8c0f0"
|
||||
checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -241,9 +241,9 @@ checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80"
|
|||
|
||||
[[package]]
|
||||
name = "aws-config"
|
||||
version = "1.1.9"
|
||||
version = "1.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "297b64446175a73987cedc3c438d79b2a654d0fff96f65ff530fbe039347644c"
|
||||
checksum = "48730d0b4c3d91c43d0d37168831d9fd0e065ad4a889a2ee9faf8d34c3d2804d"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
|
@ -284,9 +284,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "aws-runtime"
|
||||
version = "1.1.8"
|
||||
version = "1.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b13dc54b4b49f8288532334bba8f87386a40571c47c37b1304979b556dc613c8"
|
||||
checksum = "c4ee6903f9d0197510eb6b44c4d86b493011d08b4992938f7b9be0333b6685aa"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-sigv4",
|
||||
|
@ -308,9 +308,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "aws-sdk-s3"
|
||||
version = "1.21.0"
|
||||
version = "1.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc075ffee2a40cb1590bed35d7ec953589a564e768fa91947c565425cd569269"
|
||||
checksum = "644c5939c1b78097d37f3341708978d68490070d4b0f8fa91f0878678c06a7ef"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"aws-credential-types",
|
||||
|
@ -343,9 +343,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "aws-sdk-sso"
|
||||
version = "1.18.0"
|
||||
version = "1.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "019a07902c43b03167ea5df0182f0cb63fae89f9a9682c44d18cf2e4a042cb34"
|
||||
checksum = "b2be5ba83b077b67a6f7a1927eb6b212bf556e33bd74b5eaa5aa6e421910803a"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
|
@ -365,9 +365,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "aws-sdk-ssooidc"
|
||||
version = "1.18.0"
|
||||
version = "1.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04c46ee08a48a7f4eaa4ad201dcc1dd537b49c50859d14d4510e00ad9d3f9af2"
|
||||
checksum = "022ca669825f841aef17b12d4354ef2b8651e4664be49f2d9ea13e4062a80c9f"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
|
@ -387,9 +387,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "aws-sdk-sts"
|
||||
version = "1.18.0"
|
||||
version = "1.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f752ac730125ca6017f72f9db5ec1772c9ecc664f87aa7507a7d81b023c23713"
|
||||
checksum = "8e4a5f5cb007347c1ab34a6d56456301dfada921fc9e57d687ecb08baddd11ff"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-runtime",
|
||||
|
@ -600,9 +600,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "aws-types"
|
||||
version = "1.1.8"
|
||||
version = "1.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0dbf2f3da841a8930f159163175cf6a3d16ddde517c1b0fba7aa776822800f40"
|
||||
checksum = "afb278e322f16f59630a83b6b2dc992a0b48aa74ed47b4130f193fae0053d713"
|
||||
dependencies = [
|
||||
"aws-credential-types",
|
||||
"aws-smithy-async",
|
||||
|
@ -701,9 +701,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.15.4"
|
||||
version = "3.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa"
|
||||
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
|
@ -723,9 +723,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.90"
|
||||
version = "1.0.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5"
|
||||
checksum = "2678b2e3449475e95b0aa6f9b506a28e61b3dc8996592b983695e8ebb58a8b41"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
|
@ -1136,9 +1136,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.13"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a06fddc2749e0528d2813f95e050e87e52c8cbbae56223b9babf73b3e53b0cc6"
|
||||
checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
|
@ -1675,9 +1675,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.35"
|
||||
version = "1.0.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
|
||||
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
@ -2033,9 +2033,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.34"
|
||||
version = "0.3.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749"
|
||||
checksum = "ef89ece63debf11bc32d1ed8d078ac870cbeb44da02afb02a9ff135ae7ca0582"
|
||||
dependencies = [
|
||||
"deranged",
|
||||
"num-conv",
|
||||
|
@ -2053,9 +2053,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
|
|||
|
||||
[[package]]
|
||||
name = "time-macros"
|
||||
version = "0.2.17"
|
||||
version = "0.2.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774"
|
||||
checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf"
|
||||
dependencies = [
|
||||
"num-conv",
|
||||
"time-core",
|
||||
|
|
37
flake.nix
37
flake.nix
|
@ -1,14 +1,29 @@
|
|||
{
|
||||
description = "restic-alarm";
|
||||
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11";
|
||||
outputs = { self, nixpkgs } :
|
||||
let pkgs = import nixpkgs { system = "x86_64-linux"; };
|
||||
in {
|
||||
defaultPackage.x86_64-linux = pkgs.rustPlatform.buildRustPackage rec {
|
||||
pname = "restic-alarm";
|
||||
version = "0.0.1";
|
||||
src = ./.;
|
||||
cargoLock.lockFile = ./Cargo.lock;
|
||||
};
|
||||
};
|
||||
inputs = {
|
||||
cargo2nix.url = "github:cargo2nix/cargo2nix/release-0.11.0";
|
||||
flake-utils.follows = "cargo2nix/flake-utils";
|
||||
nixpkgs.follows = "cargo2nix/nixpkgs";
|
||||
};
|
||||
|
||||
outputs = inputs: with inputs;
|
||||
flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
overlays = [cargo2nix.overlays.default];
|
||||
};
|
||||
|
||||
rustPkgs = pkgs.rustBuilder.makePackageSet {
|
||||
rustVersion = "1.77.2";
|
||||
packageFun = import ./Cargo.nix;
|
||||
};
|
||||
|
||||
in rec {
|
||||
packages = {
|
||||
restic-alarm = (rustPkgs.workspace.restic-alarm {});
|
||||
default = packages.restic-alarm;
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
50
src/main.rs
50
src/main.rs
|
@ -5,6 +5,7 @@ use serde::{Deserialize, Serialize};
|
|||
use std::collections::HashMap;
|
||||
use std::convert::TryFrom;
|
||||
use std::time::{Duration, SystemTime};
|
||||
use std::fmt;
|
||||
|
||||
const RESTIC_ALARM_BUCKET: &str = "restic-alarm-state";
|
||||
const RESTIC_ALARM_STATE_FILE: &str = "state.toml";
|
||||
|
@ -51,6 +52,30 @@ struct RepoConfig {
|
|||
alert_duration: u64,
|
||||
}
|
||||
|
||||
struct AlertStatus {
|
||||
alert: bool,
|
||||
inactivity: Duration,
|
||||
last_alert: Option<Duration>,
|
||||
}
|
||||
|
||||
impl fmt::Display for AlertStatus {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let days = |d: Duration| {
|
||||
d.as_secs() / (3600 * 24)
|
||||
};
|
||||
let hours = |d: Duration| {
|
||||
(d.as_secs() / 3600) % 24
|
||||
};
|
||||
write!(f, "{}: inactivity: {} days, {} hours, last_alert: {}",
|
||||
(if self.alert { "ALERT" } else { "no alert" }),
|
||||
days(self.inactivity), hours(self.inactivity),
|
||||
match self.last_alert {
|
||||
None => "none".to_owned(),
|
||||
Some(d) => format!("{} days, {} hours", days(d), hours(d))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn is_alert_needed(
|
||||
cfg: &RepoConfig,
|
||||
inactive_for: Duration,
|
||||
|
@ -200,17 +225,21 @@ async fn repo_last_snapshot(client: &s3::Client, repo: &str) -> eyre::Result<Opt
|
|||
// (e.g. if it fails to parse).
|
||||
// So the error must not be propagated to the toplevel, which would abort the
|
||||
// alert for remaining repositories; it should instead just be reported/logged.
|
||||
async fn check_repo(client: &s3::Client, state: &mut State, repo: &str) -> eyre::Result<()> {
|
||||
async fn check_repo(client: &s3::Client, state: &mut State, repo: &str) -> eyre::Result<Option<AlertStatus>> {
|
||||
let config = read_repo_config(client, repo).await?;
|
||||
if let Some(last_snapshot) = repo_last_snapshot(client, repo).await? {
|
||||
if is_alert_needed(&config, last_snapshot, state.last_alert(repo)) {
|
||||
if let Some(inactivity) = repo_last_snapshot(client, repo).await? {
|
||||
let last_alert = state.last_alert(repo);
|
||||
let alert = is_alert_needed(&config, inactivity, last_alert);
|
||||
if alert {
|
||||
println!("Sending alert to {} about bucket {}", config.email, repo);
|
||||
send_email(&config.email, repo, last_snapshot).await?;
|
||||
send_email(&config.email, repo, inactivity).await?;
|
||||
}
|
||||
state.update_last_alert(repo);
|
||||
write_state(client, state).await?;
|
||||
Ok(Some(AlertStatus { alert, inactivity, last_alert }))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[::tokio::main]
|
||||
|
@ -224,17 +253,20 @@ async fn main() -> eyre::Result<()> {
|
|||
let client = aws_sdk_s3::Client::from_conf(config);
|
||||
|
||||
let repos = to_watch(&client).await?;
|
||||
println!("Watching repos:");
|
||||
println!("Watching {} repos", repos.len());
|
||||
for r in repos.iter() {
|
||||
println!("{}", r);
|
||||
println!("- {}", r);
|
||||
}
|
||||
let mut state = read_state(&client).await?;
|
||||
println!("Current state: {:?}", &state);
|
||||
|
||||
for repo in repos {
|
||||
match check_repo(&client, &mut state, &repo).await {
|
||||
Ok(()) => {
|
||||
println!("{}: OK", &repo);
|
||||
Ok(None) => {
|
||||
println!("{}: no snapshot, skipping", &repo)
|
||||
},
|
||||
Ok(Some(status)) => {
|
||||
println!("{}: {}", &repo, status);
|
||||
},
|
||||
Err(err) => {
|
||||
// is this the best way to log the error?
|
||||
|
|
Loading…
Reference in a new issue