fix: Improve validation errors

This commit is contained in:
Gerald Pinder 2024-12-11 19:40:12 -05:00
parent 6424bf3573
commit 3d0ae32734
99 changed files with 3773 additions and 425 deletions

347
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -102,6 +102,7 @@ tempfile.workspace = true
tokio = { workspace = true, optional = true }
bon.workspace = true
users.workspace = true
thiserror = "2.0.7"
[features]
# Top level features

View file

@ -1,6 +1,8 @@
use std::{borrow::Cow, path::PathBuf};
use blue_build_utils::syntax_highlighting::highlight_ser;
use blue_build_utils::{
constants::BLUE_BUILD_MODULE_IMAGE_REF, syntax_highlighting::highlight_ser,
};
use bon::Builder;
use colored::Colorize;
use indexmap::IndexMap;
@ -95,6 +97,15 @@ impl<'a> ModuleRequiredFields<'a> {
}
}
#[must_use]
pub fn get_module_image(&self) -> String {
format!(
"{BLUE_BUILD_MODULE_IMAGE_REF}/{}:{}",
self.module_type.typ(),
self.module_type.version().unwrap_or("latest")
)
}
#[must_use]
pub fn is_local_source(&self) -> bool {
self.source

View file

@ -5,7 +5,7 @@ use serde::{Deserialize, Deserializer, Serialize};
#[derive(Debug, Clone)]
pub struct ModuleTypeVersion<'scope> {
typ: Cow<'scope, str>,
version: Cow<'scope, str>,
version: Option<Cow<'scope, str>>,
}
impl ModuleTypeVersion<'_> {
@ -15,14 +15,21 @@ impl ModuleTypeVersion<'_> {
}
#[must_use]
pub fn version(&self) -> &str {
&self.version
pub fn version(&self) -> Option<&str> {
self.version.as_deref()
}
}
impl std::fmt::Display for ModuleTypeVersion<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}@{}", &self.typ, &self.version)
match self.version.as_deref() {
Some(version) => {
write!(f, "{}@{version}", &self.typ)
}
None => {
write!(f, "{}", &self.typ)
}
}
}
}
@ -31,12 +38,12 @@ impl<'scope> From<&'scope str> for ModuleTypeVersion<'scope> {
if let Some((typ, version)) = s.split_once('@') {
Self {
typ: Cow::Borrowed(typ),
version: Cow::Borrowed(version),
version: Some(Cow::Borrowed(version)),
}
} else {
Self {
typ: Cow::Borrowed(s),
version: Cow::Owned("latest".into()),
version: None,
}
}
}
@ -47,12 +54,12 @@ impl From<String> for ModuleTypeVersion<'_> {
if let Some((typ, version)) = s.split_once('@') {
Self {
typ: Cow::Owned(typ.to_owned()),
version: Cow::Owned(version.to_owned()),
version: Some(Cow::Owned(version.to_owned())),
}
} else {
Self {
typ: Cow::Owned(s),
version: Cow::Owned("latest".into()),
version: None,
}
}
}

View file

@ -7,16 +7,17 @@ use std::{
use blue_build_process_management::ASYNC_RUNTIME;
use blue_build_recipe::{FromFileList, ModuleExt, Recipe, StagesExt};
use blue_build_utils::constants::{
MODULE_STAGE_LIST_V1_SCHEMA_URL, MODULE_V1_SCHEMA_URL, RECIPE_V1_SCHEMA_URL,
STAGE_V1_SCHEMA_URL,
};
use bon::Builder;
use clap::Args;
use colored::Colorize;
use log::{debug, info, trace};
use miette::{bail, miette, Context, IntoDiagnostic, Report};
use rayon::prelude::*;
use schema_validator::{
SchemaValidator, MODULE_STAGE_LIST_V1_SCHEMA_URL, MODULE_V1_SCHEMA_URL, RECIPE_V1_SCHEMA_URL,
STAGE_V1_SCHEMA_URL,
};
use schema_validator::SchemaValidator;
use serde::de::DeserializeOwned;
use serde_json::Value;
@ -97,11 +98,21 @@ impl BlueBuildCommand for ValidateCommand {
impl ValidateCommand {
async fn setup_validators(&mut self) -> Result<(), Report> {
let (rv, sv, mv, mslv) = tokio::try_join!(
SchemaValidator::builder().url(RECIPE_V1_SCHEMA_URL).build(),
SchemaValidator::builder().url(STAGE_V1_SCHEMA_URL).build(),
SchemaValidator::builder().url(MODULE_V1_SCHEMA_URL).build(),
SchemaValidator::builder()
.url(RECIPE_V1_SCHEMA_URL)
.all_errors(self.all_errors)
.build(),
SchemaValidator::builder()
.url(STAGE_V1_SCHEMA_URL)
.all_errors(self.all_errors)
.build(),
SchemaValidator::builder()
.url(MODULE_V1_SCHEMA_URL)
.all_errors(self.all_errors)
.build(),
SchemaValidator::builder()
.url(MODULE_STAGE_LIST_V1_SCHEMA_URL)
.all_errors(self.all_errors)
.build(),
)?;
self.recipe_validator = Some(rv);
@ -149,15 +160,12 @@ impl ValidateCommand {
if instance.get(DF::LIST_KEY).is_some() {
debug!("{path_display} is a list file");
let err = match self
let err = self
.module_stage_list_validator
.as_ref()
.unwrap()
.process_validation(path, file_str.clone(), self.all_errors)
{
Err(e) => return vec![e],
Ok(e) => e,
};
.process_validation(path, file_str.clone())
.err();
err.map_or_else(
|| {
@ -195,13 +203,13 @@ impl ValidateCommand {
},
)
},
|err| vec![err],
|err| vec![err.into()],
)
} else {
debug!("{path_display} is a single file file");
single_validator
.process_validation(path, file_str, self.all_errors)
.map_or_else(|e| vec![e], |e| e.map_or_else(Vec::new, |e| vec![e]))
.process_validation(path, file_str)
.map_or_else(|e| vec![e.into()], |()| Vec::new())
}
}
Err(e) => vec![e],
@ -221,11 +229,11 @@ impl ValidateCommand {
let schema_validator = self.recipe_validator.as_ref().unwrap();
let err = schema_validator
.process_validation(&self.recipe, recipe_str.clone(), self.all_errors)
.map_err(err_vec)?;
.process_validation(&self.recipe, recipe_str.clone())
.err();
if let Some(err) = err {
Err(vec![err])
Err(vec![err.into()])
} else {
let recipe: Recipe = serde_yaml::from_str(&recipe_str)
.into_diagnostic()

View file

@ -35,6 +35,12 @@ impl From<&JsonLocation> for Location {
}
}
impl std::fmt::Display for Location {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl TryFrom<&str> for Location {
type Error = miette::Report;

View file

@ -1,54 +1,62 @@
use std::{
borrow::Cow,
collections::HashSet,
path::Path,
sync::{Arc, LazyLock},
};
use blue_build_process_management::ASYNC_RUNTIME;
use blue_build_recipe::ModuleTypeVersion;
use bon::bon;
use cached::proc_macro::cached;
use colored::Colorize;
use indexmap::IndexMap;
use jsonschema::{
output::Output, BasicOutput, ErrorIterator, Retrieve, Uri, ValidationError, Validator,
};
use log::{debug, trace};
use miette::{bail, miette, Context, IntoDiagnostic, LabeledSpan, NamedSource, Report, Result};
use jsonschema::{BasicOutput, Retrieve, Uri, ValidationError, Validator};
use miette::{Context, IntoDiagnostic, LabeledSpan, NamedSource};
use regex::Regex;
use serde_json::Value;
use super::{location::Location, yaml_span::YamlSpan};
pub const BASE_SCHEMA_URL: &str = "https://schema.blue-build.org";
pub const RECIPE_V1_SCHEMA_URL: &str = "https://schema.blue-build.org/recipe-v1.json";
pub const STAGE_V1_SCHEMA_URL: &str = "https://schema.blue-build.org/stage-v1.json";
pub const MODULE_V1_SCHEMA_URL: &str = "https://schema.blue-build.org/module-v1.json";
pub const MODULE_STAGE_LIST_V1_SCHEMA_URL: &str =
"https://schema.blue-build.org/module-stage-list-v1.json";
#[cfg(test)]
use std::eprintln as trace;
#[cfg(test)]
use std::eprintln as warn;
#[cfg(not(test))]
use log::{trace, warn};
mod error;
pub use error::*;
#[derive(Debug, Clone)]
pub struct SchemaValidator {
schema: Arc<Value>,
validator: Arc<Validator>,
url: &'static str,
all_errors: bool,
}
#[bon]
impl SchemaValidator {
#[builder]
pub async fn new(url: &'static str) -> Result<Self, Report> {
pub async fn new(
/// The URL of the schema to validate against
url: &'static str,
/// Produce all errors found
#[builder(default)]
all_errors: bool,
) -> Result<Self, SchemaValidateBuilderError> {
tokio::spawn(async move {
let schema: Arc<Value> = Arc::new(
reqwest::get(url)
.await
.into_diagnostic()
.with_context(|| format!("Failed to get schema at {url}"))?
.json()
.await
.into_diagnostic()
.with_context(|| format!("Failed to get json for schema {url}"))?,
);
let schema: Value = {
#[cfg(not(test))]
{
reqwest::get(url).await?.json().await?
}
#[cfg(test)]
{
serde_json::from_slice(std::fs::read_to_string(url)?.as_bytes())?
}
};
let validator = Arc::new(
tokio::task::spawn_blocking({
let schema = schema.clone();
@ -56,8 +64,6 @@ impl SchemaValidator {
jsonschema::options()
.with_retriever(ModuleSchemaRetriever)
.build(&schema)
.into_diagnostic()
.with_context(|| format!("Failed to build validator for schema {url}"))
}
})
.await
@ -65,181 +71,231 @@ impl SchemaValidator {
);
Ok(Self {
schema,
validator,
url,
all_errors,
})
})
.await
.expect("Should join task")
}
pub fn apply<'a, 'b>(&'a self, value: &'b Value) -> Output<'a, 'b> {
self.validator.apply(value)
}
pub fn iter_errors<'a>(&'a self, value: &'a Value) -> ErrorIterator<'a> {
self.validator.iter_errors(value)
}
pub fn schema(&self) -> Arc<Value> {
self.schema.clone()
}
pub const fn url(&self) -> &'static str {
self.url
}
pub fn process_validation(
pub fn process_validation<P>(
&self,
path: &Path,
path: P,
file: Arc<String>,
all_errors: bool,
) -> Result<Option<Report>> {
let recipe_path_display = path.display().to_string().bold().italic();
) -> Result<(), SchemaValidateError>
where
P: AsRef<Path>,
{
let path = path.as_ref();
let spans = self.get_spans(&file, path)?;
self.spans_to_report(spans, file, path)
}
fn get_spans(
&self,
file: &Arc<String>,
path: &Path,
) -> Result<Vec<LabeledSpan>, SchemaValidateError> {
let recipe_path_display = path.display().to_string().bold().italic();
let spanner = YamlSpan::builder().file(file.clone()).build()?;
let instance: Value = serde_yaml::from_str(&file)
.into_diagnostic()
.with_context(|| format!("Failed to deserialize recipe {recipe_path_display}"))?;
let instance: Value = serde_yaml::from_str(file)
.map_err(|e| SchemaValidateError::SerdeYaml(e, path.to_path_buf()))?;
trace!("{recipe_path_display}:\n{file}");
Ok(if all_errors {
self.process_basic_output(self.apply(&instance).basic(), file, &spanner, path)
Ok(if self.all_errors {
process_basic_output(self.validator.apply(&instance).basic(), &spanner)
} else {
self.process_err(self.iter_errors(&instance), path, file, &spanner)
process_err(self.validator.iter_errors(&instance), &spanner)
})
}
fn process_basic_output(
fn spans_to_report(
&self,
out: BasicOutput<'_>,
labels: Vec<LabeledSpan>,
file: Arc<String>,
spanner: &YamlSpan,
path: &Path,
) -> Option<Report> {
match out {
BasicOutput::Valid(_) => None,
BasicOutput::Invalid(errors) => {
let mut collection: IndexMap<Location, Vec<String>> = IndexMap::new();
let errors = {
let mut e = errors.into_iter().collect::<Vec<_>>();
e.sort_by(|e1, e2| {
e1.instance_location()
.as_str()
.cmp(e2.instance_location().as_str())
});
e
};
let errors: Vec<(Location, String)> = {
let e = errors
.into_iter()
.map(|e| {
(
Location::from(e.instance_location()),
remove_json(&e.error_description().to_string()).to_string(),
)
})
.collect::<HashSet<_>>();
let mut e = e.into_iter().collect::<Vec<_>>();
e.sort_by(|e1, e2| e1.0.as_str().cmp(e2.0.as_str()));
e
};
for (instance_path, err) in errors {
collection
.entry(instance_path)
.and_modify(|errs| {
errs.push(format!("- {}", err.bold().red()));
})
.or_insert_with(|| vec![format!("- {}", err.bold().red())]);
}
let spans = collection
.into_iter()
.map(|(key, value)| {
LabeledSpan::new_with_span(
Some(value.join("\n")),
spanner.get_span(&key).unwrap(),
)
})
.collect::<Vec<_>>();
Some(
miette!(
labels = spans,
help = format!(
"Try adding these lines to the top of your file:\n{}\n{}",
"---".bright_green(),
format!("# yaml-language-server: $schema={}", self.url).bright_green(),
),
"{} error{} encountered",
spans.len().to_string().red(),
if spans.len() == 1 { "" } else { "s" }
)
.with_source_code(
NamedSource::new(path.display().to_string(), file).with_language("yaml"),
),
)
}
}
}
fn process_err<'a, I>(
&self,
errors: I,
path: &Path,
file: Arc<String>,
spanner: &YamlSpan,
) -> Option<Report>
where
I: Iterator<Item = ValidationError<'a>>,
{
let spans = errors
.map(|err| {
LabeledSpan::new_primary_with_span(
Some(remove_json(&err.to_string()).bold().red().to_string()),
spanner
.get_span(&Location::from(err.instance_path))
.unwrap(),
)
})
.collect::<Vec<_>>();
if spans.is_empty() {
None
) -> Result<(), SchemaValidateError> {
if labels.is_empty() {
Ok(())
} else {
Some(
miette!(
labels = spans,
help = format!(
"Try adding these lines to the top of your file:\n{}\n{}",
"---".bright_green(),
format!("# yaml-language-server: $schema={}", self.url).bright_green(),
),
"{} error{} encountered",
spans.len().to_string().red(),
if spans.len() == 1 { "" } else { "s" }
)
.with_source_code(
NamedSource::new(path.display().to_string(), file).with_language("yaml"),
Err(SchemaValidateError::YamlValidate {
src: NamedSource::new(path.display().to_string(), file).with_language("yaml"),
labels,
help: format!(
"Try adding these lines to the top of your file for editor validation highlights:\n{}\n{}",
"---".bright_green(),
format!("# yaml-language-server: $schema={}", self.url).bright_green(),
),
)
})
}
}
}
fn remove_json(string: &str) -> Cow<'_, str> {
fn process_basic_output(out: BasicOutput<'_>, spanner: &YamlSpan) -> Vec<LabeledSpan> {
match out {
BasicOutput::Valid(_) => Vec::new(),
BasicOutput::Invalid(errors) => {
let errors = {
let mut e = errors.into_iter().collect::<Vec<_>>();
e.sort_by(|e1, e2| {
e1.instance_location()
.as_str()
.cmp(e2.instance_location().as_str())
});
e
};
let errors: Vec<(Location, String)> = {
let e = errors
.into_iter()
.map(|e| {
(
Location::from(e.instance_location()),
remove_json(&e.error_description().to_string()),
)
})
.collect::<HashSet<_>>();
let mut e = e.into_iter().collect::<Vec<_>>();
e.sort_by(|e1, e2| e1.0.as_str().cmp(e2.0.as_str()));
e
};
let mut collection: IndexMap<Location, Vec<String>> = IndexMap::new();
for (instance_path, err) in errors {
collection
.entry(instance_path)
.and_modify(|errs| {
errs.push(format!("- {}", err.bold().red()));
})
.or_insert_with(|| vec![format!("- {}", err.bold().red())]);
}
collection
.into_iter()
.map(|(key, value)| {
LabeledSpan::new_with_span(
Some(value.into_iter().collect::<Vec<_>>().join("\n")),
spanner.get_span(&key).unwrap(),
)
})
.collect()
}
}
}
fn process_err<'a, I>(errors: I, spanner: &YamlSpan) -> Vec<LabeledSpan>
where
I: Iterator<Item = ValidationError<'a>>,
{
errors
.flat_map(|err| process_anyof_error(&err).unwrap_or_else(|| vec![err]))
.map(|err| {
let masked_err = err.masked();
LabeledSpan::new_primary_with_span(
Some(masked_err.to_string().bold().red().to_string()),
spanner
.get_span(&Location::from(err.instance_path))
.unwrap(),
)
})
.collect()
}
fn process_anyof_error(err: &ValidationError<'_>) -> Option<Vec<ValidationError<'static>>> {
trace!("to_processed_module_err({err:#?})");
let ValidationError {
instance,
kind,
instance_path,
schema_path: _,
} = err;
let mut path_iter = instance_path.into_iter();
let uri = match (kind, path_iter.next_back(), path_iter.next_back()) {
(
jsonschema::error::ValidationErrorKind::AnyOf,
Some(jsonschema::paths::LocationSegment::Index(_)),
Some(jsonschema::paths::LocationSegment::Property("modules")),
) => {
trace!("FOUND MODULE ANYOF ERROR at {instance_path}");
if instance.get("source").is_some() {
Uri::parse("json-schema:///module-custom-v1.json".to_string()).ok()?
} else if instance.get("from-file").is_some() {
Uri::parse("json-schema:///import-v1.json".to_string()).ok()?
} else {
let typ = instance.get("type").and_then(Value::as_str)?;
let typ = ModuleTypeVersion::from(typ);
trace!("Module type: {typ}");
Uri::parse(format!(
"json-schema:///modules/{}-{}.json",
typ.typ(),
typ.version().unwrap_or("latest")
))
.ok()?
}
}
(
jsonschema::error::ValidationErrorKind::AnyOf,
Some(jsonschema::paths::LocationSegment::Index(_)),
Some(jsonschema::paths::LocationSegment::Property("stages")),
) => {
trace!("FOUND STAGE ANYOF ERROR at {instance_path}");
if instance.get("from-file").is_some() {
Uri::parse("json-schema:///import-v1.json".to_string()).ok()?
} else {
Uri::parse("json-schema:///stage-v1.json".to_string()).ok()?
}
}
_ => return None,
};
trace!("Schema URI: {uri}");
let schema = ASYNC_RUNTIME.block_on(cache_retrieve(&uri.borrow())).ok()?;
let validator = jsonschema::options()
.with_retriever(ModuleSchemaRetriever)
.build(&schema)
.inspect_err(|e| warn!("{e:#?}"))
.ok()?;
Some(
validator
.iter_errors(instance)
.flat_map(|err| process_anyof_error(&err).unwrap_or_else(|| vec![err]))
.map(|err| {
let mut err = err.to_owned();
err.instance_path = instance_path
.into_iter()
.chain(&err.instance_path)
.collect();
err
})
.inspect(|errs| {
trace!("From error: {err:#?}\nTo error list: {errs:#?}");
})
.collect(),
)
}
fn remove_json<S>(string: &S) -> String
where
S: ToString,
{
static REGEX_OBJECT: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^\{.*\}\s(.*)$").unwrap());
static REGEX_ARRAY: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^\[.*\]\s(.*)$").unwrap());
let string = string.trim();
let string = string.to_string();
if REGEX_OBJECT.is_match(string) {
REGEX_OBJECT.replace_all(string, "$1")
} else if REGEX_ARRAY.is_match(string) {
REGEX_ARRAY.replace_all(string, "$1")
if REGEX_OBJECT.is_match(&string) {
REGEX_OBJECT.replace_all(string.trim(), "$1").into_owned()
} else if REGEX_ARRAY.is_match(&string) {
REGEX_ARRAY.replace_all(string.trim(), "$1").into_owned()
} else {
Cow::Borrowed(string)
string
}
}
@ -259,26 +315,283 @@ async fn cache_retrieve(uri: &Uri<&str>) -> miette::Result<Value> {
let scheme = uri.scheme();
let path = uri.path();
let uri = match scheme.as_str() {
"json-schema" => {
format!("{BASE_SCHEMA_URL}{path}")
}
"https" => uri.to_string(),
scheme => bail!("Unknown scheme {scheme}"),
};
#[cfg(not(test))]
{
const BASE_SCHEMA_URL: &str = "https://schema.blue-build.org";
debug!("Retrieving schema from {}", uri.bold().italic());
tokio::spawn(async move {
reqwest::get(&uri)
.await
.into_diagnostic()
.with_context(|| format!("Failed to retrieve schema from {uri}"))?
.json()
.await
.into_diagnostic()
.with_context(|| format!("Failed to parse json from {uri}"))
.inspect(|value| trace!("{}:\n{value}", uri.bold().italic()))
})
.await
.expect("Should join task")
let uri = match scheme.as_str() {
"json-schema" => {
format!("{BASE_SCHEMA_URL}{path}")
}
"https" => uri.to_string(),
scheme => miette::bail!("Unknown scheme {scheme}"),
};
log::debug!("Retrieving schema from {}", uri.bold().italic());
tokio::spawn(async move {
reqwest::get(&uri)
.await
.into_diagnostic()
.with_context(|| format!("Failed to retrieve schema from {uri}"))?
.json()
.await
.into_diagnostic()
.with_context(|| format!("Failed to parse json from {uri}"))
.inspect(|value| trace!("{}:\n{value}", uri.bold().italic()))
})
.await
.expect("Should join task")
}
#[cfg(test)]
{
let uri = match scheme.as_str() {
"json-schema" | "https" => {
format!("test-files/schema/{path}")
}
_ => unreachable!(),
};
serde_json::from_slice(
std::fs::read_to_string(uri)
.into_diagnostic()
.context("Failed retrieving sub-schema")?
.as_bytes(),
)
.into_diagnostic()
.context("Failed deserializing sub-schema")
}
}
#[cfg(test)]
mod test {
use blue_build_process_management::ASYNC_RUNTIME;
use rstest::rstest;
use super::*;
#[rstest]
#[case::recipe(
"test-files/recipes/recipe-pass.yml",
"test-files/schema/recipe-v1.json"
)]
#[case::stage("test-files/recipes/stage-pass.yml", "test-files/schema/stage-v1.json")]
#[case::stage_list(
"test-files/recipes/stage-list-pass.yml",
"test-files/schema/stage-list-v1.json"
)]
#[case::module_list(
"test-files/recipes/module-list-pass.yml",
"test-files/schema/module-list-v1.json"
)]
#[case::akmods(
"test-files/recipes/modules/akmods-pass.yml",
"test-files/schema/modules/akmods-v1.json"
)]
#[case::bling(
"test-files/recipes/modules/bling-pass.yml",
"test-files/schema/modules/bling-v1.json"
)]
#[case::brew(
"test-files/recipes/modules/brew-pass.yml",
"test-files/schema/modules/brew-v1.json"
)]
#[case::chezmoi(
"test-files/recipes/modules/chezmoi-pass.yml",
"test-files/schema/modules/chezmoi-v1.json"
)]
#[case::containerfile(
"test-files/recipes/modules/containerfile-pass.yml",
"test-files/schema/modules/containerfile-v1.json"
)]
#[case::copy(
"test-files/recipes/modules/copy-pass.yml",
"test-files/schema/modules/copy-v1.json"
)]
#[case::default_flatpaks(
"test-files/recipes/modules/default-flatpaks-pass.yml",
"test-files/schema/modules/default-flatpaks-v1.json"
)]
#[case::files(
"test-files/recipes/modules/files-pass.yml",
"test-files/schema/modules/files-v1.json"
)]
#[case::fonts(
"test-files/recipes/modules/fonts-pass.yml",
"test-files/schema/modules/fonts-v1.json"
)]
#[case::gnome_extensions(
"test-files/recipes/modules/gnome-extensions-pass.yml",
"test-files/schema/modules/gnome-extensions-v1.json"
)]
#[case::gschema_overrides(
"test-files/recipes/modules/gschema-overrides-pass.yml",
"test-files/schema/modules/gschema-overrides-v1.json"
)]
#[case::justfiles(
"test-files/recipes/modules/justfiles-pass.yml",
"test-files/schema/modules/justfiles-v1.json"
)]
#[case::rpm_ostree(
"test-files/recipes/modules/rpm-ostree-pass.yml",
"test-files/schema/modules/rpm-ostree-v1.json"
)]
#[case::script(
"test-files/recipes/modules/script-pass.yml",
"test-files/schema/modules/script-v1.json"
)]
#[case::signing(
"test-files/recipes/modules/signing-pass.yml",
"test-files/schema/modules/signing-v1.json"
)]
#[case::systemd(
"test-files/recipes/modules/systemd-pass.yml",
"test-files/schema/modules/systemd-v1.json"
)]
#[case::yafti(
"test-files/recipes/modules/yafti-pass.yml",
"test-files/schema/modules/yafti-v1.json"
)]
fn pass_validation(#[case] file: &str, #[case] schema: &'static str) {
let validator = ASYNC_RUNTIME
.block_on(SchemaValidator::builder().url(schema).build())
.unwrap();
let file_contents = Arc::new(std::fs::read_to_string(file).unwrap());
let result = validator.process_validation(file, file_contents);
dbg!(&result);
assert!(result.is_ok());
}
#[rstest]
#[case::recipe(
"test-files/recipes/recipe-fail.yml",
"test-files/schema/recipe-v1.json",
6
)]
#[case::stage(
"test-files/recipes/stage-fail.yml",
"test-files/schema/stage-v1.json",
2
)]
#[case::stage_list(
"test-files/recipes/stage-list-fail.yml",
"test-files/schema/stage-list-v1.json",
2
)]
#[case::module_list(
"test-files/recipes/module-list-fail.yml",
"test-files/schema/module-list-v1.json",
35
)]
#[case::akmods(
"test-files/recipes/modules/akmods-fail.yml",
"test-files/schema/modules/akmods-v1.json",
1
)]
#[case::bling(
"test-files/recipes/modules/bling-fail.yml",
"test-files/schema/modules/bling-v1.json",
1
)]
#[case::brew(
"test-files/recipes/modules/brew-fail.yml",
"test-files/schema/modules/brew-v1.json",
3
)]
#[case::chezmoi(
"test-files/recipes/modules/chezmoi-fail.yml",
"test-files/schema/modules/chezmoi-v1.json",
3
)]
#[case::containerfile(
"test-files/recipes/modules/containerfile-fail.yml",
"test-files/schema/modules/containerfile-v1.json",
2
)]
#[case::copy(
"test-files/recipes/modules/copy-fail.yml",
"test-files/schema/modules/copy-v1.json",
2
)]
#[case::default_flatpaks(
"test-files/recipes/modules/default-flatpaks-fail.yml",
"test-files/schema/modules/default-flatpaks-v1.json",
4
)]
#[case::files(
"test-files/recipes/modules/files-fail.yml",
"test-files/schema/modules/files-v1.json",
1
)]
#[case::fonts(
"test-files/recipes/modules/fonts-fail.yml",
"test-files/schema/modules/fonts-v1.json",
2
)]
#[case::gnome_extensions(
"test-files/recipes/modules/gnome-extensions-fail.yml",
"test-files/schema/modules/gnome-extensions-v1.json",
2
)]
#[case::gschema_overrides(
"test-files/recipes/modules/gschema-overrides-fail.yml",
"test-files/schema/modules/gschema-overrides-v1.json",
1
)]
#[case::justfiles(
"test-files/recipes/modules/justfiles-fail.yml",
"test-files/schema/modules/justfiles-v1.json",
2
)]
#[case::rpm_ostree(
"test-files/recipes/modules/rpm-ostree-fail.yml",
"test-files/schema/modules/rpm-ostree-v1.json",
3
)]
#[case::script(
"test-files/recipes/modules/script-fail.yml",
"test-files/schema/modules/script-v1.json",
2
)]
#[case::signing(
"test-files/recipes/modules/signing-fail.yml",
"test-files/schema/modules/signing-v1.json",
1
)]
#[case::systemd(
"test-files/recipes/modules/systemd-fail.yml",
"test-files/schema/modules/systemd-v1.json",
4
)]
#[case::yafti(
"test-files/recipes/modules/yafti-fail.yml",
"test-files/schema/modules/yafti-v1.json",
1
)]
fn fail_validation(#[case] file: &str, #[case] schema: &'static str, #[case] err_count: usize) {
let validator = ASYNC_RUNTIME
.block_on(SchemaValidator::builder().url(schema).build())
.unwrap();
let file_contents = Arc::new(std::fs::read_to_string(file).unwrap());
let result = validator.process_validation(file, file_contents);
dbg!(&result);
assert!(result.is_err());
let SchemaValidateError::YamlValidate {
src: _,
labels,
help: _,
} = result.unwrap_err()
else {
panic!("Wrong error");
};
assert_eq!(labels.len(), err_count);
}
}

View file

@ -0,0 +1,55 @@
use std::{path::PathBuf, sync::Arc};
use colored::Colorize;
use miette::{Diagnostic, LabeledSpan, NamedSource};
use thiserror::Error;
use crate::commands::validate::yaml_span::YamlSpanError;
#[derive(Error, Diagnostic, Debug)]
pub enum SchemaValidateBuilderError {
#[error(transparent)]
#[cfg(not(test))]
#[diagnostic()]
Reqwest(#[from] reqwest::Error),
#[error(transparent)]
SerdeJson(#[from] serde_json::Error),
#[error(transparent)]
#[cfg(test)]
#[diagnostic()]
Fs(#[from] std::io::Error),
#[error(transparent)]
#[diagnostic()]
JsonSchemaBuild(#[from] jsonschema::ValidationError<'static>),
}
#[derive(Error, Diagnostic, Debug)]
pub enum SchemaValidateError {
#[error("Failed to deserialize file {}", .1.display().to_string().bold().italic())]
#[diagnostic()]
SerdeYaml(serde_yaml::Error, PathBuf),
#[error(
"{} error{} encountered",
.labels.len().to_string().red(),
if .labels.len() == 1 { "" } else { "s" }
)]
#[diagnostic()]
YamlValidate {
#[source_code]
src: NamedSource<Arc<String>>,
#[label(collection)]
labels: Vec<LabeledSpan>,
#[help]
help: String,
},
#[error(transparent)]
#[diagnostic(transparent)]
YamlSpan(#[from] YamlSpanError),
}

View file

@ -2,7 +2,7 @@ use std::sync::Arc;
use bon::bon;
use jsonschema::paths::LocationSegment;
use miette::{bail, Context, IntoDiagnostic, Result, SourceSpan};
use miette::SourceSpan;
use yaml_rust2::{
parser::{MarkedEventReceiver, Parser},
scanner::Marker,
@ -10,12 +10,18 @@ use yaml_rust2::{
};
#[cfg(not(test))]
use log::trace;
use log::{debug, trace};
#[cfg(test)]
use std::eprintln as trace;
#[cfg(test)]
use std::eprintln as debug;
use super::location::Location;
mod error;
pub use error::*;
#[derive(Debug)]
pub struct YamlSpan {
file: Arc<String>,
@ -25,7 +31,7 @@ pub struct YamlSpan {
#[bon]
impl YamlSpan {
#[builder]
pub fn new(file: Arc<String>) -> Result<Self> {
pub fn new(file: Arc<String>) -> Result<Self, YamlSpanError> {
let mut ys = Self {
file,
event_markers: Vec::default(),
@ -34,14 +40,12 @@ impl YamlSpan {
let file = ys.file.clone();
let mut parser = Parser::new_from_str(&file);
parser
.load(&mut ys, false)
.into_diagnostic()
.context("Failed to parse file")?;
parser.load(&mut ys, false)?;
Ok(ys)
}
pub fn get_span(&self, path: &Location) -> Result<SourceSpan> {
pub fn get_span(&self, path: &Location) -> Result<SourceSpan, YamlSpanError> {
debug!("Searching {path}");
let mut event_iter = self.event_markers.iter();
let mut path_iter = path.into_iter();
@ -79,7 +83,7 @@ where
Self { events, path }
}
pub fn get_span(&mut self) -> Result<SourceSpan> {
pub fn get_span(&mut self) -> Result<SourceSpan, YamlSpanError> {
let mut stream_start = false;
let mut document_start = false;
@ -108,12 +112,12 @@ where
Event::MappingStart(_, _) if stream_start && document_start => {
break self.key(key)?.into();
}
event => bail!("Failed to read event: {event:?}"),
event => return Err(YamlSpanError::UnexpectedEvent(event.to_owned())),
}
})
}
fn key(&mut self, expected_key: LocationSegment<'_>) -> Result<(usize, usize)> {
fn key(&mut self, expected_key: LocationSegment<'_>) -> Result<(usize, usize), YamlSpanError> {
trace!("Looking for location {expected_key:?}");
loop {
@ -131,10 +135,20 @@ where
if key != expected_key =>
{
trace!("Non-matching key '{key}'");
continue;
let (event, marker) = self.events.next().unwrap();
match event {
Event::Scalar(_, _, _, _) => continue,
Event::MappingStart(_, _) => self.skip_mapping(marker.index()),
Event::SequenceStart(_, _) => self.skip_sequence(marker.index()),
_ => unreachable!("{event:?}"),
};
}
(Event::Scalar(key, _, _, _), LocationSegment::Index(index)) => {
bail!("Encountered key {key} when looking for index {index}")
return Err(YamlSpanError::ExpectIndexFoundKey {
key: key.to_owned(),
index,
})
}
(Event::SequenceStart(_, _), LocationSegment::Index(index)) => {
break self.sequence(index, 0);
@ -146,7 +160,7 @@ where
self.skip_mapping(marker.index());
}
(Event::MappingEnd, _) => {
bail!("Reached end of map an haven't found key {expected_key}")
return Err(YamlSpanError::EndOfMapNoKey(expected_key.to_string()))
}
event => unreachable!("{event:?}"),
}
@ -193,13 +207,17 @@ where
}
}
fn sequence(&mut self, index: usize, curr_index: usize) -> Result<(usize, usize)> {
fn sequence(
&mut self,
index: usize,
curr_index: usize,
) -> Result<(usize, usize), YamlSpanError> {
let (event, marker) = self.events.next().expect("Need events");
trace!("{event:?} {marker:?}");
trace!("index: {index}, curr_index: {curr_index}");
Ok(match event {
Event::SequenceEnd => bail!("Reached end of sequence before reaching index {index}"),
Event::SequenceEnd => return Err(YamlSpanError::EndOfSequenceNoIndex(index)),
Event::Scalar(_, _, _, _) if index > curr_index => {
self.sequence(index, curr_index + 1)?
}
@ -236,15 +254,19 @@ where
})
}
fn value(&mut self) -> Result<(usize, usize)> {
fn value(&mut self) -> Result<(usize, usize), YamlSpanError> {
let (event, marker) = self.events.next().unwrap();
trace!("{event:?} {marker:?}");
let key = self.path.next();
trace!("{key:?}");
Ok(match (event, key) {
(Event::Scalar(value, _, _, _), None) => (marker.index(), value.len()),
(Event::Scalar(value, _, _, _), Some(segment)) => {
bail!("Encountered scalar value {value} when looking for {segment}")
return Err(YamlSpanError::UnexpectedScalar {
value: value.to_owned(),
segment: segment.to_string(),
})
}
(Event::MappingStart(_, _), Some(LocationSegment::Property(key))) => {
self.key(LocationSegment::Property(key))?

View file

@ -0,0 +1,30 @@
use miette::Diagnostic;
use thiserror::Error;
use yaml_rust2::{Event, ScanError};
#[derive(Error, Diagnostic, Debug)]
pub enum YamlSpanError {
#[error("Failed to parse file: {0}")]
#[diagnostic()]
ScanError(#[from] ScanError),
#[error("Failed to read event: {0:?}")]
#[diagnostic()]
UnexpectedEvent(Event),
#[error("Encountered key {key} when looking for index {index}")]
#[diagnostic()]
ExpectIndexFoundKey { key: String, index: usize },
#[error("Reached end of map an haven't found key {0}")]
#[diagnostic()]
EndOfMapNoKey(String),
#[error("Reached end of sequence before reaching index {0}")]
#[diagnostic()]
EndOfSequenceNoIndex(usize),
#[error("Encountered scalar value {value} when looking for {segment}")]
#[diagnostic()]
UnexpectedScalar { value: String, segment: String },
}

View file

@ -1,16 +1,21 @@
import "@typespec/json-schema";
using TypeSpec.JsonSchema;
@jsonSchema("/modules/containerfile.json")
model ContainerfileModule {
/** The containerfile module is a tool for adding custom Containerfile instructions for custom image builds.
* https://blue-build.org/reference/modules/containerfile/
*/
type: "containerfile";
/** Lines to directly insert into the generated Containerfile. */
snippets?: Array<string>;
/** Names of directories in ./containerfiles/ containing each a Containerfile to insert into the generated Containerfile. */
containerfiles?: Array<string>;
@jsonSchema("/modules/containerfile-latest.json")
model ContainerfileModuleLatest {
...ContainerfileModuleV1;
}
@jsonSchema("/modules/containerfile-v1.json")
model ContainerfileModuleV1 {
/** The containerfile module is a tool for adding custom Containerfile instructions for custom image builds.
* https://blue-build.org/reference/modules/containerfile/
*/
type: "containerfile" | "containerfile@latest" | "containerfile@v1";
/** Lines to directly insert into the generated Containerfile. */
snippets?: Array<string>;
/** Names of directories in ./containerfiles/ containing each a Containerfile to insert into the generated Containerfile. */
containerfiles?: Array<string>;
}

View file

@ -1,12 +1,17 @@
import "@typespec/json-schema";
using TypeSpec.JsonSchema;
@jsonSchema("/modules/copy.json")
model CopyModule {
@jsonSchema("/modules/copy-latest.json")
model CopyModuleLatest {
...CopyModuleV1;
}
@jsonSchema("/modules/copy-v1.json")
model CopyModuleV1 {
/** The copy module is a short-hand method of adding a COPY instruction into the Containerfile.
* https://blue-build.org/reference/modules/copy/
*/
type: "copy";
type: "copy" | "copy@latest" | "copy@v1";
/** Equivalent to the --from property in a COPY statement, use to specify an image to copy from.
* By default, the COPY source is the build environment's file tree.

View file

@ -22,7 +22,7 @@ RUN \
{%- else if module.is_local_source() %}
--mount=type=bind,from=stage-modules,src=/modules,dst=/tmp/modules,rw \
{%- else %}
--mount=type=bind,from={{ blue_build_utils::constants::BLUE_BUILD_MODULE_IMAGE_REF }}/{{ module.module_type.typ() }}:{{ module.module_type.version() }},src=/modules,dst=/tmp/modules,rw \
--mount=type=bind,from={{ module.get_module_image() }},src=/modules,dst=/tmp/modules,rw \
{%- endif %}
{%- if module.module_type.typ() == "akmods" %}
--mount=type=bind,from=stage-akmods-{{ module.generate_akmods_info(os_version).stage_name }},src=/rpms,dst=/tmp/rpms,rw \
@ -61,7 +61,7 @@ RUN \
{%- else if module.is_local_source() %}
--mount=type=bind,from=stage-modules,src=/modules,dst=/tmp/modules,rw \
{%- else %}
--mount=type=bind,from={{ blue_build_utils::constants::BLUE_BUILD_MODULE_IMAGE_REF }}/{{ module.module_type.typ() }}:{{ module.module_type.version() }},src=/modules,dst=/tmp/modules,rw \
--mount=type=bind,from={{ module.get_module_image() }},src=/modules,dst=/tmp/modules,rw \
{%- endif %}
--mount=type=bind,from={{ build_scripts_image }},src=/scripts/,dst=/tmp/scripts/ \
/tmp/scripts/run_module.sh '{{ module.module_type.typ() }}' '{{ module|json|safe }}'

View file

@ -0,0 +1,105 @@
---
# yaml-language-server: $schema=https://schema.blue-build.org/module-list-v1.json
modules:
- type: akmods
base: main
install: openrgb
- type: bling
install: rpmfusion
- type: brew
auto-update: true
update-interval: "6h"
auto-upgrade: "true"
update-wait-after-boot: "10min"
upgrade-interval: "8h"
upgrade-wait-after-boot: "30min"
nofile-limits: true
brew-analytics: "fasle"
install:
- test
- type: chezmoi
repository: 'test-repo.git'
branch: 'main'
all-users: "true"
run-every: "1d"
wait-after-boot: "5m"
disable-init: false
disable-update: "false"
file-conflict-policy: none
- type: containerfile
snippets: RUN echo "Hello!"
containerfiles: test
- type: copy
from: test-stage
src:
- /out/test
dest:
- /in/test
- type: default-flatpaks
notify: 'false'
system:
repo-url: https://dl.flathub.org/repo/flathub.flatpakrepo
repo-name:
- flathub
repo-title: test-user
install: test.org
remove:
- bad-test.org
user:
repo-url: https://dl.flathub.org/repo/flathub.flatpakrepo
repo-name: flathub
repo-title: test-user
install: test.org
remove:
- bad-test.org
- type: files
files:
source: test
destination: /usr
- type: fonts
fonts:
nerd-fonts: "JetbrainsMono"
google-fonts: "Test"
- type: gnome-extensions
install: test
uninstall: rmtest
- type: gschema-overrides
include: test
- type: justfiles
validate: 'true'
include: ./justfile
- type: rpm-ostree
repos:
- test.repo
keys: test.key
optfix:
- test
install:
- test
remove: rmtest
replace:
- replacetest
- type: script
snippets: rm -fr /*
scripts: test.sh
- type: signing
dir: /dir
- type: systemd
system:
enabled:
- test.service
disabled: disable-test.service
masked:
- masked-test.service
unmasked: unmasked-test.service
user:
enabled: test.service
disabled:
- disable-test.service
masked:
- test: masked-test.service
unmasked:
- unmasked-test.service
- type: yafti
custom-flatpaks:
- test.org

View file

@ -0,0 +1,120 @@
---
# yaml-language-server: $schema=https://schema.blue-build.org/module-list-v1.json
modules:
- type: akmods
base: main
install:
- openrgb
- type: bling
install:
- rpmfusion
- type: brew
auto-update: true
update-interval: "6h"
auto-upgrade: true
update-wait-after-boot: "10min"
upgrade-interval: "8h"
upgrade-wait-after-boot: "30min"
nofile-limits: true
brew-analytics: false
- type: chezmoi
repository: 'test-repo.git'
branch: 'main'
all-users: false
run-every: "1d"
wait-after-boot: "5m"
disable-init: false
disable-update: false
file-conflict-policy: replace
- type: containerfile
snippets:
- RUN echo "Hello!"
containerfiles:
- test
- type: copy
from: test-stage
src: /out/test
dest: /in/test
- type: default-flatpaks
notify: false
system:
repo-url: https://dl.flathub.org/repo/flathub.flatpakrepo
repo-name: flathub
repo-title: test-user
install:
- test.org
remove:
- bad-test.org
user:
repo-url: https://dl.flathub.org/repo/flathub.flatpakrepo
repo-name: flathub
repo-title: test-user
install:
- test.org
remove:
- bad-test.org
- type: files
files:
- source: test
destination: /usr
- type: fonts
fonts:
nerd-fonts:
- "JetbrainsMono"
google-fonts:
- "Test"
- type: gnome-extensions
install:
- test
uninstall:
- rmtest
- type: gschema-overrides
include:
- test
- type: justfiles
validate: true
include:
- ./justfile
- type: rpm-ostree
repos:
- test.repo
keys:
- test.key
optfix:
- test
install:
- test
remove:
- rmtest
replace:
- from-repo: updates
packages:
- replacetest
- type: script
snippets:
- rm -fr /*
scripts:
- test.sh
- type: signing
- type: systemd
system:
enabled:
- test.service
disabled:
- disable-test.service
masked:
- masked-test.service
unmasked:
- unmasked-test.service
user:
enabled:
- test.service
disabled:
- disable-test.service
masked:
- masked-test.service
unmasked:
- unmasked-test.service
- type: yafti
custom-flatpaks:
- PrettyTest: test.org

View file

@ -0,0 +1,5 @@
---
# yaml-language-server: $schema=https://schema.blue-build.org/modules/akmods.json
type: akmods
base: main
install: openrgb

View file

@ -0,0 +1,6 @@
---
# yaml-language-server: $schema=https://schema.blue-build.org/modules/akmods.json
type: akmods
base: main
install:
- openrgb

View file

@ -0,0 +1,4 @@
---
# yaml-language-server: $schema=https://schema.blue-build.org/modules/bling.json
type: bling
install: rpmfusion

View file

@ -0,0 +1,5 @@
---
# yaml-language-server: $schema=https://schema.blue-build.org/modules/bling.json
type: bling
install:
- rpmfusion

View file

@ -0,0 +1,13 @@
---
# yaml-language-server: $schema=https://schema.blue-build.org/modules/brew.json
type: brew
auto-update: true
update-interval: "6h"
auto-upgrade: "true"
update-wait-after-boot: "10min"
upgrade-interval: "8h"
upgrade-wait-after-boot: "30min"
nofile-limits: true
brew-analytics: "fasle"
install:
- test

View file

@ -0,0 +1,11 @@
---
# yaml-language-server: $schema=https://schema.blue-build.org/modules/brew.json
type: brew
auto-update: true
update-interval: "6h"
auto-upgrade: true
update-wait-after-boot: "10min"
upgrade-interval: "8h"
upgrade-wait-after-boot: "30min"
nofile-limits: true
brew-analytics: false

View file

@ -0,0 +1,11 @@
---
# yaml-language-server: $schema=https://schema.blue-build.org/modules/chezmoi.json
type: chezmoi
repository: 'test-repo.git'
branch: 'main'
all-users: "true"
run-every: "1d"
wait-after-boot: "5m"
disable-init: false
disable-update: "false"
file-conflict-policy: none

View file

@ -0,0 +1,11 @@
---
# yaml-language-server: $schema=https://schema.blue-build.org/modules/chezmoi.json
type: chezmoi
repository: 'test-repo.git'
branch: 'main'
all-users: false
run-every: "1d"
wait-after-boot: "5m"
disable-init: false
disable-update: false
file-conflict-policy: replace

View file

@ -0,0 +1,5 @@
---
# yaml-language-server: $schema=https://schema.blue-build.org/modules/containerfile.json
type: containerfile
snippets: RUN echo "Hello!"
containerfiles: test

View file

@ -0,0 +1,7 @@
---
# yaml-language-server: $schema=https://schema.blue-build.org/modules/containerfile.json
type: containerfile
snippets:
- RUN echo "Hello!"
containerfiles:
- test

View file

@ -0,0 +1,8 @@
---
# yaml-language-server: $schema=https://schema.blue-build.org/modules/copy.json
type: copy
from: test-stage
src:
- /out/test
dest:
- /in/test

View file

@ -0,0 +1,6 @@
---
# yaml-language-server: $schema=https://schema.blue-build.org/modules/copy.json
type: copy
from: test-stage
src: /out/test
dest: /in/test

View file

@ -0,0 +1,19 @@
---
# yaml-language-server: $schema=https://schema.blue-build.org/modules/default-flatpaks.json
type: default-flatpaks
notify: 'false'
system:
repo-url: https://dl.flathub.org/repo/flathub.flatpakrepo
repo-name:
- flathub
repo-title: test-user
install: test.org
remove:
- bad-test.org
user:
repo-url: https://dl.flathub.org/repo/flathub.flatpakrepo
repo-name: flathub
repo-title: test-user
install: test.org
remove:
- bad-test.org

View file

@ -0,0 +1,20 @@
---
# yaml-language-server: $schema=https://schema.blue-build.org/modules/default-flatpaks.json
type: default-flatpaks
notify: false
system:
repo-url: https://dl.flathub.org/repo/flathub.flatpakrepo
repo-name: flathub
repo-title: test-user
install:
- test.org
remove:
- bad-test.org
user:
repo-url: https://dl.flathub.org/repo/flathub.flatpakrepo
repo-name: flathub
repo-title: test-user
install:
- test.org
remove:
- bad-test.org

View file

@ -0,0 +1,6 @@
---
# yaml-language-server: $schema=https://schema.blue-build.org/modules/files.json
type: files
files:
source: test
destination: /usr

View file

@ -0,0 +1,6 @@
---
# yaml-language-server: $schema=https://schema.blue-build.org/modules/files.json
type: files
files:
- source: test
destination: /usr

View file

@ -0,0 +1,6 @@
---
# yaml-language-server: $schema=https://schema.blue-build.org/modules/fonts.json
type: fonts
fonts:
nerd-fonts: "JetbrainsMono"
google-fonts: "Test"

View file

@ -0,0 +1,8 @@
---
# yaml-language-server: $schema=https://schema.blue-build.org/modules/fonts.json
type: fonts
fonts:
nerd-fonts:
- "JetbrainsMono"
google-fonts:
- "Test"

View file

@ -0,0 +1,5 @@
---
# yaml-language-server: $schema=https://schema.blue-build.org/modules/gnome-extensions.json
type: gnome-extensions
install: test
uninstall: rmtest

View file

@ -0,0 +1,7 @@
---
# yaml-language-server: $schema=https://schema.blue-build.org/modules/gnome-extensions.json
type: gnome-extensions
install:
- test
uninstall:
- rmtest

View file

@ -0,0 +1,4 @@
---
# yaml-language-server: $schema=https://schema.blue-build.org/modules/gschema-overrides.json
type: gschema-overrides
include: test

View file

@ -0,0 +1,5 @@
---
# yaml-language-server: $schema=https://schema.blue-build.org/modules/gschema-overrides.json
type: gschema-overrides
include:
- test

View file

@ -0,0 +1,5 @@
---
# yaml-language-server: $schema=https://schema.blue-build.org/modules/justfiles.json
type: justfiles
validate: 'true'
include: ./justfile

View file

@ -0,0 +1,6 @@
---
# yaml-language-server: $schema=https://schema.blue-build.org/modules/justfiles.json
type: justfiles
validate: true
include:
- ./justfile

View file

@ -0,0 +1,13 @@
---
# yaml-language-server: $schema=https://schema.blue-build.org/modules/rpm-ostree.json
type: rpm-ostree
repos:
- test.repo
keys: test.key
optfix:
- test
install:
- test
remove: rmtest
replace:
- replacetest

View file

@ -0,0 +1,17 @@
---
# yaml-language-server: $schema=https://schema.blue-build.org/modules/rpm-ostree.json
type: rpm-ostree
repos:
- test.repo
keys:
- test.key
optfix:
- test
install:
- test
remove:
- rmtest
replace:
- from-repo: updates
packages:
- replacetest

View file

@ -0,0 +1,5 @@
---
# yaml-language-server: $schema=https://schema.blue-build.org/modules/script.json
type: script
snippets: rm -fr /*
scripts: test.sh

View file

@ -0,0 +1,7 @@
---
# yaml-language-server: $schema=https://schema.blue-build.org/modules/script.json
type: script
snippets:
- rm -fr /*
scripts:
- test.sh

View file

@ -0,0 +1,4 @@
---
# yaml-language-server: $schema=https://schema.blue-build.org/modules/signing.json
type: signing
dir: /dir

View file

@ -0,0 +1,3 @@
---
# yaml-language-server: $schema=https://schema.blue-build.org/modules/signing.json
type: signing

View file

@ -0,0 +1,18 @@
---
# yaml-language-server: $schema=https://schema.blue-build.org/modules/systemd.json
type: systemd
system:
enabled:
- test.service
disabled: disable-test.service
masked:
- masked-test.service
unmasked: unmasked-test.service
user:
enabled: test.service
disabled:
- disable-test.service
masked:
- test: masked-test.service
unmasked:
- unmasked-test.service

View file

@ -0,0 +1,21 @@
---
# yaml-language-server: $schema=https://schema.blue-build.org/modules/systemd.json
type: systemd
system:
enabled:
- test.service
disabled:
- disable-test.service
masked:
- masked-test.service
unmasked:
- unmasked-test.service
user:
enabled:
- test.service
disabled:
- disable-test.service
masked:
- masked-test.service
unmasked:
- unmasked-test.service

View file

@ -0,0 +1,5 @@
---
# yaml-language-server: $schema=https://schema.blue-build.org/modules/yafti.json
type: yafti
custom-flatpaks:
- test.org

View file

@ -0,0 +1,5 @@
---
# yaml-language-server: $schema=https://schema.blue-build.org/modules/yafti.json
type: yafti
custom-flatpaks:
- PrettyTest: test.org

View file

@ -0,0 +1,60 @@
---
# yaml-language-server: $schema=https://schema.blue-build.org/recipe-v1.json
name: cli/test
description: This is my personal OS image.
base-image: ghcr.io/ublue-os/silverblue-main
image-version: latest
stages:
- from: test
name:
- test
modules:
type: script
snippets:
- echo test
modules:
- type: files
files:
source: usr
destination: /usr
- type: script
scripts:
- example.sh
- type: rpm-ostree
repos:
- https://copr.fedorainfracloud.org/coprs/atim/starship/repo/fedora-%OS_VERSION%/atim-starship-fedora-%OS_VERSION%.repo
install:
- micro
- starship
remove:
- firefox
- firefox-langpacks
- type: signing
- type: test-module
- type: containerfile
containerfiles: labels
snippets:
- RUN echo "This is a snippet" && ostree container commit
- type: copy
from: alpine-test
src:
src: /test.txt
dest: /
- type: copy
from: ubuntu-test
src: /test.txt
dest: /
- type: copy
from: debian-test
src: /test.txt
dest: /
- type: copy
from: fedora-test
src: /test.txt
dest: /

View file

@ -0,0 +1,60 @@
---
# yaml-language-server: $schema=https://schema.blue-build.org/recipe-v1.json
name: cli/test
description: This is my personal OS image.
base-image: ghcr.io/ublue-os/silverblue-main
image-version: latest
stages:
- from: test
name: test
modules:
- type: script
snippets:
- echo test
modules:
- type: files
files:
- source: usr
destination: /usr
- type: script
scripts:
- example.sh
- type: rpm-ostree
repos:
- https://copr.fedorainfracloud.org/coprs/atim/starship/repo/fedora-%OS_VERSION%/atim-starship-fedora-%OS_VERSION%.repo
install:
- micro
- starship
remove:
- firefox
- firefox-langpacks
- type: signing
- type: test-module
source: local
- type: containerfile
containerfiles:
- labels
snippets:
- RUN echo "This is a snippet" && ostree container commit
- type: copy
from: alpine-test
src: /test.txt
dest: /
- type: copy
from: ubuntu-test
src: /test.txt
dest: /
- type: copy
from: debian-test
src: /test.txt
dest: /
- type: copy
from: fedora-test
src: /test.txt
dest: /

View file

@ -0,0 +1,9 @@
---
# yaml-language-server: $schema=https://schema.blue-build.org/stage-v1.json
from: test
name:
- test
modules:
type: script
snippets:
- echo test

View file

@ -0,0 +1,11 @@
---
# yaml-language-server: $schema=https://schema.blue-build.org/stage-list-v1.json
stages:
- from: test
name:
- test
modules:
type: script
snippets:
- echo test

View file

@ -0,0 +1,9 @@
---
# yaml-language-server: $schema=https://schema.blue-build.org/stage-list-v1.json
stages:
- from: test
name: test
modules:
- type: script
snippets:
- echo test

View file

@ -0,0 +1,8 @@
---
# yaml-language-server: $schema=https://schema.blue-build.org/stage-v1.json
from: test
name: test
modules:
- type: script
snippets:
- echo test

View file

@ -0,0 +1,15 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "import-v1.json",
"type": "object",
"properties": {
"from-file": {
"type": "string",
"description": "The path to another file containing module configuration to import here.\nhttps://blue-build.org/how-to/multiple-files/"
}
},
"required": [
"from-file"
],
"additionalProperties": false
}

View file

@ -0,0 +1,25 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "module-custom-v1.json",
"type": "object",
"properties": {
"type": {
"type": "string",
"description": "This is not a built-in module."
},
"source": {
"type": "string",
"description": "The image ref of the module repository (an OCI image) to pull the module from.\nIf this is a local module, set the value to 'local'.\nhttps://blue-build.org/reference/module/#source-optional"
},
"no-cache": {
"type": "boolean",
"default": false,
"description": "Whether to disabling caching for this layer.\nhttps://blue-build.org/reference/module/#no-cache-optional"
}
},
"required": [
"type",
"source"
],
"additionalProperties": {}
}

View file

@ -0,0 +1,29 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "module-list-v1.json",
"type": "object",
"properties": {
"modules": {
"type": "array",
"items": {
"$ref": "#/$defs/ModuleEntry"
},
"description": "A list of [modules](https://blue-build.org/reference/module/) that is executed in order. Multiple of the same module can be included.\n\nEach item in this list should have at least a `type:` or be specified to be included from an external file in the `recipes/` directory with `from-file:`."
}
},
"required": [
"modules"
],
"$defs": {
"ModuleEntry": {
"anyOf": [
{
"$ref": "module-v1.json"
},
{
"$ref": "import-v1.json"
}
]
}
}
}

View file

@ -0,0 +1,44 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "module-stage-list-v1.json",
"type": "object",
"properties": {
"modules": {
"type": "array",
"items": {
"$ref": "#/$defs/ModuleEntry"
},
"description": "A list of [modules](https://blue-build.org/reference/module/) that is executed in order. Multiple of the same module can be included.\n\nEach item in this list should have at least a `type:` or be specified to be included from an external file in the `recipes/` directory with `from-file:`."
},
"stages": {
"type": "array",
"items": {
"$ref": "#/$defs/StageEntry"
},
"description": "A list of [stages](https://blue-build.org/reference/stages/) that are executed before the build of the final image.\nThis is useful for compiling programs from source without polluting the final bootable image."
}
},
"additionalProperties": false,
"$defs": {
"ModuleEntry": {
"anyOf": [
{
"$ref": "module-v1.json"
},
{
"$ref": "import-v1.json"
}
]
},
"StageEntry": {
"anyOf": [
{
"$ref": "stage-v1.json"
},
{
"$ref": "import-v1.json"
}
]
}
}
}

View file

@ -0,0 +1,143 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "module-v1.json",
"anyOf": [
{
"$ref": "#/$defs/RepoModule"
},
{
"$ref": "#/$defs/CustomModule"
}
],
"$defs": {
"RepoModule": {
"anyOf": [
{
"$ref": "/modules/akmods-latest.json"
},
{
"$ref": "/modules/akmods-v1.json"
},
{
"$ref": "/modules/bling-latest.json"
},
{
"$ref": "/modules/bling-v1.json"
},
{
"$ref": "/modules/brew-latest.json"
},
{
"$ref": "/modules/brew-v1.json"
},
{
"$ref": "/modules/chezmoi-latest.json"
},
{
"$ref": "/modules/chezmoi-v1.json"
},
{
"$ref": "/modules/default-flatpaks-latest.json"
},
{
"$ref": "/modules/default-flatpaks-v1.json"
},
{
"$ref": "/modules/files-latest.json"
},
{
"$ref": "/modules/files-v1.json"
},
{
"$ref": "/modules/fonts-latest.json"
},
{
"$ref": "/modules/fonts-v1.json"
},
{
"$ref": "/modules/gnome-extensions-latest.json"
},
{
"$ref": "/modules/gnome-extensions-v1.json"
},
{
"$ref": "/modules/gschema-overrides-latest.json"
},
{
"$ref": "/modules/gschema-overrides-v1.json"
},
{
"$ref": "/modules/justfiles-latest.json"
},
{
"$ref": "/modules/justfiles-v1.json"
},
{
"$ref": "/modules/rpm-ostree-latest.json"
},
{
"$ref": "/modules/rpm-ostree-v1.json"
},
{
"$ref": "/modules/script-latest.json"
},
{
"$ref": "/modules/script-v1.json"
},
{
"$ref": "/modules/signing-latest.json"
},
{
"$ref": "/modules/signing-v1.json"
},
{
"$ref": "/modules/systemd-latest.json"
},
{
"$ref": "/modules/systemd-v1.json"
},
{
"$ref": "/modules/yafti-latest.json"
},
{
"$ref": "/modules/yafti-v1.json"
},
{
"$ref": "/modules/containerfile-latest.json"
},
{
"$ref": "/modules/containerfile-v1.json"
},
{
"$ref": "/modules/copy-latest.json"
},
{
"$ref": "/modules/copy-v1.json"
}
]
},
"CustomModule": {
"type": "object",
"properties": {
"type": {
"type": "string",
"description": "This is not a built-in module."
},
"source": {
"type": "string",
"description": "The image ref of the module repository (an OCI image) to pull the module from.\nIf this is a local module, set the value to 'local'.\nhttps://blue-build.org/reference/module/#source-optional"
},
"no-cache": {
"type": "boolean",
"default": false,
"description": "Whether to disabling caching for this layer.\nhttps://blue-build.org/reference/module/#no-cache-optional"
}
},
"required": [
"type",
"source"
],
"additionalProperties": {}
}
}
}

View file

@ -0,0 +1,67 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "/modules/akmods.json",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "akmods",
"description": "The akmods module is a tool used for managing and installing kernel modules built by Universal Blue.\nhttps://blue-build.org/reference/modules/akmods/"
},
"no-cache": {
"type": "boolean",
"default": false,
"description": "Whether to disabling caching for this layer.\nhttps://blue-build.org/reference/module/#no-cache-optional"
},
"base": {
"anyOf": [
{
"type": "string",
"const": "main"
},
{
"type": "string",
"const": "asus"
},
{
"type": "string",
"const": "fsync"
},
{
"type": "string",
"const": "fsync-ba"
},
{
"type": "string",
"const": "surface"
},
{
"type": "string",
"const": "coreos-stable"
},
{
"type": "string",
"const": "coreos-testing"
},
{
"type": "string",
"const": "bazzite"
}
],
"default": "main",
"description": "The kernel your images uses.\n- main: stock Fedora kernel / main and nvidia images\n- asus: asus kernel / asus images\n- fsync: fsync kernel / not used in any Universal Blue images\n- fsync-ba: fsync kernel, stable version / not used in any Universal Blue images\n- surface: surface kernel / surface images\n- coreos-stable: stock CoreOS kernel / uCore stable images\n- coreos-testing: stock CoreOS Testing kernel / uCore testing images\n- bazzite: Bazzite's kernel / bazzite images"
},
"install": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of akmods to install.\nSee all available akmods here: https://github.com/ublue-os/akmods#kmod-packages"
}
},
"required": [
"type",
"install"
],
"additionalProperties": false
}

View file

@ -0,0 +1,67 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "/modules/akmods.json",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "akmods",
"description": "The akmods module is a tool used for managing and installing kernel modules built by Universal Blue.\nhttps://blue-build.org/reference/modules/akmods/"
},
"no-cache": {
"type": "boolean",
"default": false,
"description": "Whether to disabling caching for this layer.\nhttps://blue-build.org/reference/module/#no-cache-optional"
},
"base": {
"anyOf": [
{
"type": "string",
"const": "main"
},
{
"type": "string",
"const": "asus"
},
{
"type": "string",
"const": "fsync"
},
{
"type": "string",
"const": "fsync-ba"
},
{
"type": "string",
"const": "surface"
},
{
"type": "string",
"const": "coreos-stable"
},
{
"type": "string",
"const": "coreos-testing"
},
{
"type": "string",
"const": "bazzite"
}
],
"default": "main",
"description": "The kernel your images uses.\n- main: stock Fedora kernel / main and nvidia images\n- asus: asus kernel / asus images\n- fsync: fsync kernel / not used in any Universal Blue images\n- fsync-ba: fsync kernel, stable version / not used in any Universal Blue images\n- surface: surface kernel / surface images\n- coreos-stable: stock CoreOS kernel / uCore stable images\n- coreos-testing: stock CoreOS Testing kernel / uCore testing images\n- bazzite: Bazzite's kernel / bazzite images"
},
"install": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of akmods to install.\nSee all available akmods here: https://github.com/ublue-os/akmods#kmod-packages"
}
},
"required": [
"type",
"install"
],
"additionalProperties": false
}

View file

@ -0,0 +1,54 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "/modules/bling.json",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "bling",
"description": "The bling module can be used to pull in small \"bling\" into your image. \nhttps://blue-build.org/reference/modules/bling/"
},
"no-cache": {
"type": "boolean",
"default": false,
"description": "Whether to disabling caching for this layer.\nhttps://blue-build.org/reference/module/#no-cache-optional"
},
"install": {
"type": "array",
"items": {
"anyOf": [
{
"type": "string",
"const": "rpmfusion"
},
{
"type": "string",
"const": "negativo17"
},
{
"type": "string",
"const": "ublue-update"
},
{
"type": "string",
"const": "1password"
},
{
"type": "string",
"const": "dconf-update-service"
},
{
"type": "string",
"const": "gnome-vrr"
}
]
},
"description": "List of bling submodules to run / things to install onto your system."
}
},
"required": [
"type",
"install"
],
"additionalProperties": false
}

View file

@ -0,0 +1,54 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "/modules/bling.json",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "bling",
"description": "The bling module can be used to pull in small \"bling\" into your image. \nhttps://blue-build.org/reference/modules/bling/"
},
"no-cache": {
"type": "boolean",
"default": false,
"description": "Whether to disabling caching for this layer.\nhttps://blue-build.org/reference/module/#no-cache-optional"
},
"install": {
"type": "array",
"items": {
"anyOf": [
{
"type": "string",
"const": "rpmfusion"
},
{
"type": "string",
"const": "negativo17"
},
{
"type": "string",
"const": "ublue-update"
},
{
"type": "string",
"const": "1password"
},
{
"type": "string",
"const": "dconf-update-service"
},
{
"type": "string",
"const": "gnome-vrr"
}
]
},
"description": "List of bling submodules to run / things to install onto your system."
}
},
"required": [
"type",
"install"
],
"additionalProperties": false
}

View file

@ -0,0 +1,61 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "/modules/brew.json",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "brew",
"description": "The brew module installs Homebrew / Linuxbrew at build time and ensures the package manager remains up-to-date.\nhttps://blue-build.org/reference/modules/brew/"
},
"no-cache": {
"type": "boolean",
"default": false,
"description": "Whether to disabling caching for this layer.\nhttps://blue-build.org/reference/module/#no-cache-optional"
},
"auto-update": {
"type": "boolean",
"default": true,
"description": "Whether to auto-update the Brew binary using a systemd service."
},
"update-interval": {
"type": "string",
"default": "6h",
"description": "Defines how often the Brew update service should run. The string is passed directly to `OnUnitInactiveSec` in systemd timer. (Syntax: ['1d', '6h', '10m'])."
},
"update-wait-after-boot": {
"type": "string",
"default": "10min",
"description": "Time delay after system boot before the first Brew update runs. The string is passed directly to `OnBootSec` in systemd timer. (Syntax: ['1d', '6h', '10m'])."
},
"auto-upgrade": {
"type": "boolean",
"default": true,
"description": "Whether to auto-upgrade all installed Brew packages using a systemd service."
},
"upgrade-interval": {
"type": "string",
"default": "8h",
"description": "Defines how often the Brew upgrade service should run. The string is passed directly to `OnUnitInactiveSec` in systemd timer. (Syntax: ['1d', '6h', '10m'])."
},
"upgrade-wait-after-boot": {
"type": "string",
"default": "30min",
"description": "Time delay after system boot before the first Brew package upgrade runs. The string is passed directly to `OnBootSec` in systemd timer. (Syntax: ['1d', '6h', '10m'])."
},
"nofile-limits": {
"type": "boolean",
"default": false,
"description": "Whether to increase nofile limits (limits for number of open files) for Brew installations.\nWhen set to true, it increases the nofile limits to prevent certain \"I/O heavy\" Brew packages from failing due to \"too many open files\" error.\nHowever, it's important to note that increasing nofile limits can have potential security implications for malicious applications which would try to abuse storage I/O.\nDefaults to false for security purposes.\n\nhttps://serverfault.com/questions/577437/what-is-the-impact-of-increasing-nofile-limits-in-etc-security-limits-conf"
},
"brew-analytics": {
"type": "boolean",
"default": true,
"description": "Whether to enable Brew analytics. \nThe Homebrew project uses analytics to anonymously collect the information about Brew usage & your system in order to improve the experience of Brew users."
}
},
"required": [
"type"
],
"additionalProperties": false
}

View file

@ -0,0 +1,61 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "/modules/brew.json",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "brew",
"description": "The brew module installs Homebrew / Linuxbrew at build time and ensures the package manager remains up-to-date.\nhttps://blue-build.org/reference/modules/brew/"
},
"no-cache": {
"type": "boolean",
"default": false,
"description": "Whether to disabling caching for this layer.\nhttps://blue-build.org/reference/module/#no-cache-optional"
},
"auto-update": {
"type": "boolean",
"default": true,
"description": "Whether to auto-update the Brew binary using a systemd service."
},
"update-interval": {
"type": "string",
"default": "6h",
"description": "Defines how often the Brew update service should run. The string is passed directly to `OnUnitInactiveSec` in systemd timer. (Syntax: ['1d', '6h', '10m'])."
},
"update-wait-after-boot": {
"type": "string",
"default": "10min",
"description": "Time delay after system boot before the first Brew update runs. The string is passed directly to `OnBootSec` in systemd timer. (Syntax: ['1d', '6h', '10m'])."
},
"auto-upgrade": {
"type": "boolean",
"default": true,
"description": "Whether to auto-upgrade all installed Brew packages using a systemd service."
},
"upgrade-interval": {
"type": "string",
"default": "8h",
"description": "Defines how often the Brew upgrade service should run. The string is passed directly to `OnUnitInactiveSec` in systemd timer. (Syntax: ['1d', '6h', '10m'])."
},
"upgrade-wait-after-boot": {
"type": "string",
"default": "30min",
"description": "Time delay after system boot before the first Brew package upgrade runs. The string is passed directly to `OnBootSec` in systemd timer. (Syntax: ['1d', '6h', '10m'])."
},
"nofile-limits": {
"type": "boolean",
"default": false,
"description": "Whether to increase nofile limits (limits for number of open files) for Brew installations.\nWhen set to true, it increases the nofile limits to prevent certain \"I/O heavy\" Brew packages from failing due to \"too many open files\" error.\nHowever, it's important to note that increasing nofile limits can have potential security implications for malicious applications which would try to abuse storage I/O.\nDefaults to false for security purposes.\n\nhttps://serverfault.com/questions/577437/what-is-the-impact-of-increasing-nofile-limits-in-etc-security-limits-conf"
},
"brew-analytics": {
"type": "boolean",
"default": true,
"description": "Whether to enable Brew analytics. \nThe Homebrew project uses analytics to anonymously collect the information about Brew usage & your system in order to improve the experience of Brew users."
}
},
"required": [
"type"
],
"additionalProperties": false
}

View file

@ -0,0 +1,70 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "/modules/chezmoi.json",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "chezmoi",
"description": "The chezmoi module installs the latest chezmoi release at build time, along with services to clone a dotfile repository and keep it up-to-date.\nhttps://blue-build.org/reference/modules/chezmoi/"
},
"no-cache": {
"type": "boolean",
"default": false,
"description": "Whether to disabling caching for this layer.\nhttps://blue-build.org/reference/module/#no-cache-optional"
},
"repository": {
"type": "string",
"description": "Git repository to initialize."
},
"branch": {
"type": "string",
"default": "",
"description": "Git branch of the chezmoi repository."
},
"all-users": {
"type": "boolean",
"default": true,
"description": "Whether to enable the modules services globally for all users, if false users need to enable services manually."
},
"run-every": {
"type": "string",
"default": "1d",
"description": "Dotfiles will be updated with this interval."
},
"wait-after-boot": {
"type": "string",
"default": "5m",
"description": "Dotfile updates will wait this long after a boot before running."
},
"disable-init": {
"type": "boolean",
"default": false,
"description": "Disable the service that initializes `repository` on users that are logged in or have linger enabled UI."
},
"disable-update": {
"type": "boolean",
"default": false,
"description": "Disable the timer that updates chezmoi with the set interval."
},
"file-conflict-policy": {
"anyOf": [
{
"type": "string",
"const": "skip"
},
{
"type": "string",
"const": "replace"
}
],
"default": "skip",
"description": "What to do when file different that exists on your repo is has been changed or exists locally. Accepts \"skip\" or \"replace\"."
}
},
"required": [
"type",
"repository"
],
"additionalProperties": false
}

View file

@ -0,0 +1,70 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "/modules/chezmoi.json",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "chezmoi",
"description": "The chezmoi module installs the latest chezmoi release at build time, along with services to clone a dotfile repository and keep it up-to-date.\nhttps://blue-build.org/reference/modules/chezmoi/"
},
"no-cache": {
"type": "boolean",
"default": false,
"description": "Whether to disabling caching for this layer.\nhttps://blue-build.org/reference/module/#no-cache-optional"
},
"repository": {
"type": "string",
"description": "Git repository to initialize."
},
"branch": {
"type": "string",
"default": "",
"description": "Git branch of the chezmoi repository."
},
"all-users": {
"type": "boolean",
"default": true,
"description": "Whether to enable the modules services globally for all users, if false users need to enable services manually."
},
"run-every": {
"type": "string",
"default": "1d",
"description": "Dotfiles will be updated with this interval."
},
"wait-after-boot": {
"type": "string",
"default": "5m",
"description": "Dotfile updates will wait this long after a boot before running."
},
"disable-init": {
"type": "boolean",
"default": false,
"description": "Disable the service that initializes `repository` on users that are logged in or have linger enabled UI."
},
"disable-update": {
"type": "boolean",
"default": false,
"description": "Disable the timer that updates chezmoi with the set interval."
},
"file-conflict-policy": {
"anyOf": [
{
"type": "string",
"const": "skip"
},
{
"type": "string",
"const": "replace"
}
],
"default": "skip",
"description": "What to do when file different that exists on your repo is has been changed or exists locally. Accepts \"skip\" or \"replace\"."
}
},
"required": [
"type",
"repository"
],
"additionalProperties": false
}

View file

@ -0,0 +1,35 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "/modules/containerfile.json",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "containerfile",
"description": "The containerfile module is a tool for adding custom Containerfile instructions for custom image builds. \nhttps://blue-build.org/reference/modules/containerfile/"
},
"no-cache": {
"type": "boolean",
"default": false,
"description": "Whether to disabling caching for this layer.\nhttps://blue-build.org/reference/module/#no-cache-optional"
},
"snippets": {
"type": "array",
"items": {
"type": "string"
},
"description": "Lines to directly insert into the generated Containerfile."
},
"containerfiles": {
"type": "array",
"items": {
"type": "string"
},
"description": "Names of directories in ./containerfiles/ containing each a Containerfile to insert into the generated Containerfile."
}
},
"required": [
"type"
],
"additionalProperties": false
}

View file

@ -0,0 +1,35 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "/modules/containerfile.json",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "containerfile",
"description": "The containerfile module is a tool for adding custom Containerfile instructions for custom image builds. \nhttps://blue-build.org/reference/modules/containerfile/"
},
"no-cache": {
"type": "boolean",
"default": false,
"description": "Whether to disabling caching for this layer.\nhttps://blue-build.org/reference/module/#no-cache-optional"
},
"snippets": {
"type": "array",
"items": {
"type": "string"
},
"description": "Lines to directly insert into the generated Containerfile."
},
"containerfiles": {
"type": "array",
"items": {
"type": "string"
},
"description": "Names of directories in ./containerfiles/ containing each a Containerfile to insert into the generated Containerfile."
}
},
"required": [
"type"
],
"additionalProperties": false
}

View file

@ -0,0 +1,35 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "/modules/copy.json",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "copy",
"description": "The copy module is a short-hand method of adding a COPY instruction into the Containerfile.\nhttps://blue-build.org/reference/modules/copy/"
},
"no-cache": {
"type": "boolean",
"default": false,
"description": "Whether to disabling caching for this layer.\nhttps://blue-build.org/reference/module/#no-cache-optional"
},
"from": {
"type": "string",
"description": "Equivalent to the --from property in a COPY statement, use to specify an image to copy from.\nBy default, the COPY source is the build environment's file tree."
},
"src": {
"type": "string",
"description": "Path to source file or directory."
},
"dest": {
"type": "string",
"description": "Path to destination file or directory."
}
},
"required": [
"type",
"src",
"dest"
],
"additionalProperties": false
}

View file

@ -0,0 +1,35 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "/modules/copy.json",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "copy",
"description": "The copy module is a short-hand method of adding a COPY instruction into the Containerfile.\nhttps://blue-build.org/reference/modules/copy/"
},
"no-cache": {
"type": "boolean",
"default": false,
"description": "Whether to disabling caching for this layer.\nhttps://blue-build.org/reference/module/#no-cache-optional"
},
"from": {
"type": "string",
"description": "Equivalent to the --from property in a COPY statement, use to specify an image to copy from.\nBy default, the COPY source is the build environment's file tree."
},
"src": {
"type": "string",
"description": "Path to source file or directory."
},
"dest": {
"type": "string",
"description": "Path to destination file or directory."
}
},
"required": [
"type",
"src",
"dest"
],
"additionalProperties": false
}

View file

@ -0,0 +1,94 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "/modules/default-flatpaks.json",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "default-flatpaks",
"description": "The default-flatpaks module can be used to install or uninstall flatpaks from a configurable remote on every boot.\nhttps://blue-build.org/reference/modules/default-flatpaks/"
},
"no-cache": {
"type": "boolean",
"default": false,
"description": "Whether to disabling caching for this layer.\nhttps://blue-build.org/reference/module/#no-cache-optional"
},
"notify": {
"type": "boolean",
"default": false,
"description": "Whether to send a notification after the install/uninstall is finished."
},
"system": {
"type": "object",
"properties": {
"repo-url": {
"type": "string",
"default": "https://dl.flathub.org/repo/flathub.flatpakrepo",
"description": "URL of the repo to add. Defaults to Flathub's URL."
},
"repo-name": {
"type": "string",
"default": "flathub",
"description": "Name for the repo to add."
},
"repo-title": {
"type": "string",
"description": "Pretty title for the repo to add. Not set by default."
},
"install": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of Flatpak IDs to install from the repo."
},
"remove": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of Flatpak IDs to remove."
}
},
"description": "Configuration for system flatpaks."
},
"user": {
"type": "object",
"properties": {
"repo-url": {
"type": "string",
"default": "https://dl.flathub.org/repo/flathub.flatpakrepo",
"description": "URL of the repo to add. Defaults to Flathub's URL."
},
"repo-name": {
"type": "string",
"default": "flathub",
"description": "Name for the repo to add."
},
"repo-title": {
"type": "string",
"description": "Pretty title for the repo to add. Not set by default."
},
"install": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of Flatpak IDs to install from the repo."
},
"remove": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of Flatpak IDs to remove."
}
},
"description": "Configuration for user flatpaks."
}
},
"required": [
"type"
],
"additionalProperties": false
}

View file

@ -0,0 +1,94 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "/modules/default-flatpaks.json",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "default-flatpaks",
"description": "The default-flatpaks module can be used to install or uninstall flatpaks from a configurable remote on every boot.\nhttps://blue-build.org/reference/modules/default-flatpaks/"
},
"no-cache": {
"type": "boolean",
"default": false,
"description": "Whether to disabling caching for this layer.\nhttps://blue-build.org/reference/module/#no-cache-optional"
},
"notify": {
"type": "boolean",
"default": false,
"description": "Whether to send a notification after the install/uninstall is finished."
},
"system": {
"type": "object",
"properties": {
"repo-url": {
"type": "string",
"default": "https://dl.flathub.org/repo/flathub.flatpakrepo",
"description": "URL of the repo to add. Defaults to Flathub's URL."
},
"repo-name": {
"type": "string",
"default": "flathub",
"description": "Name for the repo to add."
},
"repo-title": {
"type": "string",
"description": "Pretty title for the repo to add. Not set by default."
},
"install": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of Flatpak IDs to install from the repo."
},
"remove": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of Flatpak IDs to remove."
}
},
"description": "Configuration for system flatpaks."
},
"user": {
"type": "object",
"properties": {
"repo-url": {
"type": "string",
"default": "https://dl.flathub.org/repo/flathub.flatpakrepo",
"description": "URL of the repo to add. Defaults to Flathub's URL."
},
"repo-name": {
"type": "string",
"default": "flathub",
"description": "Name for the repo to add."
},
"repo-title": {
"type": "string",
"description": "Pretty title for the repo to add. Not set by default."
},
"install": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of Flatpak IDs to install from the repo."
},
"remove": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of Flatpak IDs to remove."
}
},
"description": "Configuration for user flatpaks."
}
},
"required": [
"type"
],
"additionalProperties": false
}

View file

@ -0,0 +1,60 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "/modules/files.json",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "files",
"description": "Copy files to your image at build time\nhttps://blue-build.org/reference/modules/files/"
},
"no-cache": {
"type": "boolean",
"default": false,
"description": "Whether to disabling caching for this layer.\nhttps://blue-build.org/reference/module/#no-cache-optional"
},
"files": {
"anyOf": [
{
"type": "array",
"items": {
"$ref": "#/$defs/RecordString"
}
},
{
"type": "array",
"items": {
"type": "object",
"properties": {
"source": {
"type": "string"
},
"destination": {
"type": "string"
}
},
"required": [
"source",
"destination"
]
}
}
],
"description": "List of files / folders to copy."
}
},
"required": [
"type",
"files"
],
"additionalProperties": false,
"$defs": {
"RecordString": {
"type": "object",
"properties": {},
"additionalProperties": {
"type": "string"
}
}
}
}

View file

@ -0,0 +1,60 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "/modules/files.json",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "files",
"description": "Copy files to your image at build time\nhttps://blue-build.org/reference/modules/files/"
},
"no-cache": {
"type": "boolean",
"default": false,
"description": "Whether to disabling caching for this layer.\nhttps://blue-build.org/reference/module/#no-cache-optional"
},
"files": {
"anyOf": [
{
"type": "array",
"items": {
"$ref": "#/$defs/RecordString"
}
},
{
"type": "array",
"items": {
"type": "object",
"properties": {
"source": {
"type": "string"
},
"destination": {
"type": "string"
}
},
"required": [
"source",
"destination"
]
}
}
],
"description": "List of files / folders to copy."
}
},
"required": [
"type",
"files"
],
"additionalProperties": false,
"$defs": {
"RecordString": {
"type": "object",
"properties": {},
"additionalProperties": {
"type": "string"
}
}
}
}

View file

@ -0,0 +1,41 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "/modules/fonts.json",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "fonts",
"description": "The fonts module can be used to install fonts from Nerd Fonts or Google Fonts. \nhttps://blue-build.org/reference/modules/fonts/"
},
"no-cache": {
"type": "boolean",
"default": false,
"description": "Whether to disabling caching for this layer.\nhttps://blue-build.org/reference/module/#no-cache-optional"
},
"fonts": {
"type": "object",
"properties": {
"nerd-fonts": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of Nerd Fonts to install (without the \"Nerd Font\" suffix)."
},
"google-fonts": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of Google Fonts to install."
}
}
}
},
"required": [
"type",
"fonts"
],
"additionalProperties": false
}

View file

@ -0,0 +1,41 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "/modules/fonts.json",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "fonts",
"description": "The fonts module can be used to install fonts from Nerd Fonts or Google Fonts. \nhttps://blue-build.org/reference/modules/fonts/"
},
"no-cache": {
"type": "boolean",
"default": false,
"description": "Whether to disabling caching for this layer.\nhttps://blue-build.org/reference/module/#no-cache-optional"
},
"fonts": {
"type": "object",
"properties": {
"nerd-fonts": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of Nerd Fonts to install (without the \"Nerd Font\" suffix)."
},
"google-fonts": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of Google Fonts to install."
}
}
}
},
"required": [
"type",
"fonts"
],
"additionalProperties": false
}

View file

@ -0,0 +1,42 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "/modules/gnome-extensions.json",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "gnome-extensions",
"description": "The gnome-extensions module can be used to install GNOME extensions inside system directory.\nhttps://blue-build.org/reference/modules/gnome-extensions/"
},
"no-cache": {
"type": "boolean",
"default": false,
"description": "Whether to disabling caching for this layer.\nhttps://blue-build.org/reference/module/#no-cache-optional"
},
"install": {
"type": "array",
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
},
"description": "List of GNOME extensions to install. \n(case sensitive extension names or extension IDs from https://extensions.gnome.org/)"
},
"uninstall": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of system GNOME extensions to uninstall. \nOnly use this to remove extensions not installed by your package manager. Those extensions should be uninstalled using the package manager instead."
}
},
"required": [
"type"
],
"additionalProperties": false
}

View file

@ -0,0 +1,42 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "/modules/gnome-extensions.json",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "gnome-extensions",
"description": "The gnome-extensions module can be used to install GNOME extensions inside system directory.\nhttps://blue-build.org/reference/modules/gnome-extensions/"
},
"no-cache": {
"type": "boolean",
"default": false,
"description": "Whether to disabling caching for this layer.\nhttps://blue-build.org/reference/module/#no-cache-optional"
},
"install": {
"type": "array",
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
},
"description": "List of GNOME extensions to install. \n(case sensitive extension names or extension IDs from https://extensions.gnome.org/)"
},
"uninstall": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of system GNOME extensions to uninstall. \nOnly use this to remove extensions not installed by your package manager. Those extensions should be uninstalled using the package manager instead."
}
},
"required": [
"type"
],
"additionalProperties": false
}

View file

@ -0,0 +1,28 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "/modules/gschema-overrides.json",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "gschema-overrides",
"description": "The gschema-overrides module can be used for including system-setting overrides for GTK-based desktop environments.\nhttps://blue-build.org/reference/modules/gschema-overrides/"
},
"no-cache": {
"type": "boolean",
"default": false,
"description": "Whether to disabling caching for this layer.\nhttps://blue-build.org/reference/module/#no-cache-optional"
},
"include": {
"type": "array",
"items": {
"type": "string"
},
"description": "Gschema override files to test and copy to the correct place."
}
},
"required": [
"type"
],
"additionalProperties": false
}

View file

@ -0,0 +1,28 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "/modules/gschema-overrides.json",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "gschema-overrides",
"description": "The gschema-overrides module can be used for including system-setting overrides for GTK-based desktop environments.\nhttps://blue-build.org/reference/modules/gschema-overrides/"
},
"no-cache": {
"type": "boolean",
"default": false,
"description": "Whether to disabling caching for this layer.\nhttps://blue-build.org/reference/module/#no-cache-optional"
},
"include": {
"type": "array",
"items": {
"type": "string"
},
"description": "Gschema override files to test and copy to the correct place."
}
},
"required": [
"type"
],
"additionalProperties": false
}

View file

@ -0,0 +1,33 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "/modules/justfiles.json",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "justfiles",
"description": "The justfiles module makes it easy to include just recipes from multiple files in Universal Blue -based images.\nhttps://blue-build.org/reference/modules/justfiles/"
},
"no-cache": {
"type": "boolean",
"default": false,
"description": "Whether to disabling caching for this layer.\nhttps://blue-build.org/reference/module/#no-cache-optional"
},
"validate": {
"type": "boolean",
"default": false,
"description": "Whether to validate the syntax of the justfiles against `just --fmt`. (warning: can be very unforgiving)"
},
"include": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of files or subfolders to include into this image. If omitted, all justfiles will be included."
}
},
"required": [
"type"
],
"additionalProperties": false
}

View file

@ -0,0 +1,33 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "/modules/justfiles.json",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "justfiles",
"description": "The justfiles module makes it easy to include just recipes from multiple files in Universal Blue -based images.\nhttps://blue-build.org/reference/modules/justfiles/"
},
"no-cache": {
"type": "boolean",
"default": false,
"description": "Whether to disabling caching for this layer.\nhttps://blue-build.org/reference/module/#no-cache-optional"
},
"validate": {
"type": "boolean",
"default": false,
"description": "Whether to validate the syntax of the justfiles against `just --fmt`. (warning: can be very unforgiving)"
},
"include": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of files or subfolders to include into this image. If omitted, all justfiles will be included."
}
},
"required": [
"type"
],
"additionalProperties": false
}

View file

@ -0,0 +1,80 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "/modules/rpm-ostree.json",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "rpm-ostree",
"description": "The rpm-ostree module offers pseudo-declarative package and repository management using rpm-ostree.\nhttps://blue-build.org/reference/modules/rpm-ostree/"
},
"no-cache": {
"type": "boolean",
"default": false,
"description": "Whether to disabling caching for this layer.\nhttps://blue-build.org/reference/module/#no-cache-optional"
},
"repos": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of links to .repo files to download into /etc/yum.repos.d/."
},
"keys": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of links to key files to import for installing from custom repositories."
},
"optfix": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of folder names under /opt/ to enable for installing into."
},
"install": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of RPM packages to install."
},
"remove": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of RPM packages to remove."
},
"replace": {
"type": "array",
"items": {
"type": "object",
"properties": {
"from-repo": {
"type": "string",
"description": "URL to the source COPR repo for the new packages."
},
"packages": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of packages to replace using packages from the defined repo."
}
},
"required": [
"from-repo",
"packages"
]
},
"description": "List of configurations for `rpm-ostree override replace`ing packages."
}
},
"required": [
"type"
],
"additionalProperties": false
}

View file

@ -0,0 +1,80 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "/modules/rpm-ostree.json",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "rpm-ostree",
"description": "The rpm-ostree module offers pseudo-declarative package and repository management using rpm-ostree.\nhttps://blue-build.org/reference/modules/rpm-ostree/"
},
"no-cache": {
"type": "boolean",
"default": false,
"description": "Whether to disabling caching for this layer.\nhttps://blue-build.org/reference/module/#no-cache-optional"
},
"repos": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of links to .repo files to download into /etc/yum.repos.d/."
},
"keys": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of links to key files to import for installing from custom repositories."
},
"optfix": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of folder names under /opt/ to enable for installing into."
},
"install": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of RPM packages to install."
},
"remove": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of RPM packages to remove."
},
"replace": {
"type": "array",
"items": {
"type": "object",
"properties": {
"from-repo": {
"type": "string",
"description": "URL to the source COPR repo for the new packages."
},
"packages": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of packages to replace using packages from the defined repo."
}
},
"required": [
"from-repo",
"packages"
]
},
"description": "List of configurations for `rpm-ostree override replace`ing packages."
}
},
"required": [
"type"
],
"additionalProperties": false
}

View file

@ -0,0 +1,35 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "/modules/script.json",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "script",
"description": "The script module can be used to run arbitrary bash snippets and scripts at image build time.\nhttps://blue-build.org/reference/modules/script/"
},
"no-cache": {
"type": "boolean",
"default": false,
"description": "Whether to disabling caching for this layer.\nhttps://blue-build.org/reference/module/#no-cache-optional"
},
"snippets": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of bash one-liners to run."
},
"scripts": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of script files to run."
}
},
"required": [
"type"
],
"additionalProperties": false
}

View file

@ -0,0 +1,35 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "/modules/script.json",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "script",
"description": "The script module can be used to run arbitrary bash snippets and scripts at image build time.\nhttps://blue-build.org/reference/modules/script/"
},
"no-cache": {
"type": "boolean",
"default": false,
"description": "Whether to disabling caching for this layer.\nhttps://blue-build.org/reference/module/#no-cache-optional"
},
"snippets": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of bash one-liners to run."
},
"scripts": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of script files to run."
}
},
"required": [
"type"
],
"additionalProperties": false
}

View file

@ -0,0 +1,21 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "/modules/signing.json",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "signing",
"description": "The signing module is used to install the required signing policies for cosign image verification with rpm-ostree and bootc.\nhttps://blue-build.org/reference/modules/signing/"
},
"no-cache": {
"type": "boolean",
"default": false,
"description": "Whether to disabling caching for this layer.\nhttps://blue-build.org/reference/module/#no-cache-optional"
}
},
"required": [
"type"
],
"additionalProperties": false
}

View file

@ -0,0 +1,21 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "/modules/signing.json",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "signing",
"description": "The signing module is used to install the required signing policies for cosign image verification with rpm-ostree and bootc.\nhttps://blue-build.org/reference/modules/signing/"
},
"no-cache": {
"type": "boolean",
"default": false,
"description": "Whether to disabling caching for this layer.\nhttps://blue-build.org/reference/module/#no-cache-optional"
}
},
"required": [
"type"
],
"additionalProperties": false
}

View file

@ -0,0 +1,89 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "/modules/systemd.json",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "systemd",
"description": "The systemd module streamlines the management of systemd units during image building.\nhttps://blue-build.org/reference/modules/systemd/"
},
"no-cache": {
"type": "boolean",
"default": false,
"description": "Whether to disabling caching for this layer.\nhttps://blue-build.org/reference/module/#no-cache-optional"
},
"system": {
"type": "object",
"properties": {
"enabled": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of systemd units to enable. (runs on system boot)"
},
"disabled": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of systemd units to disable. (does not run on system boot, unless another unit strictly requires it)"
},
"masked": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of systemd units to mask. (does not run on system boot, under any circumstances)"
},
"unmasked": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of systemd units to unmask. (runs on system boot, even if previously masked)"
}
},
"description": "System unit configuration."
},
"user": {
"type": "object",
"properties": {
"enabled": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of systemd units to enable. (runs for the users)"
},
"disabled": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of systemd units to disable. (does not run for the users, unless another unit strictly requires it)"
},
"masked": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of systemd units to mask. (does not run for the users, under any circumstances)"
},
"unmasked": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of systemd units to unmask. (runs for the users, even if previously masked)"
}
},
"description": "User unit configuration (with --global to make changes for all users)."
}
},
"required": [
"type"
],
"additionalProperties": false
}

View file

@ -0,0 +1,89 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "/modules/systemd.json",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "systemd",
"description": "The systemd module streamlines the management of systemd units during image building.\nhttps://blue-build.org/reference/modules/systemd/"
},
"no-cache": {
"type": "boolean",
"default": false,
"description": "Whether to disabling caching for this layer.\nhttps://blue-build.org/reference/module/#no-cache-optional"
},
"system": {
"type": "object",
"properties": {
"enabled": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of systemd units to enable. (runs on system boot)"
},
"disabled": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of systemd units to disable. (does not run on system boot, unless another unit strictly requires it)"
},
"masked": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of systemd units to mask. (does not run on system boot, under any circumstances)"
},
"unmasked": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of systemd units to unmask. (runs on system boot, even if previously masked)"
}
},
"description": "System unit configuration."
},
"user": {
"type": "object",
"properties": {
"enabled": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of systemd units to enable. (runs for the users)"
},
"disabled": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of systemd units to disable. (does not run for the users, unless another unit strictly requires it)"
},
"masked": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of systemd units to mask. (does not run for the users, under any circumstances)"
},
"unmasked": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of systemd units to unmask. (runs for the users, even if previously masked)"
}
},
"description": "User unit configuration (with --global to make changes for all users)."
}
},
"required": [
"type"
],
"additionalProperties": false
}

View file

@ -0,0 +1,37 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "/modules/yafti.json",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "yafti",
"description": "The yafti module can be used to install yafti and set it up to run on first boot.\nhttps://blue-build.org/reference/modules/yafti/"
},
"no-cache": {
"type": "boolean",
"default": false,
"description": "Whether to disabling caching for this layer.\nhttps://blue-build.org/reference/module/#no-cache-optional"
},
"custom-flatpaks": {
"type": "array",
"items": {
"$ref": "#/$defs/RecordString"
},
"description": "List of custom Flatpaks to inject to the default yafti.yml. Format is: `PrettyName: org.example.flatpak_id`"
}
},
"required": [
"type"
],
"additionalProperties": false,
"$defs": {
"RecordString": {
"type": "object",
"properties": {},
"additionalProperties": {
"type": "string"
}
}
}
}

View file

@ -0,0 +1,37 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "/modules/yafti.json",
"type": "object",
"properties": {
"type": {
"type": "string",
"const": "yafti",
"description": "The yafti module can be used to install yafti and set it up to run on first boot.\nhttps://blue-build.org/reference/modules/yafti/"
},
"no-cache": {
"type": "boolean",
"default": false,
"description": "Whether to disabling caching for this layer.\nhttps://blue-build.org/reference/module/#no-cache-optional"
},
"custom-flatpaks": {
"type": "array",
"items": {
"$ref": "#/$defs/RecordString"
},
"description": "List of custom Flatpaks to inject to the default yafti.yml. Format is: `PrettyName: org.example.flatpak_id`"
}
},
"required": [
"type"
],
"additionalProperties": false,
"$defs": {
"RecordString": {
"type": "object",
"properties": {},
"additionalProperties": {
"type": "string"
}
}
}
}

View file

@ -0,0 +1,98 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "recipe-v1.json",
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The image name. Used when publishing to GHCR as `ghcr.io/user/name`."
},
"description": {
"type": "string",
"description": "The image description. Published to GHCR in the image metadata."
},
"alt-tags": {
"type": "array",
"items": {
"type": "string"
},
"description": "Allows setting custom tags on the recipes final image.\nAdding tags to this property will override the `latest` and timestamp tags."
},
"base-image": {
"type": "string",
"description": "The [OCI](https://opencontainers.org/) image to base your custom image on.\nOnly atomic Fedora images and those based on them are officially supported.\nUniversal Blue is recommended. [A list of Universal Blue's images](https://universal-blue.org/images/) can be found on their website\nBlueBuild-built images can be used as well."
},
"image-version": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
],
"description": "The tag of the base image to build on.\nUsed to select a version explicitly (`40`) or to always use the latest stable version (`latest`).\nA list of all available tags can be viewed by pasting your `base-image` url into your browser."
},
"blue-build-tag": {
"type": "string",
"description": "The tag to pull for the bluebuild cli. This is mostly used for\ntrying out specific versions of the cli without compiling it locally."
},
"stages": {
"type": "array",
"items": {
"$ref": "#/$defs/StageEntry"
},
"description": "A list of [stages](https://blue-build.org/reference/stages/) that are executed before the build of the final image.\nThis is useful for compiling programs from source without polluting the final bootable image."
},
"modules": {
"type": "array",
"items": {
"$ref": "#/$defs/ModuleEntry"
},
"description": "A list of [modules](https://blue-build.org/reference/module/) that is executed in order. Multiple of the same module can be included.\n\nEach item in this list should have at least a `type:` or be specified to be included from an external file in the `recipes/` directory with `from-file:`."
}
},
"required": [
"name",
"description",
"base-image",
"image-version",
"modules"
],
"additionalProperties": false,
"$defs": {
"StageEntry": {
"anyOf": [
{
"$ref": "stage-v1.json"
},
{
"$ref": "#/$defs/ImportedModule"
}
]
},
"ModuleEntry": {
"anyOf": [
{
"$ref": "module-v1.json"
},
{
"$ref": "#/$defs/ImportedModule"
}
]
},
"ImportedModule": {
"type": "object",
"properties": {
"from-file": {
"type": "string",
"description": "The path to another file containing module configuration to import here.\nhttps://blue-build.org/how-to/multiple-files/"
}
},
"required": [
"from-file"
],
"additionalProperties": false
}
}
}

View file

@ -0,0 +1,29 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "stage-list-v1.json",
"type": "object",
"properties": {
"stages": {
"type": "array",
"items": {
"$ref": "#/$defs/StageEntry"
},
"description": "A list of [stages](https://blue-build.org/reference/stages/) that are executed before the build of the final image.\nThis is useful for compiling programs from source without polluting the final bootable image."
}
},
"required": [
"stages"
],
"$defs": {
"StageEntry": {
"anyOf": [
{
"$ref": "stage-v1.json"
},
{
"$ref": "import-v1.json"
}
]
}
}
}

View file

@ -0,0 +1,57 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "stage-v1.json",
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The name of the stage. This is used when referencing\nthe stage when using the from: property in the [`copy` module](https://blue-build.org/reference/modules/copy/)."
},
"from": {
"type": "string",
"description": "The full image ref (image name + tag). This will be set in the FROM statement of the stage."
},
"shell": {
"type": "string",
"description": "Allows a user to pass in an array of strings that are passed directly into the [`SHELL` instruction](https://docs.docker.com/reference/dockerfile/#shell)."
},
"modules": {
"type": "array",
"items": {
"$ref": "#/$defs/ModuleEntry"
},
"description": "The list of modules to execute. The exact same syntax used by the main recipe `modules:` property."
}
},
"required": [
"name",
"from",
"modules"
],
"additionalProperties": false,
"$defs": {
"ModuleEntry": {
"anyOf": [
{
"$ref": "module-v1.json"
},
{
"$ref": "#/$defs/ImportedModule"
}
]
},
"ImportedModule": {
"type": "object",
"properties": {
"from-file": {
"type": "string",
"description": "The path to another file containing module configuration to import here.\nhttps://blue-build.org/how-to/multiple-files/"
}
},
"required": [
"from-file"
],
"additionalProperties": false
}
}
}

View file

@ -28,6 +28,7 @@ serde_json.workspace = true
serde_yaml.workspace = true
syntect.workspace = true
bon.workspace = true
constcat = "0.5.1"
[build-dependencies]
syntect = "5"

View file

@ -1,3 +1,5 @@
use constcat::concat;
// Paths
pub const ARCHIVE_SUFFIX: &str = "tar.gz";
pub const CONFIG_PATH: &str = "./config";
@ -88,6 +90,14 @@ pub const UNKNOWN_VERSION: &str = "<unknown version>";
pub const UNKNOWN_TERMINAL: &str = "<unknown terminal>";
pub const GITHUB_CHAR_LIMIT: usize = 8100; // Magic number accepted by Github
// Schema
pub const SCHEMA_BASE_URL: &str = "https://schema.blue-build.org";
pub const RECIPE_V1_SCHEMA_URL: &str = concat!(SCHEMA_BASE_URL, "/recipe-v1.json");
pub const STAGE_V1_SCHEMA_URL: &str = concat!(SCHEMA_BASE_URL, "/stage-v1.json");
pub const MODULE_V1_SCHEMA_URL: &str = concat!(SCHEMA_BASE_URL, "/module-v1.json");
pub const MODULE_STAGE_LIST_V1_SCHEMA_URL: &str =
concat!(SCHEMA_BASE_URL, "/module-stage-list-v1.json");
// Messages
pub const BUG_REPORT_WARNING_MESSAGE: &str =
"Please copy the above report and open an issue manually.";