Add config validation binary and scripts

This commit is contained in:
Albert Armea 2026-01-04 21:00:44 -05:00
parent 65292566f2
commit 7abd61f09a
5 changed files with 321 additions and 0 deletions

View file

@ -5,6 +5,10 @@ edition.workspace = true
license.workspace = true license.workspace = true
description = "Configuration parsing and validation for shepherdd" description = "Configuration parsing and validation for shepherdd"
[[bin]]
name = "validate-config"
path = "src/bin/validate-config.rs"
[dependencies] [dependencies]
shepherd-util = { workspace = true } shepherd-util = { workspace = true }
shepherd-api = { workspace = true } shepherd-api = { workspace = true }

View file

@ -0,0 +1,101 @@
//! Config validation CLI tool
//!
//! Validates a shepherdd configuration file and reports any errors.
use shepherd_api::EntryKind;
use shepherd_util::default_config_path;
use std::path::PathBuf;
use std::process::ExitCode;
fn main() -> ExitCode {
let args: Vec<String> = std::env::args().collect();
let config_path = match args.get(1) {
Some(path) => PathBuf::from(path),
None => {
let default_path = default_config_path();
eprintln!("Usage: validate-config [config-file]");
eprintln!();
eprintln!("Validates a shepherdd configuration file.");
eprintln!();
eprintln!("If no path is provided, uses: {}", default_path.display());
eprintln!();
eprintln!("Example:");
eprintln!(" validate-config {}", default_path.display());
eprintln!(" validate-config config.example.toml");
return ExitCode::from(2);
}
};
// Check file exists
if !config_path.exists() {
eprintln!("Error: Configuration file not found: {}", config_path.display());
return ExitCode::from(1);
}
// Try to load and validate
match shepherd_config::load_config(&config_path) {
Ok(policy) => {
println!("✓ Configuration is valid");
println!();
println!("Summary:");
println!(" Config version: {}", shepherd_config::CURRENT_CONFIG_VERSION);
println!(" Entries: {}", policy.entries.len());
// Show entry summary
if !policy.entries.is_empty() {
println!();
println!("Entries:");
for entry in &policy.entries {
let kind_str = match &entry.kind {
EntryKind::Process { command, .. } => {
format!("process ({})", command)
}
EntryKind::Snap { snap_name, .. } => {
format!("snap ({})", snap_name)
}
EntryKind::Vm { driver, .. } => {
format!("vm ({})", driver)
}
EntryKind::Media { library_id, .. } => {
format!("media ({})", library_id)
}
EntryKind::Custom { type_name, .. } => {
format!("custom ({})", type_name)
}
};
println!(" - {} [{}]: {}", entry.id.as_str(), kind_str, entry.label);
}
}
ExitCode::SUCCESS
}
Err(e) => {
eprintln!("✗ Configuration validation failed");
eprintln!();
match &e {
shepherd_config::ConfigError::ReadError(io_err) => {
eprintln!("Failed to read file: {}", io_err);
}
shepherd_config::ConfigError::ParseError(parse_err) => {
eprintln!("TOML parse error:");
eprintln!(" {}", parse_err);
}
shepherd_config::ConfigError::ValidationFailed { errors } => {
eprintln!("Validation errors ({}):", errors.len());
for err in errors {
eprintln!(" - {}", err);
}
}
shepherd_config::ConfigError::UnsupportedVersion(ver) => {
eprintln!(
"Unsupported config version: {} (expected {})",
ver,
shepherd_config::CURRENT_CONFIG_VERSION
);
}
}
ExitCode::from(1)
}
}
}

View file

@ -15,6 +15,9 @@ This directory contains the unified script system for shepherd-launcher.
# Building # Building
./shepherd build [--release] ./shepherd build [--release]
# Configuration
./shepherd config validate [path]
# Development # Development
./shepherd dev run ./shepherd dev run
@ -39,6 +42,7 @@ scripts/
│ ├── common.sh # Logging, error handling, sudo helpers │ ├── common.sh # Logging, error handling, sudo helpers
│ ├── deps.sh # Dependency management │ ├── deps.sh # Dependency management
│ ├── build.sh # Cargo build logic │ ├── build.sh # Cargo build logic
│ ├── config.sh # Configuration validation
│ ├── sway.sh # Nested sway execution │ ├── sway.sh # Nested sway execution
│ ├── install.sh # Installation logic │ ├── install.sh # Installation logic
│ └── harden.sh # User hardening/unhardening │ └── harden.sh # User hardening/unhardening

202
scripts/lib/config.sh Normal file
View file

@ -0,0 +1,202 @@
#!/usr/bin/env bash
# Configuration validation logic for shepherd-launcher
# Validates shepherdd configuration files
# Get the directory containing this script
CONFIG_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Source common utilities
# shellcheck source=common.sh
source "$CONFIG_LIB_DIR/common.sh"
# Default configuration paths
# Uses XDG_CONFIG_HOME or ~/.config/shepherd/config.toml
get_default_config_path() {
if [[ -n "${XDG_CONFIG_HOME:-}" ]]; then
echo "$XDG_CONFIG_HOME/shepherd/config.toml"
else
echo "$HOME/.config/shepherd/config.toml"
fi
}
EXAMPLE_CONFIG_NAME="config.example.toml"
# Get path to the validate-config binary
get_validate_binary() {
local release="${1:-false}"
local repo_root
repo_root="$(get_repo_root)"
if [[ "$release" == "true" ]]; then
echo "$repo_root/target/release/validate-config"
else
echo "$repo_root/target/debug/validate-config"
fi
}
# Build the validate-config binary if needed
build_validate_binary() {
local release="${1:-false}"
local repo_root
repo_root="$(get_repo_root)"
verify_repo
require_command cargo rust
cd "$repo_root" || die "Failed to change directory to $repo_root"
if [[ "$release" == "true" ]]; then
info "Building validate-config (release mode)..."
cargo build --release --bin validate-config
else
info "Building validate-config..."
cargo build --bin validate-config
fi
}
# Ensure the validate binary exists, building if necessary
ensure_validate_binary() {
local release="${1:-false}"
local binary
binary="$(get_validate_binary "$release")"
if [[ ! -x "$binary" ]]; then
build_validate_binary "$release"
fi
echo "$binary"
}
# Validate a configuration file
validate_config_file() {
local config_path="$1"
local release="${2:-false}"
# Build/find the validator
local binary
binary="$(ensure_validate_binary "$release")"
# Run validation
"$binary" "$config_path"
}
# Show config help
config_usage() {
cat <<EOF
Usage: shepherd config <command> [options]
Commands:
validate [path] Validate a configuration file
help Show this help message
Options:
--release Use release build of validator
The validate command checks a configuration file for:
- Valid TOML syntax
- Correct config_version
- Valid entry definitions
- Valid time windows and availability specs
- Warning thresholds that make sense with limits
Examples:
# Validate the installed config
shepherd config validate
# Validate a specific file
shepherd config validate /path/to/config.toml
# Validate the example config in the repo
shepherd config validate config.example.toml
Default paths:
Installed: $(get_default_config_path)
Example: \$REPO_ROOT/$EXAMPLE_CONFIG_NAME
EOF
}
# Main entry point for config commands
config_main() {
local subcmd="${1:-}"
shift || true
case "$subcmd" in
validate)
local config_path=""
local release="false"
# Parse arguments
while [[ $# -gt 0 ]]; do
case "$1" in
--release)
release="true"
shift
;;
-h|--help)
config_usage
return 0
;;
-*)
die "Unknown option: $1"
;;
*)
if [[ -z "$config_path" ]]; then
config_path="$1"
else
die "Too many arguments"
fi
shift
;;
esac
done
# Determine config path
if [[ -z "$config_path" ]]; then
# Try default path first
local default_config
default_config="$(get_default_config_path)"
if [[ -f "$default_config" ]]; then
config_path="$default_config"
info "Using installed config: $config_path"
else
# Fall back to example in repo
local repo_root
repo_root="$(get_repo_root)"
local example_path="$repo_root/$EXAMPLE_CONFIG_NAME"
if [[ -f "$example_path" ]]; then
config_path="$example_path"
info "Using example config: $config_path"
else
die "No config file found. Specify a path or install shepherdd."
fi
fi
fi
# Resolve relative paths
if [[ ! "$config_path" = /* ]]; then
config_path="$(pwd)/$config_path"
fi
# Check file exists
if [[ ! -f "$config_path" ]]; then
die "Configuration file not found: $config_path"
fi
# Validate
info "Validating: $config_path"
echo ""
validate_config_file "$config_path" "$release"
;;
""|help|-h|--help)
config_usage
;;
*)
error "Unknown config command: $subcmd"
echo ""
config_usage
exit 1
;;
esac
}

View file

@ -21,6 +21,8 @@ source "$LIB_DIR/sway.sh"
source "$LIB_DIR/install.sh" source "$LIB_DIR/install.sh"
# shellcheck source=lib/harden.sh # shellcheck source=lib/harden.sh
source "$LIB_DIR/harden.sh" source "$LIB_DIR/harden.sh"
# shellcheck source=lib/config.sh
source "$LIB_DIR/config.sh"
# Version # Version
VERSION="0.1.0" VERSION="0.1.0"
@ -35,6 +37,7 @@ Usage: shepherd <command> [subcommand] [options]
Commands: Commands:
deps Manage system dependencies deps Manage system dependencies
build Build shepherd binaries build Build shepherd binaries
config Configuration validation
dev Development commands dev Development commands
install Install shepherd to the system install Install shepherd to the system
harden User hardening for kiosk mode harden User hardening for kiosk mode
@ -60,6 +63,10 @@ Hardening:
shepherd harden apply --user USER Apply kiosk restrictions shepherd harden apply --user USER Apply kiosk restrictions
shepherd harden revert --user USER Remove restrictions shepherd harden revert --user USER Remove restrictions
Configuration:
shepherd config validate Validate installed config
shepherd config validate <path> Validate a specific file
Options: Options:
-h, --help Show this help message -h, --help Show this help message
-V, --version Show version -V, --version Show version
@ -80,6 +87,9 @@ main() {
build) build)
build_main "$@" build_main "$@"
;; ;;
config)
config_main "$@"
;;
dev) dev)
local subcmd="${1:-}" local subcmd="${1:-}"
shift || true shift || true