Publish schema of state.json

This commit is contained in:
Miguel Jacq 2026-01-03 18:17:25 +11:00
parent 418fc7e4ef
commit 5951f3c88c
Signed by: mig5
GPG key ID: 59B3F0C24135C6A9
6 changed files with 840 additions and 0 deletions

View file

@ -33,6 +33,7 @@
<ul class="navbar-nav ms-auto align-items-lg-center gap-lg-2">
<li class="nav-item"><a class="nav-link" href="docs.html">Docs</a></li>
<li class="nav-item"><a class="nav-link" href="examples.html">Examples</a></li>
<li class="nav-item"><a class="nav-link" href="schema.html">Schema</a></li>
<li class="nav-item"><a class="nav-link" href="security.html">Security Design</a></li>
<li class="nav-item ms-lg-2">
<a class="btn btn-sm btn-outline-dark" href="https://git.mig5.net/mig5/enroll" target="_blank" rel="noreferrer">
@ -61,6 +62,7 @@
<div class="list-group">
<a class="list-group-item list-group-item-action" href="#model">Mental model</a>
<a class="list-group-item list-group-item-action" href="#harvest">How harvesting works</a>
<a class="list-group-item list-group-item-action" href="#schema">State schema</a>
<a class="list-group-item list-group-item-action" href="#modes">Single-site vs multi-site</a>
<a class="list-group-item list-group-item-action" href="#remote">Remote harvesting</a>
<a class="list-group-item list-group-item-action" href="#templates">JinjaTurtle templates</a>
@ -158,6 +160,19 @@
</div>
</section>
<section id="schema" class="scroll-mt-nav mb-5">
<h2 class="section-title fw-bold">State schema</h2>
<p class="text-secondary">Enroll writes a <code>state.json</code> file describing what was harvested. The canonical definition of that file format is the JSON Schema below.</p>
<div class="callout p-4">
<div class="d-flex flex-wrap gap-2 align-items-center">
<a class="btn btn-sm btn-dark" href="schema.html"><i class="bi bi-eye"></i> View formatted schema</a>
<a class="btn btn-sm btn-outline-dark" href="schema/state.schema.json"><i class="bi bi-braces"></i> state.schema.json</a>
</div>
</div>
</section>
<section id="modes" class="scroll-mt-nav mb-5">
<h2 class="section-title fw-bold">Single-site vs multi-site</h2>
<p class="text-secondary">Manifest output has two styles. Choose based on how you'll use the result.</p>
@ -521,6 +536,7 @@ sudo journalctl -u enroll-harvest-diff.service -n 200 --no-pager
<ul class="list-unstyled small mb-0">
<li><a class="link-secondary text-decoration-none" href="docs.html">Docs</a></li>
<li><a class="link-secondary text-decoration-none" href="examples.html">Examples</a></li>
<li><a class="link-secondary text-decoration-none" href="schema.html">Schema</a></li>
<li><a class="link-secondary text-decoration-none" href="security.html">Security Design</a></li>
</ul>
</div>

View file

@ -33,6 +33,7 @@
<ul class="navbar-nav ms-auto align-items-lg-center gap-lg-2">
<li class="nav-item"><a class="nav-link" href="docs.html">Docs</a></li>
<li class="nav-item"><a class="nav-link" href="examples.html">Examples</a></li>
<li class="nav-item"><a class="nav-link" href="schema.html">Schema</a></li>
<li class="nav-item"><a class="nav-link" href="security.html">Security Design</a></li>
<li class="nav-item ms-lg-2">
<a class="btn btn-sm btn-outline-dark" href="https://git.mig5.net/mig5/enroll" target="_blank" rel="noreferrer">
@ -174,6 +175,7 @@
<ul class="list-unstyled small mb-0">
<li><a class="link-secondary text-decoration-none" href="docs.html">Docs</a></li>
<li><a class="link-secondary text-decoration-none" href="examples.html">Examples</a></li>
<li><a class="link-secondary text-decoration-none" href="schema.html">Schema</a></li>
<li><a class="link-secondary text-decoration-none" href="security.html">Security Design</a></li>
</ul>
</div>

View file

@ -37,6 +37,7 @@
<ul class="navbar-nav ms-auto align-items-lg-center gap-lg-2">
<li class="nav-item"><a class="nav-link" href="docs.html">Docs</a></li>
<li class="nav-item"><a class="nav-link" href="examples.html">Examples</a></li>
<li class="nav-item"><a class="nav-link" href="schema.html">Schema</a></li>
<li class="nav-item"><a class="nav-link" href="security.html">Security Design</a></li>
<li class="nav-item ms-lg-2">
<a class="btn btn-sm btn-outline-dark" href="https://git.mig5.net/mig5/enroll" target="_blank" rel="noreferrer">

145
src/schema.html Normal file
View file

@ -0,0 +1,145 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Enroll State Schema</title>
<meta name="description" content="JSON Schema describing the Enroll harvest state.json format.">
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<!-- Bootstrap -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet">
<link href="assets/css/site.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-expand-lg bg-white bg-opacity-75 sticky-top border-bottom" data-bs-theme="light">
<div class="container py-1">
<a class="navbar-brand fw-bold d-flex align-items-center gap-2" href="index.html">
<img class="brand-mark" src="assets/img/enroll.svg" alt="Enroll">
<span>Enroll</span>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#nav" aria-controls="nav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="nav">
<ul class="navbar-nav ms-auto align-items-lg-center gap-lg-2">
<li class="nav-item"><a class="nav-link" href="docs.html">Docs</a></li>
<li class="nav-item"><a class="nav-link" href="examples.html">Examples</a></li>
<li class="nav-item"><a class="nav-link" href="schema.html">Schema</a></li>
<li class="nav-item"><a class="nav-link" href="security.html">Security Design</a></li>
<li class="nav-item ms-lg-2">
<a class="btn btn-sm btn-outline-dark" href="https://git.mig5.net/mig5/enroll" target="_blank" rel="noreferrer">
<i class="bi bi-git"></i> Repo
</a>
</li>
</ul>
</div>
</div>
</nav>
<header class="py-5 hero">
<div class="container py-3">
<div class="kicker mb-3"><i class="bi bi-braces"></i> Schema</div>
<h1 class="display-6 fw-bold mb-2">Harvest <code>state.json</code> schema</h1>
<p class="lead mb-0">A machine-readable contract for Enrolls harvest output.</p>
</div>
</header>
<main class="py-5">
<div class="container">
<div class="row g-4">
<div class="col-lg-4">
<div class="callout p-4">
<div class="fw-semibold mb-2">Links</div>
<div class="d-grid gap-2">
<a class="btn btn-sm btn-outline-dark" href="schema/state.schema.json"><i class="bi bi-download"></i> Raw JSON Schema</a>
<a class="btn btn-sm btn-outline-dark" href="docs.html#schema"><i class="bi bi-book"></i> Docs section</a>
</div>
<hr class="my-3">
<p class="small text-secondary mb-0">Whitespace doesnt matter for JSON Schema, so this page pretty-prints the same content youd fetch from the raw link.</p>
</div>
</div>
<div class="col-lg-8">
<div class="feature-card p-4">
<div class="d-flex align-items-center justify-content-between flex-wrap gap-2">
<h2 class="h4 fw-bold mb-0">state.schema.json</h2>
<a class="btn btn-sm btn-outline-secondary" href="schema/state.schema.json" target="_blank" rel="noreferrer"><i class="bi bi-box-arrow-up-right"></i> Open raw</a>
</div>
<div class="codeblock terminal mt-3">
<button class="btn btn-sm btn-outline-secondary copy-btn" data-copy-target="#schema-code"><i class="bi bi-clipboard"></i> Copy</button>
<pre class="mb-0"><code id="schema-code">Loading…</code></pre>
</div>
<div class="small text-secondary mt-3">Tip: you can validate a harvest with <code>python -m jsonschema -i state.json schema/state.schema.json</code> (or use the repositorys CI helper script if you have it).</div>
</div>
</div>
</div>
</div>
</main>
<footer class="py-5">
<div class="container">
<div class="row g-4 align-items-start">
<div class="col-lg-6">
<div class="d-flex align-items-center gap-2 mb-2">
<img class="brand-mark" src="assets/img/enroll.svg" alt="Enroll">
<div class="fw-bold">Enroll (a mig5 project)</div>
<span class="badge badge-soft rounded-pill">CLI</span>
<span class="badge badge-soft rounded-pill">Ansible</span>
</div>
<p class="smallprint mb-3">Reverse-engineering servers into Ansible.</p>
<div class="d-flex flex-wrap gap-2">
<a class="btn btn-sm btn-outline-dark" href="https://git.mig5.net/mig5/enroll" target="_blank" rel="noreferrer"><i class="bi bi-git"></i> Repo</a>
<a class="btn btn-sm btn-outline-dark" href="https://pypi.org/project/enroll/" target="_blank" rel="noreferrer"><i class="bi bi-box"></i> PyPI</a>
</div>
</div>
<div class="col-lg-3">
<div class="fw-semibold mb-2">Site</div>
<ul class="list-unstyled small mb-0">
<li><a class="link-secondary text-decoration-none" href="docs.html">Docs</a></li>
<li><a class="link-secondary text-decoration-none" href="examples.html">Examples</a></li>
<li><a class="link-secondary text-decoration-none" href="schema.html">Schema</a></li>
<li><a class="link-secondary text-decoration-none" href="security.html">Security Design</a></li>
</ul>
</div>
<div class="col-lg-3">
<div class="fw-semibold mb-2">Contact</div>
<ul class="list-unstyled small mb-0">
<li><a class="link-secondary text-decoration-none" href="https://nr.mig5.net/forms/mig5/contact" target="_blank" rel="noreferrer">Form</a></li>
<li><span class="text-secondary">Fediverse:</span> <a class="link-secondary text-decoration-none" href="https://goto.mig5.net/@mig5" target="_blank" rel="noreferrer">@mig5</a></li>
</ul>
</div>
</div>
<hr class="my-4">
<div class="d-flex flex-column flex-md-row justify-content-between align-items-md-center gap-2 small">
<div class="text-secondary">© <span id="year"></span> <a href="https://mig5.net" target="_blank" rel="noopener noreferrer">mig5 system administration</a></div>
</div>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="assets/js/site.js"></script>
<script>
document.getElementById('year').textContent = new Date().getFullYear();
(async () => {
const el = document.getElementById('schema-code');
try {
const res = await fetch('schema/state.schema.json', {cache: 'no-store'});
const obj = await res.json();
el.textContent = JSON.stringify(obj, null, 2);
} catch (e) {
el.textContent = 'Failed to load schema: ' + (e && e.message ? e.message : String(e));
}
})();
</script>
</body>
</html>

View file

@ -0,0 +1,674 @@
{
"$defs": {
"AptConfigSnapshot": {
"allOf": [
{
"$ref": "#/$defs/RoleCommon"
},
{
"properties": {
"role_name": {
"const": "apt_config"
}
},
"type": "object"
}
],
"unevaluatedProperties": false
},
"DnfConfigSnapshot": {
"allOf": [
{
"$ref": "#/$defs/RoleCommon"
},
{
"properties": {
"role_name": {
"const": "dnf_config"
}
},
"type": "object"
}
],
"unevaluatedProperties": false
},
"EtcCustomSnapshot": {
"allOf": [
{
"$ref": "#/$defs/RoleCommon"
},
{
"properties": {
"role_name": {
"const": "etc_custom"
}
},
"type": "object"
}
],
"unevaluatedProperties": false
},
"ExcludedFile": {
"additionalProperties": false,
"properties": {
"path": {
"minLength": 1,
"pattern": "^/.*",
"type": "string"
},
"reason": {
"enum": [
"user_excluded",
"unreadable",
"log_file",
"denied_path",
"too_large",
"not_regular_file",
"binary_like",
"sensitive_content"
],
"type": "string"
}
},
"required": [
"path",
"reason"
],
"type": "object"
},
"ExtraPathsSnapshot": {
"allOf": [
{
"$ref": "#/$defs/RoleCommon"
},
{
"properties": {
"exclude_patterns": {
"items": {
"type": "string"
},
"type": "array"
},
"include_patterns": {
"items": {
"type": "string"
},
"type": "array"
},
"role_name": {
"const": "extra_paths"
}
},
"required": [
"include_patterns",
"exclude_patterns"
],
"type": "object"
}
],
"unevaluatedProperties": false
},
"InstalledPackageInstance": {
"additionalProperties": false,
"properties": {
"arch": {
"minLength": 1,
"type": "string"
},
"version": {
"minLength": 1,
"type": "string"
}
},
"required": [
"version",
"arch"
],
"type": "object"
},
"ManagedDir": {
"additionalProperties": false,
"properties": {
"group": {
"minLength": 1,
"type": "string"
},
"mode": {
"pattern": "^[0-7]{4}$",
"type": "string"
},
"owner": {
"minLength": 1,
"type": "string"
},
"path": {
"minLength": 1,
"pattern": "^/.*",
"type": "string"
},
"reason": {
"enum": [
"parent_of_managed_file",
"user_include_dir"
],
"type": "string"
}
},
"required": [
"path",
"owner",
"group",
"mode",
"reason"
],
"type": "object"
},
"ManagedFile": {
"additionalProperties": false,
"properties": {
"group": {
"minLength": 1,
"type": "string"
},
"mode": {
"pattern": "^[0-7]{4}$",
"type": "string"
},
"owner": {
"minLength": 1,
"type": "string"
},
"path": {
"minLength": 1,
"pattern": "^/.*",
"type": "string"
},
"reason": {
"enum": [
"apt_config",
"apt_keyring",
"apt_signed_by_keyring",
"apt_source",
"authorized_keys",
"cron_snippet",
"custom_specific_path",
"custom_unowned",
"dnf_config",
"logrotate_snippet",
"modified_conffile",
"modified_packaged_file",
"related_timer",
"rpm_gpg_key",
"ssh_public_key",
"system_cron",
"system_firewall",
"system_logrotate",
"system_modprobe",
"system_mounts",
"system_network",
"system_rc",
"system_security",
"system_sysctl",
"systemd_dropin",
"systemd_envfile",
"user_include",
"usr_local_bin_script",
"usr_local_etc_custom",
"yum_conf",
"yum_config",
"yum_repo"
],
"type": "string"
},
"src_rel": {
"minLength": 1,
"pattern": "^[^/].*",
"type": "string"
}
},
"required": [
"path",
"src_rel",
"owner",
"group",
"mode",
"reason"
],
"type": "object"
},
"ObservedVia": {
"oneOf": [
{
"additionalProperties": false,
"properties": {
"kind": {
"const": "user_installed"
}
},
"required": [
"kind"
],
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"kind": {
"const": "systemd_unit"
},
"ref": {
"minLength": 1,
"type": "string"
}
},
"required": [
"kind",
"ref"
],
"type": "object"
},
{
"additionalProperties": false,
"properties": {
"kind": {
"const": "package_role"
},
"ref": {
"minLength": 1,
"type": "string"
}
},
"required": [
"kind",
"ref"
],
"type": "object"
}
]
},
"PackageInventoryEntry": {
"additionalProperties": false,
"properties": {
"arches": {
"items": {
"minLength": 1,
"type": "string"
},
"type": "array"
},
"installations": {
"items": {
"$ref": "#/$defs/InstalledPackageInstance"
},
"type": "array"
},
"observed_via": {
"items": {
"$ref": "#/$defs/ObservedVia"
},
"type": "array"
},
"roles": {
"items": {
"minLength": 1,
"type": "string"
},
"type": "array"
},
"version": {
"type": [
"string",
"null"
]
}
},
"required": [
"version",
"arches",
"installations",
"observed_via",
"roles"
],
"type": "object"
},
"PackageSnapshot": {
"allOf": [
{
"$ref": "#/$defs/RoleCommon"
},
{
"properties": {
"package": {
"minLength": 1,
"type": "string"
}
},
"required": [
"package"
],
"type": "object"
}
],
"unevaluatedProperties": false
},
"RoleCommon": {
"properties": {
"excluded": {
"items": {
"$ref": "#/$defs/ExcludedFile"
},
"type": "array"
},
"managed_dirs": {
"items": {
"$ref": "#/$defs/ManagedDir"
},
"type": "array"
},
"managed_files": {
"items": {
"$ref": "#/$defs/ManagedFile"
},
"type": "array"
},
"notes": {
"items": {
"type": "string"
},
"type": "array"
},
"role_name": {
"minLength": 1,
"pattern": "^[A-Za-z0-9_]+$",
"type": "string"
}
},
"required": [
"role_name",
"managed_dirs",
"managed_files",
"excluded",
"notes"
],
"type": "object"
},
"ServiceSnapshot": {
"allOf": [
{
"$ref": "#/$defs/RoleCommon"
},
{
"properties": {
"active_state": {
"type": [
"string",
"null"
]
},
"condition_result": {
"type": [
"string",
"null"
]
},
"packages": {
"items": {
"minLength": 1,
"type": "string"
},
"type": "array"
},
"role_name": {
"minLength": 1,
"pattern": "^[a-z_][a-z0-9_]*$",
"type": "string"
},
"sub_state": {
"type": [
"string",
"null"
]
},
"unit": {
"minLength": 1,
"type": "string"
},
"unit_file_state": {
"type": [
"string",
"null"
]
}
},
"required": [
"unit",
"packages",
"active_state",
"sub_state",
"unit_file_state",
"condition_result"
],
"type": "object"
}
],
"unevaluatedProperties": false
},
"UserEntry": {
"additionalProperties": false,
"properties": {
"gecos": {
"type": "string"
},
"gid": {
"minimum": 0,
"type": "integer"
},
"home": {
"type": "string"
},
"name": {
"minLength": 1,
"type": "string"
},
"primary_group": {
"minLength": 1,
"type": "string"
},
"shell": {
"type": "string"
},
"supplementary_groups": {
"items": {
"minLength": 1,
"type": "string"
},
"type": "array"
},
"uid": {
"minimum": 0,
"type": "integer"
}
},
"required": [
"name",
"uid",
"gid",
"gecos",
"home",
"shell",
"primary_group",
"supplementary_groups"
],
"type": "object"
},
"UsersSnapshot": {
"allOf": [
{
"$ref": "#/$defs/RoleCommon"
},
{
"properties": {
"role_name": {
"const": "users"
},
"users": {
"items": {
"$ref": "#/$defs/UserEntry"
},
"type": "array"
}
},
"required": [
"users"
],
"type": "object"
}
],
"unevaluatedProperties": false
},
"UsrLocalCustomSnapshot": {
"allOf": [
{
"$ref": "#/$defs/RoleCommon"
},
{
"properties": {
"role_name": {
"const": "usr_local_custom"
}
},
"type": "object"
}
],
"unevaluatedProperties": false
}
},
"$id": "https://enroll.sh/schema/state.schema.json",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"additionalProperties": false,
"properties": {
"enroll": {
"additionalProperties": false,
"properties": {
"harvest_time": {
"minimum": 0,
"type": "integer"
},
"version": {
"type": "string"
}
},
"required": [
"version",
"harvest_time"
],
"type": "object"
},
"host": {
"additionalProperties": false,
"properties": {
"hostname": {
"minLength": 1,
"type": "string"
},
"os": {
"enum": [
"debian",
"redhat",
"unknown"
],
"type": "string"
},
"os_release": {
"additionalProperties": {
"type": "string"
},
"type": "object"
},
"pkg_backend": {
"enum": [
"dpkg",
"rpm"
],
"type": "string"
}
},
"required": [
"hostname",
"os",
"pkg_backend",
"os_release"
],
"type": "object"
},
"inventory": {
"additionalProperties": false,
"properties": {
"packages": {
"additionalProperties": {
"$ref": "#/$defs/PackageInventoryEntry"
},
"type": "object"
}
},
"required": [
"packages"
],
"type": "object"
},
"roles": {
"additionalProperties": false,
"properties": {
"apt_config": {
"$ref": "#/$defs/AptConfigSnapshot"
},
"dnf_config": {
"$ref": "#/$defs/DnfConfigSnapshot"
},
"etc_custom": {
"$ref": "#/$defs/EtcCustomSnapshot"
},
"extra_paths": {
"$ref": "#/$defs/ExtraPathsSnapshot"
},
"packages": {
"items": {
"$ref": "#/$defs/PackageSnapshot"
},
"type": "array"
},
"services": {
"items": {
"$ref": "#/$defs/ServiceSnapshot"
},
"type": "array"
},
"users": {
"$ref": "#/$defs/UsersSnapshot"
},
"usr_local_custom": {
"$ref": "#/$defs/UsrLocalCustomSnapshot"
}
},
"required": [
"users",
"services",
"packages",
"apt_config",
"dnf_config",
"etc_custom",
"usr_local_custom",
"extra_paths"
],
"type": "object"
}
},
"required": [
"enroll",
"host",
"inventory",
"roles"
],
"title": "Enroll harvest state.json schema (latest)",
"type": "object"
}

View file

@ -33,6 +33,7 @@
<ul class="navbar-nav ms-auto align-items-lg-center gap-lg-2">
<li class="nav-item"><a class="nav-link" href="docs.html">Docs</a></li>
<li class="nav-item"><a class="nav-link" href="examples.html">Examples</a></li>
<li class="nav-item"><a class="nav-link" href="schema.html">Schema</a></li>
<li class="nav-item"><a class="nav-link" href="security.html">Security Design</a></li>
<li class="nav-item ms-lg-2">
<a class="btn btn-sm btn-outline-dark" href="https://git.mig5.net/mig5/enroll" target="_blank" rel="noreferrer">
@ -173,6 +174,7 @@
<ul class="list-unstyled small mb-0">
<li><a class="link-secondary text-decoration-none" href="docs.html">Docs</a></li>
<li><a class="link-secondary text-decoration-none" href="examples.html">Examples</a></li>
<li><a class="link-secondary text-decoration-none" href="schema.html">Schema</a></li>
<li><a class="link-secondary text-decoration-none" href="security.html">Security Design</a></li>
</ul>
</div>