provisioning/docs/book/architecture/DATABASE_AND_CONFIG_ARCHITECTURE.html
Jesús Pérez 6a59d34bb1
chore: update provisioning configuration and documentation
Update configuration files, templates, and internal documentation
for the provisioning repository system.

Configuration Updates:
- KMS configuration modernization
- Plugin system settings
- Service port mappings
- Test cluster topologies
- Installation configuration examples
- VM configuration defaults
- Cedar authorization policies

Documentation Updates:
- Library module documentation
- Extension API guides
- AI system documentation
- Service management guides
- Test environment setup
- Plugin usage guides
- Validator configuration documentation

All changes are backward compatible.
2025-12-11 21:50:42 +00:00

533 lines
25 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE HTML>
<html lang="en" class="ayu sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Database and Config Architecture - Provisioning Platform Documentation</title>
<!-- Custom HTML head -->
<meta name="description" content="Complete documentation for the Provisioning Platform - Infrastructure automation with Nushell, KCL, and Rust">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff">
<link rel="icon" href="../favicon.svg">
<link rel="shortcut icon" href="../favicon.png">
<link rel="stylesheet" href="../css/variables.css">
<link rel="stylesheet" href="../css/general.css">
<link rel="stylesheet" href="../css/chrome.css">
<link rel="stylesheet" href="../css/print.css" media="print">
<!-- Fonts -->
<link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
<link rel="stylesheet" href="../fonts/fonts.css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" id="highlight-css" href="../highlight.css">
<link rel="stylesheet" id="tomorrow-night-css" href="../tomorrow-night.css">
<link rel="stylesheet" id="ayu-highlight-css" href="../ayu-highlight.css">
<!-- Custom theme stylesheets -->
<!-- Provide site root and default themes to javascript -->
<script>
const path_to_root = "../";
const default_light_theme = "ayu";
const default_dark_theme = "navy";
</script>
<!-- Start loading toc.js asap -->
<script src="../toc.js"></script>
</head>
<body>
<div id="mdbook-help-container">
<div id="mdbook-help-popup">
<h2 class="mdbook-help-title">Keyboard shortcuts</h2>
<div>
<p>Press <kbd></kbd> or <kbd></kbd> to navigate between chapters</p>
<p>Press <kbd>S</kbd> or <kbd>/</kbd> to search in the book</p>
<p>Press <kbd>?</kbd> to show this help</p>
<p>Press <kbd>Esc</kbd> to hide this help</p>
</div>
</div>
</div>
<div id="body-container">
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script>
try {
let theme = localStorage.getItem('mdbook-theme');
let sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script>
const default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? default_dark_theme : default_light_theme;
let theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
const html = document.documentElement;
html.classList.remove('ayu')
html.classList.add(theme);
html.classList.add("js");
</script>
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
<!-- Hide / unhide sidebar before it is displayed -->
<script>
let sidebar = null;
const sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
} else {
sidebar = 'hidden';
}
sidebar_toggle.checked = sidebar === 'visible';
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<!-- populated by js -->
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
<noscript>
<iframe class="sidebar-iframe-outer" src="../toc.html"></iframe>
</noscript>
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
<div class="sidebar-resize-indicator"></div>
</div>
</nav>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky">
<div class="left-buttons">
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</label>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="default_theme">Auto</button></li>
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
<button id="search-toggle" class="icon-button" type="button" title="Search (`/`)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="/ s" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title">Provisioning Platform Documentation</h1>
<div class="right-buttons">
<a href="../print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
<a href="https://github.com/provisioning/provisioning-platform" title="Git repository" aria-label="Git repository">
<i id="git-repository-button" class="fa fa-github"></i>
</a>
<a href="https://github.com/provisioning/provisioning-platform/edit/main/provisioning/docs/src/architecture/DATABASE_AND_CONFIG_ARCHITECTURE.md" title="Suggest an edit" aria-label="Suggest an edit">
<i id="git-edit-button" class="fa fa-edit"></i>
</a>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script>
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<h1 id="database-and-configuration-architecture"><a class="header" href="#database-and-configuration-architecture">Database and Configuration Architecture</a></h1>
<p><strong>Date</strong>: 2025-10-07
<strong>Status</strong>: ACTIVE DOCUMENTATION</p>
<hr />
<h2 id="control-center-database-dbs"><a class="header" href="#control-center-database-dbs">Control-Center Database (DBS)</a></h2>
<h3 id="database-type-surrealdb-in-memory-backend"><a class="header" href="#database-type-surrealdb-in-memory-backend">Database Type: <strong>SurrealDB</strong> (In-Memory Backend)</a></h3>
<p>Control-Center uses <strong>SurrealDB with kv-mem backend</strong>, an embedded in-memory database - <strong>no separate database server required</strong>.</p>
<h3 id="database-configuration"><a class="header" href="#database-configuration">Database Configuration</a></h3>
<pre><code class="language-toml">[database]
url = "memory" # In-memory backend
namespace = "control_center"
database = "main"
</code></pre>
<p><strong>Storage</strong>: In-memory (data persists during process lifetime)</p>
<p><strong>Production Alternative</strong>: Switch to remote WebSocket connection for persistent storage:</p>
<pre><code class="language-toml">[database]
url = "ws://localhost:8000"
namespace = "control_center"
database = "main"
username = "root"
password = "secret"
</code></pre>
<h3 id="why-surrealdb-kv-mem"><a class="header" href="#why-surrealdb-kv-mem">Why SurrealDB kv-mem?</a></h3>
<div class="table-wrapper"><table><thead><tr><th>Feature</th><th>SurrealDB kv-mem</th><th>RocksDB</th><th>PostgreSQL</th></tr></thead><tbody>
<tr><td><strong>Deployment</strong></td><td>Embedded (no server)</td><td>Embedded</td><td>Server only</td></tr>
<tr><td><strong>Build Deps</strong></td><td>None</td><td>libclang, bzip2</td><td>Many</td></tr>
<tr><td><strong>Docker</strong></td><td>Simple</td><td>Complex</td><td>External service</td></tr>
<tr><td><strong>Performance</strong></td><td>Very fast (memory)</td><td>Very fast (disk)</td><td>Network latency</td></tr>
<tr><td><strong>Use Case</strong></td><td>Dev/test, graphs</td><td>Production K/V</td><td>Relational data</td></tr>
<tr><td><strong>GraphQL</strong></td><td>Built-in</td><td>None</td><td>External</td></tr>
</tbody></table>
</div>
<p><strong>Control-Center choice</strong>: SurrealDB kv-mem for <strong>zero-dependency embedded storage</strong>, perfect for:</p>
<ul>
<li>Policy engine state</li>
<li>Session management</li>
<li>Configuration cache</li>
<li>Audit logs</li>
<li>User credentials</li>
<li>Graph-based policy relationships</li>
</ul>
<h3 id="additional-database-support"><a class="header" href="#additional-database-support">Additional Database Support</a></h3>
<p>Control-Center also supports (via Cargo.toml dependencies):</p>
<ol>
<li>
<p><strong>SurrealDB (WebSocket)</strong> - For production persistent storage</p>
<pre><code class="language-toml">surrealdb = { version = "2.3", features = ["kv-mem", "protocol-ws", "protocol-http"] }
</code></pre>
</li>
<li>
<p><strong>SQLx</strong> - For SQL database backends (optional)</p>
<pre><code class="language-toml">sqlx = { workspace = true }
</code></pre>
</li>
</ol>
<p><strong>Default</strong>: SurrealDB kv-mem (embedded, no extra setup, no build dependencies)</p>
<hr />
<h2 id="orchestrator-database"><a class="header" href="#orchestrator-database">Orchestrator Database</a></h2>
<h3 id="storage-type-filesystem-file-based-queue"><a class="header" href="#storage-type-filesystem-file-based-queue">Storage Type: <strong>Filesystem</strong> (File-based Queue)</a></h3>
<p>Orchestrator uses simple file-based storage by default:</p>
<pre><code class="language-toml">[orchestrator.storage]
type = "filesystem" # Default
backend_path = "{{orchestrator.paths.data_dir}}/queue.rkvs"
</code></pre>
<p><strong>Resolved Path</strong>:</p>
<pre><code>{{workspace.path}}/.orchestrator/data/queue.rkvs
</code></pre>
<h3 id="optional-surrealdb-backend"><a class="header" href="#optional-surrealdb-backend">Optional: SurrealDB Backend</a></h3>
<p>For production deployments, switch to SurrealDB:</p>
<pre><code class="language-toml">[orchestrator.storage]
type = "surrealdb-server" # or surrealdb-embedded
[orchestrator.storage.surrealdb]
url = "ws://localhost:8000"
namespace = "orchestrator"
database = "tasks"
username = "root"
password = "secret"
</code></pre>
<hr />
<h2 id="configuration-loading-architecture"><a class="header" href="#configuration-loading-architecture">Configuration Loading Architecture</a></h2>
<h3 id="hierarchical-configuration-system"><a class="header" href="#hierarchical-configuration-system">Hierarchical Configuration System</a></h3>
<p>All services load configuration in this order (priority: low → high):</p>
<pre><code>1. System Defaults provisioning/config/config.defaults.toml
2. Service Defaults provisioning/platform/{service}/config.defaults.toml
3. Workspace Config workspace/{name}/config/provisioning.yaml
4. User Config ~/Library/Application Support/provisioning/user_config.yaml
5. Environment Variables PROVISIONING_*, CONTROL_CENTER_*, ORCHESTRATOR_*
6. Runtime Overrides --config flag or API updates
</code></pre>
<h3 id="variable-interpolation"><a class="header" href="#variable-interpolation">Variable Interpolation</a></h3>
<p>Configs support dynamic variable interpolation:</p>
<pre><code class="language-toml">[paths]
base = "/Users/Akasha/project-provisioning/provisioning"
data_dir = "{{paths.base}}/data" # Resolves to: /Users/.../data
[database]
url = "rocksdb://{{paths.data_dir}}/control-center.db"
# Resolves to: rocksdb:///Users/.../data/control-center.db
</code></pre>
<p><strong>Supported Variables</strong>:</p>
<ul>
<li><code>{{paths.*}}</code> - Path variables from config</li>
<li><code>{{workspace.path}}</code> - Current workspace path</li>
<li><code>{{env.HOME}}</code> - Environment variables</li>
<li><code>{{now.date}}</code> - Current date/time</li>
<li><code>{{git.branch}}</code> - Git branch name</li>
</ul>
<h3 id="service-specific-config-files"><a class="header" href="#service-specific-config-files">Service-Specific Config Files</a></h3>
<p>Each platform service has its own <code>config.defaults.toml</code>:</p>
<div class="table-wrapper"><table><thead><tr><th>Service</th><th>Config File</th><th>Purpose</th></tr></thead><tbody>
<tr><td><strong>Orchestrator</strong></td><td><code>provisioning/platform/orchestrator/config.defaults.toml</code></td><td>Workflow management, queue settings</td></tr>
<tr><td><strong>Control-Center</strong></td><td><code>provisioning/platform/control-center/config.defaults.toml</code></td><td>Web UI, auth, database</td></tr>
<tr><td><strong>MCP Server</strong></td><td><code>provisioning/platform/mcp-server/config.defaults.toml</code></td><td>AI integration settings</td></tr>
<tr><td><strong>KMS</strong></td><td><code>provisioning/core/services/kms/config.defaults.toml</code></td><td>Key management</td></tr>
</tbody></table>
</div>
<h3 id="central-configuration"><a class="header" href="#central-configuration">Central Configuration</a></h3>
<p><strong>Master config</strong>: <code>provisioning/config/config.defaults.toml</code></p>
<p>Contains:</p>
<ul>
<li>Global paths</li>
<li>Provider configurations</li>
<li>Cache settings</li>
<li>Debug flags</li>
<li>Environment-specific overrides</li>
</ul>
<h3 id="workspace-aware-paths"><a class="header" href="#workspace-aware-paths">Workspace-Aware Paths</a></h3>
<p>All services use workspace-aware paths:</p>
<p><strong>Orchestrator</strong>:</p>
<pre><code class="language-toml">[orchestrator.paths]
base = "{{workspace.path}}/.orchestrator"
data_dir = "{{orchestrator.paths.base}}/data"
logs_dir = "{{orchestrator.paths.base}}/logs"
queue_dir = "{{orchestrator.paths.data_dir}}/queue"
</code></pre>
<p><strong>Control-Center</strong>:</p>
<pre><code class="language-toml">[paths]
base = "{{workspace.path}}/.control-center"
data_dir = "{{paths.base}}/data"
logs_dir = "{{paths.base}}/logs"
</code></pre>
<p><strong>Result</strong> (workspace: <code>workspace-librecloud</code>):</p>
<pre><code>workspace-librecloud/
├── .orchestrator/
│ ├── data/
│ │ └── queue.rkvs
│ └── logs/
└── .control-center/
├── data/
│ └── control-center.db
└── logs/
</code></pre>
<hr />
<h2 id="environment-variable-overrides"><a class="header" href="#environment-variable-overrides">Environment Variable Overrides</a></h2>
<p>Any config value can be overridden via environment variables:</p>
<h3 id="control-center"><a class="header" href="#control-center">Control-Center</a></h3>
<pre><code class="language-bash"># Override server port
export CONTROL_CENTER_SERVER_PORT=8081
# Override database URL
export CONTROL_CENTER_DATABASE_URL="rocksdb:///custom/path/db"
# Override JWT secret
export CONTROL_CENTER_JWT_ISSUER="my-issuer"
</code></pre>
<h3 id="orchestrator"><a class="header" href="#orchestrator">Orchestrator</a></h3>
<pre><code class="language-bash"># Override orchestrator port
export ORCHESTRATOR_SERVER_PORT=8080
# Override storage backend
export ORCHESTRATOR_STORAGE_TYPE="surrealdb-server"
export ORCHESTRATOR_STORAGE_SURREALDB_URL="ws://localhost:8000"
# Override concurrency
export ORCHESTRATOR_QUEUE_MAX_CONCURRENT_TASKS=10
</code></pre>
<h3 id="naming-convention"><a class="header" href="#naming-convention">Naming Convention</a></h3>
<pre><code>{SERVICE}_{SECTION}_{KEY} = value
</code></pre>
<p><strong>Examples</strong>:</p>
<ul>
<li><code>CONTROL_CENTER_SERVER_PORT</code><code>[server] port</code></li>
<li><code>ORCHESTRATOR_QUEUE_MAX_CONCURRENT_TASKS</code><code>[queue] max_concurrent_tasks</code></li>
<li><code>PROVISIONING_DEBUG_ENABLED</code><code>[debug] enabled</code></li>
</ul>
<hr />
<h2 id="docker-vs-native-configuration"><a class="header" href="#docker-vs-native-configuration">Docker vs Native Configuration</a></h2>
<h3 id="docker-deployment"><a class="header" href="#docker-deployment">Docker Deployment</a></h3>
<p><strong>Container paths</strong> (resolved inside container):</p>
<pre><code class="language-toml">[paths]
base = "/app/provisioning"
data_dir = "/data" # Mounted volume
logs_dir = "/var/log/orchestrator" # Mounted volume
</code></pre>
<p><strong>Docker Compose volumes</strong>:</p>
<pre><code class="language-yaml">services:
orchestrator:
volumes:
- orchestrator-data:/data
- orchestrator-logs:/var/log/orchestrator
control-center:
volumes:
- control-center-data:/data
volumes:
orchestrator-data:
orchestrator-logs:
control-center-data:
</code></pre>
<h3 id="native-deployment"><a class="header" href="#native-deployment">Native Deployment</a></h3>
<p><strong>Host paths</strong> (macOS/Linux):</p>
<pre><code class="language-toml">[paths]
base = "/Users/Akasha/project-provisioning/provisioning"
data_dir = "{{workspace.path}}/.orchestrator/data"
logs_dir = "{{workspace.path}}/.orchestrator/logs"
</code></pre>
<hr />
<h2 id="configuration-validation"><a class="header" href="#configuration-validation">Configuration Validation</a></h2>
<p>Check current configuration:</p>
<pre><code class="language-bash"># Show effective configuration
provisioning env
# Show all config and environment
provisioning allenv
# Validate configuration
provisioning validate config
# Show service-specific config
PROVISIONING_DEBUG=true ./orchestrator --show-config
</code></pre>
<hr />
<h2 id="kms-database"><a class="header" href="#kms-database">KMS Database</a></h2>
<p><strong>Cosmian KMS</strong> uses its own database (when deployed):</p>
<pre><code class="language-bash"># KMS database location (Docker)
/data/kms.db # SQLite database inside KMS container
# KMS database location (Native)
{{workspace.path}}/.kms/data/kms.db
</code></pre>
<p>KMS also integrates with Control-Centers KMS hybrid backend (local + remote):</p>
<pre><code class="language-toml">[kms]
mode = "hybrid" # local, remote, or hybrid
[kms.local]
database_path = "{{paths.data_dir}}/kms.db"
[kms.remote]
server_url = "http://localhost:9998" # Cosmian KMS server
</code></pre>
<hr />
<h2 id="summary"><a class="header" href="#summary">Summary</a></h2>
<h3 id="control-center-database"><a class="header" href="#control-center-database">Control-Center Database</a></h3>
<ul>
<li><strong>Type</strong>: RocksDB (embedded)</li>
<li><strong>Location</strong>: <code>{{workspace.path}}/.control-center/data/control-center.db</code></li>
<li><strong>No server required</strong>: Embedded in control-center process</li>
</ul>
<h3 id="orchestrator-database-1"><a class="header" href="#orchestrator-database-1">Orchestrator Database</a></h3>
<ul>
<li><strong>Type</strong>: Filesystem (default) or SurrealDB (production)</li>
<li><strong>Location</strong>: <code>{{workspace.path}}/.orchestrator/data/queue.rkvs</code></li>
<li><strong>Optional server</strong>: SurrealDB for production</li>
</ul>
<h3 id="configuration-loading"><a class="header" href="#configuration-loading">Configuration Loading</a></h3>
<ol>
<li>System defaults (provisioning/config/)</li>
<li>Service defaults (platform/{service}/)</li>
<li>Workspace config</li>
<li>User config</li>
<li>Environment variables</li>
<li>Runtime overrides</li>
</ol>
<h3 id="best-practices"><a class="header" href="#best-practices">Best Practices</a></h3>
<ul>
<li>✅ Use workspace-aware paths</li>
<li>✅ Override via environment variables in Docker</li>
<li>✅ Keep secrets in KMS, not config files</li>
<li>✅ Use RocksDB for single-node deployments</li>
<li>✅ Use SurrealDB for distributed/production deployments</li>
</ul>
<hr />
<p><strong>Related Documentation</strong>:</p>
<ul>
<li>Configuration System: <code>.claude/features/configuration-system.md</code></li>
<li>KMS Architecture: <code>provisioning/platform/control-center/src/kms/README.md</code></li>
<li>Workspace Switching: <code>.claude/features/workspace-switching.md</code></li>
</ul>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../architecture/COMPLIANCE_IMPLEMENTATION_SUMMARY.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../architecture/JWT_AUTH_IMPLEMENTATION.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../architecture/COMPLIANCE_IMPLEMENTATION_SUMMARY.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../architecture/JWT_AUTH_IMPLEMENTATION.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</nav>
</div>
<!-- Livereload script (if served using the cli tool) -->
<script>
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsAddress = wsProtocol + "//" + location.host + "/" + "__livereload";
const socket = new WebSocket(wsAddress);
socket.onmessage = function (event) {
if (event.data === "reload") {
socket.close();
location.reload();
}
};
window.onbeforeunload = function() {
socket.close();
}
</script>
<script>
window.playground_copyable = true;
</script>
<script src="../elasticlunr.min.js"></script>
<script src="../mark.min.js"></script>
<script src="../searcher.js"></script>
<script src="../clipboard.min.js"></script>
<script src="../highlight.js"></script>
<script src="../book.js"></script>
<!-- Custom JS scripts -->
</div>
</body>
</html>