feat(homelab): add minecraft management interface
This commit is contained in:
130
nix/homelab/Cargo.lock
generated
130
nix/homelab/Cargo.lock
generated
@@ -30,6 +30,15 @@ version = "0.2.21"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "android_system_properties"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "anstream"
|
name = "anstream"
|
||||||
version = "0.6.21"
|
version = "0.6.21"
|
||||||
@@ -289,6 +298,19 @@ version = "0.2.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chrono"
|
||||||
|
version = "0.4.43"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fac4744fb15ae8337dc853fee7fb3f4e48c0fbaa23d0afe49c447b4fab126118"
|
||||||
|
dependencies = [
|
||||||
|
"iana-time-zone",
|
||||||
|
"js-sys",
|
||||||
|
"num-traits",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "clap"
|
name = "clap"
|
||||||
version = "4.5.54"
|
version = "4.5.54"
|
||||||
@@ -664,6 +686,7 @@ checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"futures-executor",
|
||||||
"futures-io",
|
"futures-io",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
@@ -686,6 +709,17 @@ version = "0.3.31"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-executor"
|
||||||
|
version = "0.3.31"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-task",
|
||||||
|
"futures-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-io"
|
name = "futures-io"
|
||||||
version = "0.3.31"
|
version = "0.3.31"
|
||||||
@@ -840,11 +874,17 @@ name = "homelab-cli"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"askama",
|
||||||
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"dialoguer",
|
"dialoguer",
|
||||||
|
"futures",
|
||||||
|
"indicatif",
|
||||||
"k8s-openapi",
|
"k8s-openapi",
|
||||||
"kube",
|
"kube",
|
||||||
"schemars",
|
"schemars",
|
||||||
|
"serde_json",
|
||||||
|
"serde_yaml",
|
||||||
"thiserror 2.0.18",
|
"thiserror 2.0.18",
|
||||||
"thiserror-ext",
|
"thiserror-ext",
|
||||||
"tokio",
|
"tokio",
|
||||||
@@ -979,6 +1019,30 @@ dependencies = [
|
|||||||
"windows-registry",
|
"windows-registry",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone"
|
||||||
|
version = "0.1.64"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "33e57f83510bb73707521ebaffa789ec8caf86f9657cad665b092b581d40e9fb"
|
||||||
|
dependencies = [
|
||||||
|
"android_system_properties",
|
||||||
|
"core-foundation-sys",
|
||||||
|
"iana-time-zone-haiku",
|
||||||
|
"js-sys",
|
||||||
|
"log",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"windows-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone-haiku"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "icu_collections"
|
name = "icu_collections"
|
||||||
version = "2.1.1"
|
version = "2.1.1"
|
||||||
@@ -1097,6 +1161,19 @@ dependencies = [
|
|||||||
"hashbrown",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "indicatif"
|
||||||
|
version = "0.18.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9375e112e4b463ec1b1c6c011953545c65a30164fbab5b581df32b3abf0dcb88"
|
||||||
|
dependencies = [
|
||||||
|
"console",
|
||||||
|
"portable-atomic",
|
||||||
|
"unicode-width",
|
||||||
|
"unit-prefix",
|
||||||
|
"web-time",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ipnet"
|
name = "ipnet"
|
||||||
version = "2.11.0"
|
version = "2.11.0"
|
||||||
@@ -2129,15 +2206,15 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.145"
|
version = "1.0.149"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
|
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"memchr",
|
"memchr",
|
||||||
"ryu",
|
|
||||||
"serde",
|
"serde",
|
||||||
"serde_core",
|
"serde_core",
|
||||||
|
"zmij",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -2601,6 +2678,12 @@ version = "0.2.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
|
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unit-prefix"
|
||||||
|
version = "0.5.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "81e544489bf3d8ef66c953931f56617f423cd4b5494be343d9b9d3dda037b9a3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unsafe-libyaml"
|
name = "unsafe-libyaml"
|
||||||
version = "0.2.11"
|
version = "0.2.11"
|
||||||
@@ -2785,6 +2868,41 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-core"
|
||||||
|
version = "0.62.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
|
||||||
|
dependencies = [
|
||||||
|
"windows-implement",
|
||||||
|
"windows-interface",
|
||||||
|
"windows-link",
|
||||||
|
"windows-result",
|
||||||
|
"windows-strings",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-implement"
|
||||||
|
version = "0.60.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-interface"
|
||||||
|
version = "0.59.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-link"
|
name = "windows-link"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
@@ -3165,3 +3283,9 @@ dependencies = [
|
|||||||
"quote",
|
"quote",
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zmij"
|
||||||
|
version = "1.0.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dfcd145825aace48cff44a8844de64bf75feec3080e0aa5cdbde72961ae51a65"
|
||||||
|
|||||||
@@ -5,11 +5,17 @@ edition = "2024"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.100"
|
anyhow = "1.0.100"
|
||||||
|
askama = "0.15.1"
|
||||||
|
chrono = "0.4.43"
|
||||||
clap = { version = "4.5.54", features = ["derive"] }
|
clap = { version = "4.5.54", features = ["derive"] }
|
||||||
dialoguer = "0.12.0"
|
dialoguer = "0.12.0"
|
||||||
|
futures = "0.3.31"
|
||||||
|
indicatif = "0.18.3"
|
||||||
k8s-openapi = { version = "0.27.0", features = ["latest", "schemars", "v1_35"] }
|
k8s-openapi = { version = "0.27.0", features = ["latest", "schemars", "v1_35"] }
|
||||||
kube = { version = "3.0.0", features = ["runtime", "derive"] }
|
kube = { version = "3.0.0", features = ["runtime", "derive"] }
|
||||||
schemars = "1.2.0"
|
schemars = "1.2.0"
|
||||||
|
serde_json = "1.0.149"
|
||||||
|
serde_yaml = "0.9.34"
|
||||||
thiserror = "2.0.18"
|
thiserror = "2.0.18"
|
||||||
thiserror-ext = "0.3.0"
|
thiserror-ext = "0.3.0"
|
||||||
tokio = { version = "1.49.0", features = ["macros", "rt"] }
|
tokio = { version = "1.49.0", features = ["macros", "rt"] }
|
||||||
|
|||||||
3
nix/homelab/cli/askama.toml
Normal file
3
nix/homelab/cli/askama.toml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
[[escaper]]
|
||||||
|
path = "askama::filters::Text"
|
||||||
|
extensions = ["yaml"]
|
||||||
@@ -1,7 +1,18 @@
|
|||||||
|
use askama::Template;
|
||||||
use clap::{Args, Subcommand};
|
use clap::{Args, Subcommand};
|
||||||
use kube::Api;
|
use futures::{AsyncBufReadExt, StreamExt, TryStreamExt};
|
||||||
|
use k8s_openapi::api::apps::v1::Deployment;
|
||||||
|
use k8s_openapi::api::batch::v1::Job;
|
||||||
|
use k8s_openapi::api::core::v1::Pod;
|
||||||
|
use kube::api::{Api, LogParams, Patch, PatchParams, PostParams};
|
||||||
|
use kube::runtime::{WatchStreamExt, watcher};
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
use crate::{AppState, State};
|
use crate::State;
|
||||||
|
use crate::error::{ErrorKind, Result};
|
||||||
|
use crate::reporter::Reporter;
|
||||||
|
|
||||||
|
const NAMESPACE: &str = "minecraft";
|
||||||
|
|
||||||
#[derive(Debug, Args)]
|
#[derive(Debug, Args)]
|
||||||
pub struct MinecraftCommand {
|
pub struct MinecraftCommand {
|
||||||
@@ -11,6 +22,7 @@ pub struct MinecraftCommand {
|
|||||||
|
|
||||||
#[derive(Debug, Subcommand)]
|
#[derive(Debug, Subcommand)]
|
||||||
enum MinecraftSubcommand {
|
enum MinecraftSubcommand {
|
||||||
|
/// Backup a minecraft world
|
||||||
Backup {
|
Backup {
|
||||||
/// the world to backup
|
/// the world to backup
|
||||||
#[arg(short, long)]
|
#[arg(short, long)]
|
||||||
@@ -18,12 +30,115 @@ enum MinecraftSubcommand {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Template)]
|
||||||
|
#[template(path = "backup-job.yaml")]
|
||||||
|
struct BackupJobTemplate<'a> {
|
||||||
|
world: &'a str,
|
||||||
|
}
|
||||||
|
|
||||||
impl MinecraftCommand {
|
impl MinecraftCommand {
|
||||||
pub async fn run(&self, app_state: State) {
|
pub async fn run(&self, state: State) -> Result<()> {
|
||||||
match &self.command {
|
match &self.command {
|
||||||
MinecraftSubcommand::Backup { world } => backup_world(app_state, &world),
|
MinecraftSubcommand::Backup { world } => backup_world(state, world).await,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn backup_world(app_state: State, world: &str) {}
|
pub async fn backup_world(state: State, world: &str) -> Result<()> {
|
||||||
|
let reporter = Reporter::new();
|
||||||
|
let job_name = format!("minecraft-{}-backup", world);
|
||||||
|
|
||||||
|
reporter.status(format!("Scaling deployment minecraft-{world}"));
|
||||||
|
scale_deployment(&state.client, NAMESPACE, &format!("minecraft-{world}"), 0).await?;
|
||||||
|
|
||||||
|
reporter.status("Creating backup job...");
|
||||||
|
|
||||||
|
let job = build_backup_job(world)?;
|
||||||
|
let jobs: Api<Job> = Api::namespaced(state.client.clone(), NAMESPACE);
|
||||||
|
jobs.create(&PostParams::default(), &job).await?;
|
||||||
|
|
||||||
|
reporter.status("Waiting for pod to start...");
|
||||||
|
|
||||||
|
let pods: Api<Pod> = Api::namespaced(state.client.clone(), NAMESPACE);
|
||||||
|
let pod_name = wait_for_job_pod(&pods, &job_name).await?;
|
||||||
|
|
||||||
|
reporter.status("Running backup...");
|
||||||
|
|
||||||
|
stream_pod_logs(&pods, &pod_name, &reporter).await?;
|
||||||
|
|
||||||
|
let job = jobs.get(&job_name).await?;
|
||||||
|
let status = job.status.as_ref();
|
||||||
|
let succeeded = status.and_then(|s| s.succeeded).unwrap_or(0);
|
||||||
|
let failed = status.and_then(|s| s.failed).unwrap_or(0);
|
||||||
|
|
||||||
|
reporter.status(format!("Scaling deployment minecraft-{world}, replicas: 1"));
|
||||||
|
scale_deployment(&state.client, NAMESPACE, &format!("minecraft-{world}"), 1).await?;
|
||||||
|
if succeeded > 0 {
|
||||||
|
reporter.success("Backup complete");
|
||||||
|
Ok(())
|
||||||
|
} else if failed > 0 {
|
||||||
|
reporter.fail("Backup job failed");
|
||||||
|
Err(ErrorKind::BackupFailed("Job failed".to_string()).into())
|
||||||
|
} else {
|
||||||
|
reporter.fail("Backup job status unknown");
|
||||||
|
Err(ErrorKind::BackupFailed("Unknown status".to_string()).into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn scale_deployment(
|
||||||
|
client: &kube::Client,
|
||||||
|
namespace: &str,
|
||||||
|
name: &str,
|
||||||
|
replicas: i32,
|
||||||
|
) -> Result<()> {
|
||||||
|
let deployments: Api<Deployment> = Api::namespaced(client.clone(), namespace);
|
||||||
|
let patch = json!({ "spec": { "replicas": replicas } });
|
||||||
|
deployments
|
||||||
|
.patch(name, &PatchParams::default(), &Patch::Merge(&patch))
|
||||||
|
.await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn wait_for_job_pod(pods: &Api<Pod>, job_name: &str) -> Result<String> {
|
||||||
|
let label_selector = format!("job-name={}", job_name);
|
||||||
|
let config = watcher::Config::default().labels(&label_selector);
|
||||||
|
|
||||||
|
let mut stream = watcher(pods.clone(), config).applied_objects().boxed();
|
||||||
|
|
||||||
|
while let Some(pod) = stream.try_next().await? {
|
||||||
|
let name = pod.metadata.name.as_deref().unwrap_or_default();
|
||||||
|
let phase = pod
|
||||||
|
.status
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|s| s.phase.as_deref())
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
if phase == "Running" || phase == "Succeeded" || phase == "Failed" {
|
||||||
|
return Ok(name.to_string());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Err(ErrorKind::BackupFailed("Pod never started".to_string()).into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_backup_job(world: &str) -> Result<Job> {
|
||||||
|
let template = BackupJobTemplate { world };
|
||||||
|
let yaml = template.render()?;
|
||||||
|
Ok(serde_yaml::from_str(&yaml)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn stream_pod_logs(pods: &Api<Pod>, pod_name: &str, reporter: &Reporter) -> Result<()> {
|
||||||
|
let params = LogParams {
|
||||||
|
follow: true,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let stream = pods.log_stream(pod_name, ¶ms).await?;
|
||||||
|
let mut lines = stream.lines();
|
||||||
|
|
||||||
|
while let Some(line) = lines.try_next().await? {
|
||||||
|
reporter.log(&line);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,16 @@ use thiserror::Error;
|
|||||||
pub enum ErrorKind {
|
pub enum ErrorKind {
|
||||||
#[error("kube error: {0}")]
|
#[error("kube error: {0}")]
|
||||||
Kube(#[from] kube::Error),
|
Kube(#[from] kube::Error),
|
||||||
|
#[error("watcher error: {0}")]
|
||||||
|
Watcher(#[from] kube::runtime::watcher::Error),
|
||||||
|
#[error("io error: {0}")]
|
||||||
|
Io(#[from] std::io::Error),
|
||||||
|
#[error("backup failed: {0}")]
|
||||||
|
BackupFailed(String),
|
||||||
|
#[error("template error: {0}")]
|
||||||
|
Template(#[from] askama::Error),
|
||||||
|
#[error("error deserializing yaml: {0}")]
|
||||||
|
Yaml(#[from] serde_yaml::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = core::result::Result<T, Error>;
|
pub type Result<T> = core::result::Result<T, Error>;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
mod commands;
|
mod commands;
|
||||||
mod error;
|
mod error;
|
||||||
|
mod reporter;
|
||||||
|
|
||||||
use std::{ops::Deref, sync::Arc};
|
use std::{ops::Deref, sync::Arc};
|
||||||
|
|
||||||
@@ -48,7 +49,7 @@ async fn main() -> anyhow::Result<()> {
|
|||||||
let app_state = State::new(AppState::new().await?);
|
let app_state = State::new(AppState::new().await?);
|
||||||
|
|
||||||
match cli.command {
|
match cli.command {
|
||||||
Some(Commands::Minecraft(cmd)) => cmd.run(app_state.clone()).await,
|
Some(Commands::Minecraft(cmd)) => cmd.run(app_state.clone()).await?,
|
||||||
_ => Cli::command().print_long_help()?,
|
_ => Cli::command().print_long_help()?,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
42
nix/homelab/cli/src/reporter.rs
Normal file
42
nix/homelab/cli/src/reporter.rs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
use indicatif::{ProgressBar, ProgressStyle};
|
||||||
|
|
||||||
|
pub struct Reporter {
|
||||||
|
spinner: ProgressBar,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Reporter {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let spinner = ProgressBar::new_spinner();
|
||||||
|
spinner.set_style(
|
||||||
|
ProgressStyle::default_spinner()
|
||||||
|
.template("{spinner:.cyan} {msg}")
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
spinner.enable_steady_tick(std::time::Duration::from_millis(100));
|
||||||
|
Self { spinner }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn status(&self, msg: impl Into<String>) {
|
||||||
|
self.spinner.set_message(msg.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn log(&self, line: &str) {
|
||||||
|
self.spinner.suspend(|| {
|
||||||
|
println!(" │ {}", line);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn success(&self, msg: &str) {
|
||||||
|
self.spinner.finish_with_message(format!("✓ {}", msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fail(&self, msg: &str) {
|
||||||
|
self.spinner.finish_with_message(format!("✗ {}", msg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Reporter {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
40
nix/homelab/cli/templates/backup-job.yaml
Normal file
40
nix/homelab/cli/templates/backup-job.yaml
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
apiVersion: batch/v1
|
||||||
|
kind: Job
|
||||||
|
metadata:
|
||||||
|
name: minecraft-{{ world }}-backup
|
||||||
|
namespace: minecraft
|
||||||
|
labels:
|
||||||
|
app: minecraft-backup
|
||||||
|
world: {{ world }}
|
||||||
|
spec:
|
||||||
|
ttlSecondsAfterFinished: 300
|
||||||
|
backoffLimit: 0
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
job-name: minecraft-{{ world }}-backup
|
||||||
|
spec:
|
||||||
|
restartPolicy: Never
|
||||||
|
containers:
|
||||||
|
- name: backup
|
||||||
|
image: busybox
|
||||||
|
command:
|
||||||
|
- "sh"
|
||||||
|
- "-c"
|
||||||
|
- |
|
||||||
|
tar -czvf /backups/minecraft-{{ world }}-manual.tar.gz -C /data .
|
||||||
|
volumeMounts:
|
||||||
|
- name: data
|
||||||
|
mountPath: /data
|
||||||
|
readOnly: true
|
||||||
|
- name: backups
|
||||||
|
mountPath: /backups
|
||||||
|
volumes:
|
||||||
|
- name: data
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: minecraft-{{ world }}-datadir
|
||||||
|
readOnly: true
|
||||||
|
- name: backups
|
||||||
|
nfs:
|
||||||
|
server: 192.168.27.2
|
||||||
|
path: /backup/minecraft
|
||||||
Reference in New Issue
Block a user