1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
//! `aion store harden`: bring every file under a store to its private mode,
//! once, for a store that predates private-by-creation (haematite 0.12.1) or
//! that someone loosened by hand.
//!
//! The boot no longer walks the tree — it checks the data root and the shard
//! directories only — so this verb is where the full walk lives. It narrates
//! one line per top-level entry with the running file count and ends with a
//! total, and it refuses to run while a live server holds the store.
//!
//! Exit codes: 0 walked; 2 the verb could not do its job (no store to walk, a
//! held store, a failure in the tree) — the reason is on stderr in one
//! sentence.
use std::path::PathBuf;
use std::process::ExitCode;
use aion_server::config::{CliOverrides, ServerConfig};
use aion_server::store_harden::harden_store_tree;
use clap::{Args, Subcommand};
/// `aion store …`: verbs against a store's data directory, with no server.
#[derive(Args, Clone, Debug)]
pub struct StoreCommand {
/// The verb.
#[command(subcommand)]
pub command: StoreSubcommand,
}
/// The store verbs.
#[derive(Subcommand, Clone, Debug)]
pub enum StoreSubcommand {
/// Bring every directory under the store to 0700 and every file to 0600.
///
/// The one-time step for a store created before Aion 0.28 (files were
/// created with the process umask and repaired at every boot), or for a
/// store whose modes were loosened by hand. Refuses while a server holds
/// the store. Newer stores need it never: every file is private from its
/// own creation.
Harden(HardenArgs),
}
/// Arguments to `aion store harden`.
#[derive(Args, Clone, Debug)]
pub struct HardenArgs {
/// The store's data directory. Defaults to `store.data_dir` from the
/// configuration this machine's server would load.
#[arg(long, value_name = "DIR")]
pub data_dir: Option<PathBuf>,
/// Explicit configuration file, when resolving the data directory from
/// configuration.
#[arg(long, value_name = "FILE")]
pub config: Option<PathBuf>,
}
/// Run `aion store harden`.
pub fn harden(args: &HardenArgs) -> ExitCode {
let data_dir = match resolve_data_dir(args) {
Ok(data_dir) => data_dir,
Err(reason) => {
eprintln!("aion store harden: {reason}");
return ExitCode::from(2);
}
};
println!("hardening {}", data_dir.display());
let outcome = harden_store_tree(&data_dir, |progress| {
println!(
" {}: {} file(s), {} so far",
progress.entry, progress.files, progress.total_files
);
});
match outcome {
Ok(report) => {
println!(
"done: {} directories set to 0700, {} files set to 0600",
report.directories, report.files
);
ExitCode::SUCCESS
}
Err(error) => {
eprintln!("aion store harden: {error}");
ExitCode::from(2)
}
}
}
fn resolve_data_dir(args: &HardenArgs) -> Result<PathBuf, String> {
if let Some(explicit) = &args.data_dir {
return Ok(explicit.clone());
}
let overrides = CliOverrides {
config_path: args.config.clone(),
..CliOverrides::default()
};
let config = ServerConfig::load(&overrides)
.map_err(|error| format!("could not load the configuration to find the store: {error}"))?;
match config.store.data_dir {
Some(data_dir) => Ok(PathBuf::from(data_dir)),
None => Err(
"the configuration names no store.data_dir (the store backend is not haematite); \
pass --data-dir"
.to_owned(),
),
}
}