rm sled migration, sqlite migration

This commit is contained in:
lda
2026-05-03 16:22:43 +07:00 Verified
parent 7e8e1fca23
commit b573e9f27f
15 changed files with 149 additions and 510 deletions
Generated
+18 -121
View File
@@ -233,12 +233,6 @@ version = "1.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.11.0" version = "2.11.0"
@@ -475,24 +469,6 @@ version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853"
[[package]]
name = "crc32fast"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"crossbeam-utils",
]
[[package]] [[package]]
name = "crossbeam-queue" name = "crossbeam-queue"
version = "0.3.12" version = "0.3.12"
@@ -514,10 +490,10 @@ version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags",
"crossterm_winapi", "crossterm_winapi",
"document-features", "document-features",
"parking_lot 0.12.5", "parking_lot",
"rustix", "rustix",
"winapi", "winapi",
] ]
@@ -747,16 +723,6 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "fs2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
dependencies = [
"libc",
"winapi",
]
[[package]] [[package]]
name = "fs_extra" name = "fs_extra"
version = "1.3.0" version = "1.3.0"
@@ -813,7 +779,7 @@ checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"lock_api", "lock_api",
"parking_lot 0.12.5", "parking_lot",
] ]
[[package]] [[package]]
@@ -862,15 +828,6 @@ dependencies = [
"slab", "slab",
] ]
[[package]]
name = "fxhash"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
dependencies = [
"byteorder",
]
[[package]] [[package]]
name = "generic-array" name = "generic-array"
version = "0.14.7" version = "0.14.7"
@@ -1309,15 +1266,6 @@ dependencies = [
"serde_core", "serde_core",
] ]
[[package]]
name = "instant"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "ipnet" name = "ipnet"
version = "2.12.0" version = "2.12.0"
@@ -1470,7 +1418,7 @@ version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c" checksum = "e02f3bb43d335493c96bf3fd3a321600bf6bd07ed34bc64118e9293bdffea46c"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags",
"libc", "libc",
"plain", "plain",
"redox_syscall 0.7.4", "redox_syscall 0.7.4",
@@ -1617,7 +1565,7 @@ version = "0.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ce3636fa715e988114552619582b530481fd5ef176a1e5c1bf024077c2c9445" checksum = "4ce3636fa715e988114552619582b530481fd5ef176a1e5c1bf024077c2c9445"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags",
"libc", "libc",
"log", "log",
"netlink-packet-core", "netlink-packet-core",
@@ -1656,7 +1604,7 @@ version = "0.30.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags",
"cfg-if", "cfg-if",
"cfg_aliases", "cfg_aliases",
"libc", "libc",
@@ -1668,7 +1616,7 @@ version = "0.31.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3" checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags",
"cfg-if", "cfg-if",
"cfg_aliases", "cfg_aliases",
"libc", "libc",
@@ -1859,17 +1807,6 @@ version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
[[package]]
name = "parking_lot"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
dependencies = [
"instant",
"lock_api",
"parking_lot_core 0.8.6",
]
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
version = "0.12.5" version = "0.12.5"
@@ -1877,21 +1814,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
dependencies = [ dependencies = [
"lock_api", "lock_api",
"parking_lot_core 0.9.12", "parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
dependencies = [
"cfg-if",
"instant",
"libc",
"redox_syscall 0.2.16",
"smallvec",
"winapi",
] ]
[[package]] [[package]]
@@ -2189,22 +2112,13 @@ dependencies = [
"getrandom 0.3.4", "getrandom 0.3.4",
] ]
[[package]]
name = "redox_syscall"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags 1.3.2",
]
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.5.18" version = "0.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags",
] ]
[[package]] [[package]]
@@ -2213,7 +2127,7 @@ version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a" checksum = "f450ad9c3b1da563fb6948a8e0fb0fb9269711c9c73d9ea1de5058c79c8d643a"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags",
] ]
[[package]] [[package]]
@@ -2394,7 +2308,7 @@ version = "1.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags",
"errno", "errno",
"libc", "libc",
"linux-raw-sys", "linux-raw-sys",
@@ -2557,7 +2471,7 @@ version = "3.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags",
"core-foundation", "core-foundation",
"core-foundation-sys", "core-foundation-sys",
"libc", "libc",
@@ -2696,7 +2610,7 @@ dependencies = [
"futures-util", "futures-util",
"log", "log",
"once_cell", "once_cell",
"parking_lot 0.12.5", "parking_lot",
"scc", "scc",
"serial_test_derive", "serial_test_derive",
] ]
@@ -2775,22 +2689,6 @@ version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
[[package]]
name = "sled"
version = "0.34.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935"
dependencies = [
"crc32fast",
"crossbeam-epoch",
"crossbeam-utils",
"fs2",
"fxhash",
"libc",
"log",
"parking_lot 0.11.2",
]
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.15.1" version = "1.15.1"
@@ -2923,7 +2821,7 @@ checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526"
dependencies = [ dependencies = [
"atoi", "atoi",
"base64", "base64",
"bitflags 2.11.0", "bitflags",
"byteorder", "byteorder",
"bytes", "bytes",
"crc", "crc",
@@ -2966,7 +2864,7 @@ checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46"
dependencies = [ dependencies = [
"atoi", "atoi",
"base64", "base64",
"bitflags 2.11.0", "bitflags",
"byteorder", "byteorder",
"crc", "crc",
"dotenvy", "dotenvy",
@@ -3399,7 +3297,7 @@ version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags",
"bytes", "bytes",
"futures-core", "futures-core",
"futures-util", "futures-util",
@@ -3759,7 +3657,6 @@ dependencies = [
"reqwest 0.13.2", "reqwest 0.13.2",
"serde", "serde",
"serde_json", "serde_json",
"sled",
"sqlx", "sqlx",
"time", "time",
"tokio", "tokio",
@@ -3932,7 +3829,7 @@ version = "0.244.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe"
dependencies = [ dependencies = [
"bitflags 2.11.0", "bitflags",
"hashbrown 0.15.5", "hashbrown 0.15.5",
"indexmap 2.14.0", "indexmap 2.14.0",
"semver", "semver",
@@ -4437,7 +4334,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bitflags 2.11.0", "bitflags",
"indexmap 2.14.0", "indexmap 2.14.0",
"log", "log",
"serde", "serde",
+1 -1
View File
@@ -146,7 +146,7 @@ Relative paths in config are resolved under `data_dir`.
Legacy sled state can be imported explicitly: Legacy sled state can be imported explicitly:
```sh ```sh
wakey-control-plane import-sled-state --from-sled-state /var/lib/wakey-control-plane/state.db --to-state-file /var/lib/wakey-control-plane/state.sqlite3 wakey-control-plane migrate-sqlite-state --from /var/lib/wakey-control-plane/legacy.sqlite3 --to /var/lib/wakey-control-plane/state.sqlite3
``` ```
Enroll tokens are now expiring and revocable. Issuance returns `expires_at_unix`. Enroll tokens are now expiring and revocable. Issuance returns `expires_at_unix`.
+1 -1
View File
@@ -20,7 +20,7 @@ reqwest = { version = "0.13", default-features = false, features = [
] } ] }
serde = { workspace = true } serde = { workspace = true }
serde_json = { workspace = true } serde_json = { workspace = true }
sled = "0.34"
sqlx = { version = "0.8", features = [ sqlx = { version = "0.8", features = [
"runtime-tokio", "runtime-tokio",
"sqlite", "sqlite",
+5 -6
View File
@@ -43,8 +43,8 @@ pub enum Command {
RevokeAgent(RevokeAgentArgs), RevokeAgent(RevokeAgentArgs),
/// Print state backend stats. /// Print state backend stats.
StateStats(StateStatsArgs), StateStats(StateStatsArgs),
/// Import a legacy sled state directory into a SQLite state file. /// Migrate a legacy sqlite state into a new SQLite state file.
ImportSledState(ImportSledStateArgs), MigrateSqliteState(MigrateSqliteStateArgs),
/// Send SIGHUP to an already-running daemon. /// Send SIGHUP to an already-running daemon.
Reload(ReloadArgs), Reload(ReloadArgs),
} }
@@ -253,14 +253,13 @@ pub struct StateStatsArgs {
pub target: AdminTargetArgs, pub target: AdminTargetArgs,
} }
// not the greatest thing ive used, this and the migration code gets deleted shortly anyways
#[derive(Args)] #[derive(Args)]
pub struct ImportSledStateArgs { pub struct MigrateSqliteStateArgs {
#[arg(long, visible_alias = "from")] #[arg(long, visible_alias = "from")]
pub from_sled_state: PathBuf, pub from_sqlite_db: PathBuf,
#[arg(long, visible_alias = "to")] #[arg(long, visible_alias = "to")]
pub to_state_file: PathBuf, pub to_sqlite_db: PathBuf,
#[arg(long)] #[arg(long)]
pub force: bool, pub force: bool,
+2 -2
View File
@@ -61,9 +61,9 @@ async fn main() -> Result<()> {
tracing::init(cli.verbose, &config::TelemetryConfig::default())?; tracing::init(cli.verbose, &config::TelemetryConfig::default())?;
runtime::state_stats(args).await runtime::state_stats(args).await
} }
Command::ImportSledState(args) => { Command::MigrateSqliteState(args) => {
tracing::init(cli.verbose, &config::TelemetryConfig::default())?; tracing::init(cli.verbose, &config::TelemetryConfig::default())?;
runtime::import_sled_state(args).await runtime::migrate_sqlite_state(args).await
} }
Command::Reload(args) => { Command::Reload(args) => {
tracing::init(cli.verbose, &config::TelemetryConfig::default())?; tracing::init(cli.verbose, &config::TelemetryConfig::default())?;
+7 -6
View File
@@ -4,7 +4,7 @@ use anyhow::{Context, Result};
use crate::api; use crate::api;
use crate::cli::{ use crate::cli::{
ImportSledStateArgs, IssueEnrollTokenArgs, ListEnrollTokensArgs, RevokeAgentArgs, IssueEnrollTokenArgs, ListEnrollTokensArgs, MigrateSqliteStateArgs, RevokeAgentArgs,
RevokeEnrollTokenArgs, StateStatsArgs, RevokeEnrollTokenArgs, StateStatsArgs,
}; };
use crate::config; use crate::config;
@@ -277,10 +277,11 @@ pub async fn state_stats(args: StateStatsArgs) -> Result<()> {
Ok(()) Ok(())
} }
pub async fn import_sled_state(args: ImportSledStateArgs) -> Result<()> { pub async fn migrate_sqlite_state(args: MigrateSqliteStateArgs) -> Result<()> {
state::Store::import_sled_state(&args.from_sled_state, &args.to_state_file, args.force).await?; state::Store::migrate_sqlite_state(&args.from_sqlite_db, &args.to_sqlite_db, args.force)
println!("from_sled_state={}", args.from_sled_state.display()); .await?;
println!("to_state_file={}", args.to_state_file.display()); println!("from_sqlite_db={}", args.from_sqlite_db.display());
println!("imported=true"); println!("to_sqlite_db={}", args.to_sqlite_db.display());
println!("migrated=true");
Ok(()) Ok(())
} }
+1 -1
View File
@@ -27,7 +27,7 @@ mod admin;
mod process; mod process;
pub use admin::revoke_agent; pub use admin::revoke_agent;
pub use admin::{ pub use admin::{
import_sled_state, issue_enroll_token, list_enroll_tokens, revoke_enroll_token, state_stats, issue_enroll_token, list_enroll_tokens, migrate_sqlite_state, revoke_enroll_token, state_stats,
}; };
pub use process::reload_daemon; pub use process::reload_daemon;
use process::{remove_pid_file, write_pid_file}; use process::{remove_pid_file, write_pid_file};
+1 -63
View File
@@ -29,11 +29,10 @@ mod db;
mod devices; mod devices;
mod enrollment; mod enrollment;
mod helpers; mod helpers;
mod import_sled; mod migrate_sqlite;
use helpers::alerts_audit::*; use helpers::alerts_audit::*;
use helpers::core::*; use helpers::core::*;
use helpers::legacy::*;
use helpers::rows::*; use helpers::rows::*;
#[cfg(test)] #[cfg(test)]
@@ -588,65 +587,4 @@ mod tests {
let _ = fs::remove_dir_all(&dir); let _ = fs::remove_dir_all(&dir);
} }
#[tokio::test]
async fn import_sled_state_copies_core_records() {
let dir =
std::env::temp_dir().join(format!("wakey-cp-store-test-{}", uuid::Uuid::new_v4()));
let sled_path = dir.join("legacy-state.db");
let sqlite_path = dir.join("state.sqlite3");
fs::create_dir_all(&dir).expect("dir should exist");
let legacy = sled::open(&sled_path).expect("legacy db should open");
let meta = legacy.open_tree("meta").expect("meta should open");
meta.insert(
super::SCHEMA_VERSION_KEY.as_bytes(),
&super::SCHEMA_VERSION.to_le_bytes(),
)
.expect("schema should insert");
let enroll = legacy
.open_tree("enroll_tokens")
.expect("enroll tree should open");
enroll
.insert(b"enr-import-test", &(i64::MAX as u64).to_le_bytes())
.expect("token should insert");
let agents = legacy.open_tree("agents").expect("agents should open");
agents
.insert(b"agent-import", b"tok-import")
.expect("agent should insert");
let agent_meta = legacy.open_tree("agent_meta").expect("meta should open");
agent_meta
.insert(b"agent-import", b"imported-router")
.expect("nickname should insert");
legacy.flush().expect("legacy flush should succeed");
drop(agent_meta);
drop(agents);
drop(enroll);
drop(meta);
drop(legacy);
Store::import_sled_state(&sled_path, &sqlite_path, false)
.await
.expect("import should succeed");
let store = Store::load_or_init(&sqlite_path, Vec::new(), Duration::from_secs(60))
.await
.expect("sqlite should load");
assert!(store.verify_agent_token("agent-import", "tok-import").await);
let agents = store.list_agents_with_nicknames().await;
assert!(agents.iter().any(|(id, nickname)| {
id == "agent-import" && nickname.as_deref() == Some("imported-router")
}));
let tokens = store
.list_enroll_tokens()
.await
.expect("tokens should list");
assert!(
tokens
.iter()
.any(|token| token.enroll_token == "enr-import-test")
);
let _ = fs::remove_dir_all(&dir);
}
} }
+1 -2
View File
@@ -8,9 +8,8 @@ impl Store {
) -> Result<Self> { ) -> Result<Self> {
if path.is_dir() { if path.is_dir() {
anyhow::bail!( anyhow::bail!(
"state_file {} is a directory, which looks like a legacy sled store; run `wakey-control-plane import-sled-state --from-sled-state {} --to-state-file <sqlite-file>` and update state_file", "state_file {} is a directory, which looks like a legacy sled store; sled state is no longer supported",
path.display(), path.display(),
path.display()
); );
} }
@@ -2,7 +2,6 @@ use super::*;
pub(super) mod alerts_audit; pub(super) mod alerts_audit;
pub(super) mod core; pub(super) mod core;
pub(super) mod legacy;
pub(super) mod rows; pub(super) mod rows;
#[cfg(test)] #[cfg(test)]
pub(crate) mod test_helpers; pub(crate) mod test_helpers;
@@ -1,38 +1,5 @@
use super::*; use super::*;
pub async fn insert_audit_event(pool: &SqlitePool, key: &str, event: &AuditEvent) -> Result<()> {
let metadata_json =
serde_json::to_string(&event.metadata).context("failed to encode audit metadata")?;
let ts_unix = i64::try_from(event.ts_unix).context("audit timestamp overflow")?;
let latency_ms = event
.latency_ms
.map(i64::try_from)
.transpose()
.context("audit latency overflow")?;
sqlx::query!(
"INSERT OR REPLACE INTO audit_events
(event_key, event_id, ts_unix, actor_type, actor_id, agent_id, request_id,
event_type, outcome, latency_ms, message, metadata_json)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12)",
key,
event.event_id,
ts_unix,
event.actor_type,
event.actor_id,
event.agent_id,
event.request_id,
event.event_type,
event.outcome,
latency_ms,
event.message,
metadata_json
)
.execute(pool)
.await
.context("failed persisting audit event")?;
Ok(())
}
pub async fn insert_active_alert( pub async fn insert_active_alert(
tx: &mut Transaction<'_, Sqlite>, tx: &mut Transaction<'_, Sqlite>,
alert: &AlertState, alert: &AlertState,
@@ -66,36 +33,6 @@ pub async fn insert_active_alert(
Ok(()) Ok(())
} }
pub async fn insert_active_alert_pool(pool: &SqlitePool, alert: &AlertState) -> Result<()> {
let metadata_json =
serde_json::to_string(&alert.metadata).context("failed to encode active alert metadata")?;
let alert_value = i64::try_from(alert.value).context("active alert value overflow")?;
let alert_threshold =
i64::try_from(alert.threshold).context("active alert threshold overflow")?;
let last_seen_unix =
i64::try_from(alert.last_seen_unix).context("active alert timestamp overflow")?;
sqlx::query!(
"INSERT OR REPLACE INTO active_alerts
(alert_id, kind, severity, status, agent_id, message, value, threshold,
last_seen_unix, metadata_json)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)",
alert.alert_id,
alert.kind,
alert.severity,
alert.status,
alert.agent_id,
alert.message,
alert_value,
alert_threshold,
last_seen_unix,
metadata_json
)
.execute(pool)
.await
.context("failed writing active alert snapshot")?;
Ok(())
}
pub async fn insert_alert_transition( pub async fn insert_alert_transition(
tx: &mut Transaction<'_, Sqlite>, tx: &mut Transaction<'_, Sqlite>,
key: &str, key: &str,
@@ -126,36 +63,6 @@ pub async fn insert_alert_transition(
Ok(()) Ok(())
} }
pub async fn insert_alert_transition_pool(
pool: &SqlitePool,
key: &str,
transition: &AlertTransition,
) -> Result<()> {
let metadata_json = serde_json::to_string(&transition.metadata)
.context("failed to encode alert transition metadata")?;
let ts_unix = i64::try_from(transition.ts_unix).context("alert timestamp overflow")?;
sqlx::query!(
"INSERT OR REPLACE INTO alert_transitions
(transition_key, transition_id, ts_unix, alert_id, kind, agent_id,
from_status, to_status, message, metadata_json)
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10)",
key,
transition.transition_id,
ts_unix,
transition.alert_id,
transition.kind,
transition.agent_id,
transition.from_status,
transition.to_status,
transition.message,
metadata_json
)
.execute(pool)
.await
.context("failed persisting alert transition")?;
Ok(())
}
pub fn audit_event_from_row(row: sqlx::sqlite::SqliteRow) -> Result<AuditEvent> { pub fn audit_event_from_row(row: sqlx::sqlite::SqliteRow) -> Result<AuditEvent> {
let ts_unix: i64 = row.try_get("ts_unix")?; let ts_unix: i64 = row.try_get("ts_unix")?;
let latency_ms: Option<i64> = row.try_get("latency_ms")?; let latency_ms: Option<i64> = row.try_get("latency_ms")?;
@@ -29,15 +29,6 @@ pub fn now_unix() -> u64 {
.unwrap_or(0) .unwrap_or(0)
} }
pub fn decode_expiry(raw: &[u8]) -> Result<u64> {
if raw.len() != 8 {
anyhow::bail!("invalid token expiry length {}", raw.len());
}
let mut arr = [0u8; 8];
arr.copy_from_slice(raw);
Ok(u64::from_le_bytes(arr))
}
pub fn decode_schema(raw: &[u8]) -> Result<u32> { pub fn decode_schema(raw: &[u8]) -> Result<u32> {
if raw.len() != 4 { if raw.len() != 4 {
anyhow::bail!("invalid schema version length {}", raw.len()); anyhow::bail!("invalid schema version length {}", raw.len());
@@ -1,143 +0,0 @@
use super::*;
pub async fn import_tree_raw(
pool: &SqlitePool,
legacy: &sled::Db,
sled_tree: &str,
sql_table: &str,
key_col: &str,
value_col: &str,
) -> Result<()> {
let tree = legacy
.open_tree(sled_tree)
.with_context(|| format!("failed to open legacy {sled_tree} tree"))?;
let sql =
format!("INSERT OR REPLACE INTO {sql_table} ({key_col}, {value_col}) VALUES (?1, ?2)");
for item in tree.iter() {
let (key, value) =
item.with_context(|| format!("failed reading legacy {sled_tree} tree"))?;
let key = String::from_utf8(key.to_vec())
.with_context(|| format!("legacy {sled_tree} key is not utf-8"))?;
sqlx::query(&sql)
.bind(key)
.bind(value.to_vec())
.execute(pool)
.await
.with_context(|| format!("failed importing legacy {sled_tree} row"))?;
}
Ok(())
}
pub async fn import_enroll_tokens(pool: &SqlitePool, legacy: &sled::Db) -> Result<()> {
let tree = legacy
.open_tree("enroll_tokens")
.context("failed to open legacy enroll_tokens tree")?;
for item in tree.iter() {
let (token, expiry) = item.context("failed reading legacy enroll token")?;
let token =
String::from_utf8(token.to_vec()).context("legacy enroll token key is not utf-8")?;
let expires_at_unix = i64::try_from(decode_expiry(expiry.as_ref())?)
.context("legacy token expiry overflow")?;
sqlx::query!(
"INSERT OR REPLACE INTO enroll_tokens (token, expires_at_unix) VALUES (?1, ?2)",
token,
expires_at_unix
)
.execute(pool)
.await
.context("failed importing legacy enroll token")?;
}
Ok(())
}
pub async fn import_agents(pool: &SqlitePool, legacy: &sled::Db) -> Result<()> {
let tree = legacy
.open_tree("agents")
.context("failed to open legacy agents tree")?;
for item in tree.iter() {
let (agent_id, agent_token) = item.context("failed reading legacy agent")?;
let agent_id =
String::from_utf8(agent_id.to_vec()).context("legacy agent id is not utf-8")?;
let agent_token =
String::from_utf8(agent_token.to_vec()).context("legacy agent token is not utf-8")?;
sqlx::query!(
"INSERT OR REPLACE INTO agents (agent_id, agent_token) VALUES (?1, ?2)",
agent_id,
agent_token
)
.execute(pool)
.await
.context("failed importing legacy agent")?;
}
Ok(())
}
pub async fn import_agent_meta(pool: &SqlitePool, legacy: &sled::Db) -> Result<()> {
let tree = legacy
.open_tree("agent_meta")
.context("failed to open legacy agent_meta tree")?;
for item in tree.iter() {
let (agent_id, nickname) = item.context("failed reading legacy agent metadata")?;
let agent_id =
String::from_utf8(agent_id.to_vec()).context("legacy agent id is not utf-8")?;
let nickname =
String::from_utf8(nickname.to_vec()).context("legacy nickname is not utf-8")?;
sqlx::query!(
"INSERT OR REPLACE INTO agent_meta (agent_id, nickname) VALUES (?1, ?2)",
agent_id,
nickname
)
.execute(pool)
.await
.context("failed importing legacy agent metadata")?;
}
Ok(())
}
pub async fn import_audit_events(pool: &SqlitePool, legacy: &sled::Db) -> Result<()> {
let tree = legacy
.open_tree("audit_events")
.context("failed to open legacy audit_events tree")?;
for item in tree.iter() {
let (key, value) = item.context("failed reading legacy audit event")?;
let key = String::from_utf8(key.to_vec()).context("legacy audit key is not utf-8")?;
let event: AuditEvent =
serde_json::from_slice(value.as_ref()).context("failed decoding legacy audit event")?;
insert_audit_event(pool, &key, &event)
.await
.context("failed importing legacy audit event")?;
}
Ok(())
}
pub async fn import_active_alerts(pool: &SqlitePool, legacy: &sled::Db) -> Result<()> {
let tree = legacy
.open_tree("active_alerts")
.context("failed to open legacy active_alerts tree")?;
for item in tree.iter() {
let (_, value) = item.context("failed reading legacy active alert")?;
let alert: AlertState = serde_json::from_slice(value.as_ref())
.context("failed decoding legacy active alert")?;
insert_active_alert_pool(pool, &alert)
.await
.context("failed importing legacy active alert")?;
}
Ok(())
}
pub async fn import_alert_transitions(pool: &SqlitePool, legacy: &sled::Db) -> Result<()> {
let tree = legacy
.open_tree("alert_transitions")
.context("failed to open legacy alert_transitions tree")?;
for item in tree.iter() {
let (key, value) = item.context("failed reading legacy alert transition")?;
let key =
String::from_utf8(key.to_vec()).context("legacy alert transition key is not utf-8")?;
let transition: AlertTransition = serde_json::from_slice(value.as_ref())
.context("failed decoding legacy alert transition")?;
insert_alert_transition_pool(pool, &key, &transition)
.await
.context("failed importing legacy alert transition")?;
}
Ok(())
}
@@ -1,61 +0,0 @@
use super::*;
impl Store {
pub async fn import_sled_state(
from_sled_state: &Path,
to_state_file: &Path,
force: bool,
) -> Result<()> {
if !from_sled_state.is_dir() {
anyhow::bail!(
"legacy sled state {} does not exist or is not a directory",
from_sled_state.display()
);
}
if to_state_file.is_dir() {
anyhow::bail!(
"target state file {} is a directory",
to_state_file.display()
);
}
if to_state_file.exists()
&& to_state_file
.metadata()
.with_context(|| format!("failed to stat {}", to_state_file.display()))?
.len()
> 0
{
if !force {
anyhow::bail!(
"target SQLite state file {} already exists and is non-empty; re-run with --force to overwrite",
to_state_file.display()
);
}
std::fs::remove_file(to_state_file)
.with_context(|| format!("failed to remove {}", to_state_file.display()))?;
}
let legacy = sled::open(from_sled_state).with_context(|| {
format!(
"failed to open legacy sled state {}",
from_sled_state.display()
)
})?;
let store = Store::load_or_init(to_state_file, Vec::new(), Duration::from_secs(1)).await?;
import_tree_raw(&store.pool, &legacy, "meta", "meta", "key", "value").await?;
import_enroll_tokens(&store.pool, &legacy).await?;
import_agents(&store.pool, &legacy).await?;
import_agent_meta(&store.pool, &legacy).await?;
import_audit_events(&store.pool, &legacy).await?;
import_active_alerts(&store.pool, &legacy).await?;
import_alert_transitions(&store.pool, &legacy).await?;
info!(
from = %from_sled_state.display(),
to = %to_state_file.display(),
"imported legacy sled state into SQLite"
);
Ok(())
}
}
@@ -0,0 +1,112 @@
use super::*;
impl Store {
pub async fn migrate_sqlite_state(
from_sqlite_state: &Path,
to_state_file: &Path,
force: bool,
) -> Result<()> {
if !from_sqlite_state.is_file() {
anyhow::bail!(
"legacy sqlite state {} does not exist or is not a file",
from_sqlite_state.display()
);
}
if to_state_file.is_dir() {
anyhow::bail!(
"target state file {} is a directory",
to_state_file.display()
);
}
let is_same_file = match (
std::fs::canonicalize(from_sqlite_state),
std::fs::canonicalize(to_state_file),
) {
(Ok(from_canon), Ok(to_canon)) => from_canon == to_canon,
_ => from_sqlite_state == to_state_file,
};
let actual_from_path = if is_same_file {
let mut bak = to_state_file.to_path_buf();
let mut file_name = bak.file_name().unwrap_or_default().to_os_string();
file_name.push(".bak");
bak.set_file_name(file_name);
if bak.exists() {
if !force {
anyhow::bail!(
"backup file {} already exists; re-run with --force to overwrite",
bak.display()
);
}
std::fs::remove_file(&bak).with_context(|| {
format!("failed to remove existing backup {}", bak.display())
})?;
}
std::fs::rename(from_sqlite_state, &bak)
.with_context(|| "failed to rename legacy state for in-place migration")?;
bak
} else {
if to_state_file.exists()
&& to_state_file
.metadata()
.with_context(|| format!("failed to stat {}", to_state_file.display()))?
.len()
> 0
{
if !force {
anyhow::bail!(
"target SQLite state file {} already exists and is non-empty; re-run with --force to overwrite",
to_state_file.display()
);
}
std::fs::remove_file(to_state_file)
.with_context(|| format!("failed to remove {}", to_state_file.display()))?;
}
from_sqlite_state.to_path_buf()
};
let store = Store::load_or_init(to_state_file, Vec::new(), Duration::from_secs(1)).await?;
let from_path_str = actual_from_path
.to_str()
.ok_or_else(|| anyhow::anyhow!("invalid path"))?;
sqlx::query(&format!("ATTACH DATABASE '{}' AS legacy", from_path_str))
.execute(&store.pool)
.await
.with_context(|| "failed to attach legacy database")?;
let tables = [
"meta",
"enroll_tokens",
"agents",
"agent_meta",
"known_devices",
"device_identifiers",
"audit_events",
"active_alerts",
"alert_transitions",
];
for table in tables {
let q = format!("INSERT INTO {} SELECT * FROM legacy.{}", table, table);
sqlx::query(&q)
.execute(&store.pool)
.await
.with_context(|| format!("failed to migrate table {}", table))?;
}
sqlx::query("DETACH DATABASE legacy")
.execute(&store.pool)
.await?;
info!(
from = %from_sqlite_state.display(),
to = %to_state_file.display(),
"migrated legacy sqlite state into new sqlite state"
);
Ok(())
}
}