Update to add kekyl
29
.github/workflows/jekyll-gh-pages.yml
vendored
@ -1,12 +1,13 @@
|
||||
# Sample workflow for building and deploying a Jekyll site to GitHub Pages
|
||||
name: Deploy Jekyll with GitHub Pages dependencies preinstalled
|
||||
# Build and deploy Jekyll site from /docs to GitHub Pages
|
||||
name: Deploy Jekyll (docs subdir)
|
||||
|
||||
on:
|
||||
# Runs on pushes targeting the default branch
|
||||
push:
|
||||
branches: ["jekyll"]
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
branches:
|
||||
- "main"
|
||||
paths:
|
||||
- "docs/**"
|
||||
# Allows manual runs from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
||||
@ -15,8 +16,7 @@ permissions:
|
||||
pages: write
|
||||
id-token: write
|
||||
|
||||
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
|
||||
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
|
||||
# Allow only one concurrent deployment; don't cancel in-progress runs
|
||||
concurrency:
|
||||
group: "pages"
|
||||
cancel-in-progress: false
|
||||
@ -28,15 +28,22 @@ jobs:
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v5
|
||||
|
||||
# Build Jekyll from the docs/ directory
|
||||
- name: Build with Jekyll
|
||||
uses: actions/jekyll-build-pages@v1
|
||||
with:
|
||||
source: ./
|
||||
destination: ./_site
|
||||
source: ./docs
|
||||
destination: ./docs/_site
|
||||
|
||||
# Upload the built site from docs/_site
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
with:
|
||||
path: docs/_site/
|
||||
|
||||
# Deployment job
|
||||
deploy:
|
||||
@ -48,4 +55,4 @@ jobs:
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
uses: actions/deploy-pages@v4
|
||||
7612
docs/_site/assets/css/just-the-docs-dark.css
Normal file
1
docs/_site/assets/css/just-the-docs-dark.css.map
Normal file
7612
docs/_site/assets/css/just-the-docs-default.css
Normal file
1
docs/_site/assets/css/just-the-docs-default.css.map
Normal file
3
docs/_site/assets/css/just-the-docs-head-nav.css
Normal file
@ -0,0 +1,3 @@
|
||||
.site-nav ul li a {
|
||||
background-image: linear-gradient(-90deg, rgb(31.6333333333, 30.8222222222, 34.8777777778) 0%, rgba(31.6333333333, 30.8222222222, 34.8777777778, 0.8) 80%, rgba(31.6333333333, 30.8222222222, 34.8777777778, 0) 100%);
|
||||
}
|
||||
7348
docs/_site/assets/css/just-the-docs-light.css
Normal file
1
docs/_site/assets/css/just-the-docs-light.css.map
Normal file
574
docs/_site/assets/js/just-the-docs.js
Normal file
@ -0,0 +1,574 @@
|
||||
(function (jtd, undefined) {
|
||||
|
||||
// Event handling
|
||||
|
||||
jtd.addEvent = function(el, type, handler) {
|
||||
if (el.attachEvent) el.attachEvent('on'+type, handler); else el.addEventListener(type, handler);
|
||||
}
|
||||
jtd.removeEvent = function(el, type, handler) {
|
||||
if (el.detachEvent) el.detachEvent('on'+type, handler); else el.removeEventListener(type, handler);
|
||||
}
|
||||
jtd.onReady = function(ready) {
|
||||
// in case the document is already rendered
|
||||
if (document.readyState!='loading') ready();
|
||||
// modern browsers
|
||||
else if (document.addEventListener) document.addEventListener('DOMContentLoaded', ready);
|
||||
// IE <= 8
|
||||
else document.attachEvent('onreadystatechange', function(){
|
||||
if (document.readyState=='complete') ready();
|
||||
});
|
||||
}
|
||||
|
||||
// Show/hide mobile menu
|
||||
|
||||
function initNav() {
|
||||
jtd.addEvent(document, 'click', function(e){
|
||||
var target = e.target;
|
||||
while (target && !(target.classList && target.classList.contains('nav-list-expander'))) {
|
||||
target = target.parentNode;
|
||||
}
|
||||
if (target) {
|
||||
e.preventDefault();
|
||||
target.ariaPressed = target.parentNode.classList.toggle('active');
|
||||
}
|
||||
});
|
||||
|
||||
const siteNav = document.getElementById('site-nav');
|
||||
const mainHeader = document.getElementById('main-header');
|
||||
const menuButton = document.getElementById('menu-button');
|
||||
|
||||
disableHeadStyleSheets();
|
||||
|
||||
jtd.addEvent(menuButton, 'click', function(e){
|
||||
e.preventDefault();
|
||||
|
||||
if (menuButton.classList.toggle('nav-open')) {
|
||||
siteNav.classList.add('nav-open');
|
||||
mainHeader.classList.add('nav-open');
|
||||
menuButton.ariaPressed = true;
|
||||
} else {
|
||||
siteNav.classList.remove('nav-open');
|
||||
mainHeader.classList.remove('nav-open');
|
||||
menuButton.ariaPressed = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// The <head> element is assumed to include the following stylesheets:
|
||||
// - a <link> to /assets/css/just-the-docs-head-nav.css,
|
||||
// with id 'jtd-head-nav-stylesheet'
|
||||
// - a <style> containing the result of _includes/css/activation.scss.liquid.
|
||||
// To avoid relying on the order of stylesheets (which can change with HTML
|
||||
// compression, user-added JavaScript, and other side effects), stylesheets
|
||||
// are only interacted with via ID
|
||||
|
||||
function disableHeadStyleSheets() {
|
||||
const headNav = document.getElementById('jtd-head-nav-stylesheet');
|
||||
if (headNav) {
|
||||
headNav.disabled = true;
|
||||
}
|
||||
|
||||
const activation = document.getElementById('jtd-nav-activation');
|
||||
if (activation) {
|
||||
activation.disabled = true;
|
||||
}
|
||||
}
|
||||
// Site search
|
||||
|
||||
function initSearch() {
|
||||
var request = new XMLHttpRequest();
|
||||
request.open('GET', '/assets/js/search-data.json', true);
|
||||
|
||||
request.onload = function(){
|
||||
if (request.status >= 200 && request.status < 400) {
|
||||
var docs = JSON.parse(request.responseText);
|
||||
|
||||
lunr.tokenizer.separator = /[\s\-/]+/
|
||||
|
||||
var index = lunr(function(){
|
||||
this.ref('id');
|
||||
this.field('title', { boost: 200 });
|
||||
this.field('content', { boost: 2 });
|
||||
this.field('relUrl');
|
||||
this.metadataWhitelist = ['position']
|
||||
|
||||
for (var i in docs) {
|
||||
|
||||
this.add({
|
||||
id: i,
|
||||
title: docs[i].title,
|
||||
content: docs[i].content,
|
||||
relUrl: docs[i].relUrl
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
searchLoaded(index, docs);
|
||||
} else {
|
||||
console.log('Error loading ajax request. Request status:' + request.status);
|
||||
}
|
||||
};
|
||||
|
||||
request.onerror = function(){
|
||||
console.log('There was a connection error');
|
||||
};
|
||||
|
||||
request.send();
|
||||
}
|
||||
|
||||
function searchLoaded(index, docs) {
|
||||
var index = index;
|
||||
var docs = docs;
|
||||
var searchInput = document.getElementById('search-input');
|
||||
var searchResults = document.getElementById('search-results');
|
||||
var mainHeader = document.getElementById('main-header');
|
||||
var currentInput;
|
||||
var currentSearchIndex = 0;
|
||||
|
||||
function showSearch() {
|
||||
document.documentElement.classList.add('search-active');
|
||||
}
|
||||
|
||||
function hideSearch() {
|
||||
document.documentElement.classList.remove('search-active');
|
||||
}
|
||||
|
||||
function update() {
|
||||
currentSearchIndex++;
|
||||
|
||||
var input = searchInput.value;
|
||||
if (input === '') {
|
||||
hideSearch();
|
||||
} else {
|
||||
showSearch();
|
||||
// scroll search input into view, workaround for iOS Safari
|
||||
window.scroll(0, -1);
|
||||
setTimeout(function(){ window.scroll(0, 0); }, 0);
|
||||
}
|
||||
if (input === currentInput) {
|
||||
return;
|
||||
}
|
||||
currentInput = input;
|
||||
searchResults.innerHTML = '';
|
||||
if (input === '') {
|
||||
return;
|
||||
}
|
||||
|
||||
var results = index.query(function (query) {
|
||||
var tokens = lunr.tokenizer(input)
|
||||
query.term(tokens, {
|
||||
boost: 10
|
||||
});
|
||||
query.term(tokens, {
|
||||
wildcard: lunr.Query.wildcard.TRAILING
|
||||
});
|
||||
});
|
||||
|
||||
if ((results.length == 0) && (input.length > 2)) {
|
||||
var tokens = lunr.tokenizer(input).filter(function(token, i) {
|
||||
return token.str.length < 20;
|
||||
})
|
||||
if (tokens.length > 0) {
|
||||
results = index.query(function (query) {
|
||||
query.term(tokens, {
|
||||
editDistance: Math.round(Math.sqrt(input.length / 2 - 1))
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (results.length == 0) {
|
||||
var noResultsDiv = document.createElement('div');
|
||||
noResultsDiv.classList.add('search-no-result');
|
||||
noResultsDiv.innerText = 'No results found';
|
||||
searchResults.appendChild(noResultsDiv);
|
||||
|
||||
} else {
|
||||
var resultsList = document.createElement('ul');
|
||||
resultsList.classList.add('search-results-list');
|
||||
searchResults.appendChild(resultsList);
|
||||
|
||||
addResults(resultsList, results, 0, 10, 100, currentSearchIndex);
|
||||
}
|
||||
|
||||
function addResults(resultsList, results, start, batchSize, batchMillis, searchIndex) {
|
||||
if (searchIndex != currentSearchIndex) {
|
||||
return;
|
||||
}
|
||||
for (var i = start; i < (start + batchSize); i++) {
|
||||
if (i == results.length) {
|
||||
return;
|
||||
}
|
||||
addResult(resultsList, results[i]);
|
||||
}
|
||||
setTimeout(function() {
|
||||
addResults(resultsList, results, start + batchSize, batchSize, batchMillis, searchIndex);
|
||||
}, batchMillis);
|
||||
}
|
||||
|
||||
function addResult(resultsList, result) {
|
||||
var doc = docs[result.ref];
|
||||
|
||||
var resultsListItem = document.createElement('li');
|
||||
resultsListItem.classList.add('search-results-list-item');
|
||||
resultsList.appendChild(resultsListItem);
|
||||
|
||||
var resultLink = document.createElement('a');
|
||||
resultLink.classList.add('search-result');
|
||||
resultLink.setAttribute('href', doc.url);
|
||||
resultsListItem.appendChild(resultLink);
|
||||
|
||||
var resultTitle = document.createElement('div');
|
||||
resultTitle.classList.add('search-result-title');
|
||||
resultLink.appendChild(resultTitle);
|
||||
|
||||
// note: the SVG svg-doc is only loaded as a Jekyll include if site.search_enabled is true; see _includes/icons/icons.html
|
||||
var resultDoc = document.createElement('div');
|
||||
resultDoc.classList.add('search-result-doc');
|
||||
resultDoc.innerHTML = '<svg viewBox="0 0 24 24" class="search-result-icon"><use xlink:href="#svg-doc"></use></svg>';
|
||||
resultTitle.appendChild(resultDoc);
|
||||
|
||||
var resultDocTitle = document.createElement('div');
|
||||
resultDocTitle.classList.add('search-result-doc-title');
|
||||
resultDocTitle.innerHTML = doc.doc;
|
||||
resultDoc.appendChild(resultDocTitle);
|
||||
var resultDocOrSection = resultDocTitle;
|
||||
|
||||
if (doc.doc != doc.title) {
|
||||
resultDoc.classList.add('search-result-doc-parent');
|
||||
var resultSection = document.createElement('div');
|
||||
resultSection.classList.add('search-result-section');
|
||||
resultSection.innerHTML = doc.title;
|
||||
resultTitle.appendChild(resultSection);
|
||||
resultDocOrSection = resultSection;
|
||||
}
|
||||
|
||||
var metadata = result.matchData.metadata;
|
||||
var titlePositions = [];
|
||||
var contentPositions = [];
|
||||
for (var j in metadata) {
|
||||
var meta = metadata[j];
|
||||
if (meta.title) {
|
||||
var positions = meta.title.position;
|
||||
for (var k in positions) {
|
||||
titlePositions.push(positions[k]);
|
||||
}
|
||||
}
|
||||
if (meta.content) {
|
||||
var positions = meta.content.position;
|
||||
for (var k in positions) {
|
||||
var position = positions[k];
|
||||
var previewStart = position[0];
|
||||
var previewEnd = position[0] + position[1];
|
||||
var ellipsesBefore = true;
|
||||
var ellipsesAfter = true;
|
||||
for (var k = 0; k < 5; k++) {
|
||||
var nextSpace = doc.content.lastIndexOf(' ', previewStart - 2);
|
||||
var nextDot = doc.content.lastIndexOf('. ', previewStart - 2);
|
||||
if ((nextDot >= 0) && (nextDot > nextSpace)) {
|
||||
previewStart = nextDot + 1;
|
||||
ellipsesBefore = false;
|
||||
break;
|
||||
}
|
||||
if (nextSpace < 0) {
|
||||
previewStart = 0;
|
||||
ellipsesBefore = false;
|
||||
break;
|
||||
}
|
||||
previewStart = nextSpace + 1;
|
||||
}
|
||||
for (var k = 0; k < 10; k++) {
|
||||
var nextSpace = doc.content.indexOf(' ', previewEnd + 1);
|
||||
var nextDot = doc.content.indexOf('. ', previewEnd + 1);
|
||||
if ((nextDot >= 0) && (nextDot < nextSpace)) {
|
||||
previewEnd = nextDot;
|
||||
ellipsesAfter = false;
|
||||
break;
|
||||
}
|
||||
if (nextSpace < 0) {
|
||||
previewEnd = doc.content.length;
|
||||
ellipsesAfter = false;
|
||||
break;
|
||||
}
|
||||
previewEnd = nextSpace;
|
||||
}
|
||||
contentPositions.push({
|
||||
highlight: position,
|
||||
previewStart: previewStart, previewEnd: previewEnd,
|
||||
ellipsesBefore: ellipsesBefore, ellipsesAfter: ellipsesAfter
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (titlePositions.length > 0) {
|
||||
titlePositions.sort(function(p1, p2){ return p1[0] - p2[0] });
|
||||
resultDocOrSection.innerHTML = '';
|
||||
addHighlightedText(resultDocOrSection, doc.title, 0, doc.title.length, titlePositions);
|
||||
}
|
||||
|
||||
if (contentPositions.length > 0) {
|
||||
contentPositions.sort(function(p1, p2){ return p1.highlight[0] - p2.highlight[0] });
|
||||
var contentPosition = contentPositions[0];
|
||||
var previewPosition = {
|
||||
highlight: [contentPosition.highlight],
|
||||
previewStart: contentPosition.previewStart, previewEnd: contentPosition.previewEnd,
|
||||
ellipsesBefore: contentPosition.ellipsesBefore, ellipsesAfter: contentPosition.ellipsesAfter
|
||||
};
|
||||
var previewPositions = [previewPosition];
|
||||
for (var j = 1; j < contentPositions.length; j++) {
|
||||
contentPosition = contentPositions[j];
|
||||
if (previewPosition.previewEnd < contentPosition.previewStart) {
|
||||
previewPosition = {
|
||||
highlight: [contentPosition.highlight],
|
||||
previewStart: contentPosition.previewStart, previewEnd: contentPosition.previewEnd,
|
||||
ellipsesBefore: contentPosition.ellipsesBefore, ellipsesAfter: contentPosition.ellipsesAfter
|
||||
}
|
||||
previewPositions.push(previewPosition);
|
||||
} else {
|
||||
previewPosition.highlight.push(contentPosition.highlight);
|
||||
previewPosition.previewEnd = contentPosition.previewEnd;
|
||||
previewPosition.ellipsesAfter = contentPosition.ellipsesAfter;
|
||||
}
|
||||
}
|
||||
|
||||
var resultPreviews = document.createElement('div');
|
||||
resultPreviews.classList.add('search-result-previews');
|
||||
resultLink.appendChild(resultPreviews);
|
||||
|
||||
var content = doc.content;
|
||||
for (var j = 0; j < Math.min(previewPositions.length, 3); j++) {
|
||||
var position = previewPositions[j];
|
||||
|
||||
var resultPreview = document.createElement('div');
|
||||
resultPreview.classList.add('search-result-preview');
|
||||
resultPreviews.appendChild(resultPreview);
|
||||
|
||||
if (position.ellipsesBefore) {
|
||||
resultPreview.appendChild(document.createTextNode('... '));
|
||||
}
|
||||
addHighlightedText(resultPreview, content, position.previewStart, position.previewEnd, position.highlight);
|
||||
if (position.ellipsesAfter) {
|
||||
resultPreview.appendChild(document.createTextNode(' ...'));
|
||||
}
|
||||
}
|
||||
}
|
||||
var resultRelUrl = document.createElement('span');
|
||||
resultRelUrl.classList.add('search-result-rel-url');
|
||||
resultRelUrl.innerText = doc.relUrl;
|
||||
resultTitle.appendChild(resultRelUrl);
|
||||
}
|
||||
|
||||
function addHighlightedText(parent, text, start, end, positions) {
|
||||
var index = start;
|
||||
for (var i in positions) {
|
||||
var position = positions[i];
|
||||
var span = document.createElement('span');
|
||||
span.innerHTML = text.substring(index, position[0]);
|
||||
parent.appendChild(span);
|
||||
index = position[0] + position[1];
|
||||
var highlight = document.createElement('span');
|
||||
highlight.classList.add('search-result-highlight');
|
||||
highlight.innerHTML = text.substring(position[0], index);
|
||||
parent.appendChild(highlight);
|
||||
}
|
||||
var span = document.createElement('span');
|
||||
span.innerHTML = text.substring(index, end);
|
||||
parent.appendChild(span);
|
||||
}
|
||||
}
|
||||
|
||||
jtd.addEvent(searchInput, 'focus', function(){
|
||||
setTimeout(update, 0);
|
||||
});
|
||||
|
||||
jtd.addEvent(searchInput, 'keyup', function(e){
|
||||
switch (e.keyCode) {
|
||||
case 27: // When esc key is pressed, hide the results and clear the field
|
||||
searchInput.value = '';
|
||||
break;
|
||||
case 38: // arrow up
|
||||
case 40: // arrow down
|
||||
case 13: // enter
|
||||
e.preventDefault();
|
||||
return;
|
||||
}
|
||||
update();
|
||||
});
|
||||
|
||||
jtd.addEvent(searchInput, 'keydown', function(e){
|
||||
switch (e.keyCode) {
|
||||
case 38: // arrow up
|
||||
e.preventDefault();
|
||||
var active = document.querySelector('.search-result.active');
|
||||
if (active) {
|
||||
active.classList.remove('active');
|
||||
if (active.parentElement.previousSibling) {
|
||||
var previous = active.parentElement.previousSibling.querySelector('.search-result');
|
||||
previous.classList.add('active');
|
||||
}
|
||||
}
|
||||
return;
|
||||
case 40: // arrow down
|
||||
e.preventDefault();
|
||||
var active = document.querySelector('.search-result.active');
|
||||
if (active) {
|
||||
if (active.parentElement.nextSibling) {
|
||||
var next = active.parentElement.nextSibling.querySelector('.search-result');
|
||||
active.classList.remove('active');
|
||||
next.classList.add('active');
|
||||
}
|
||||
} else {
|
||||
var next = document.querySelector('.search-result');
|
||||
if (next) {
|
||||
next.classList.add('active');
|
||||
}
|
||||
}
|
||||
return;
|
||||
case 13: // enter
|
||||
e.preventDefault();
|
||||
var active = document.querySelector('.search-result.active');
|
||||
if (active) {
|
||||
active.click();
|
||||
} else {
|
||||
var first = document.querySelector('.search-result');
|
||||
if (first) {
|
||||
first.click();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
jtd.addEvent(document, 'click', function(e){
|
||||
if (e.target != searchInput) {
|
||||
hideSearch();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Switch theme
|
||||
|
||||
jtd.getTheme = function() {
|
||||
var cssFileHref = document.querySelector('[rel="stylesheet"]').getAttribute('href');
|
||||
return cssFileHref.substring(cssFileHref.lastIndexOf('-') + 1, cssFileHref.length - 4);
|
||||
}
|
||||
|
||||
jtd.setTheme = function(theme) {
|
||||
var cssFile = document.querySelector('[rel="stylesheet"]');
|
||||
cssFile.setAttribute('href', '/assets/css/just-the-docs-' + theme + '.css');
|
||||
}
|
||||
|
||||
// Note: pathname can have a trailing slash on a local jekyll server
|
||||
// and not have the slash on GitHub Pages
|
||||
|
||||
function navLink() {
|
||||
var pathname = document.location.pathname;
|
||||
|
||||
var navLink = document.getElementById('site-nav').querySelector('a[href="' + pathname + '"]');
|
||||
if (navLink) {
|
||||
return navLink;
|
||||
}
|
||||
|
||||
// The `permalink` setting may produce navigation links whose `href` ends with `/` or `.html`.
|
||||
// To find these links when `/` is omitted from or added to pathname, or `.html` is omitted:
|
||||
|
||||
if (pathname.endsWith('/') && pathname != '/') {
|
||||
pathname = pathname.slice(0, -1);
|
||||
}
|
||||
|
||||
if (pathname != '/') {
|
||||
navLink = document.getElementById('site-nav').querySelector('a[href="' + pathname + '"], a[href="' + pathname + '/"], a[href="' + pathname + '.html"]');
|
||||
if (navLink) {
|
||||
return navLink;
|
||||
}
|
||||
}
|
||||
|
||||
return null; // avoids `undefined`
|
||||
}
|
||||
|
||||
// Scroll site-nav to ensure the link to the current page is visible
|
||||
|
||||
function scrollNav() {
|
||||
const targetLink = navLink();
|
||||
if (targetLink) {
|
||||
targetLink.scrollIntoView({ block: "center" });
|
||||
targetLink.removeAttribute('href');
|
||||
}
|
||||
}
|
||||
|
||||
// Find the nav-list-link that refers to the current page
|
||||
// then make it and all enclosing nav-list-item elements active.
|
||||
|
||||
function activateNav() {
|
||||
var target = navLink();
|
||||
if (target) {
|
||||
target.classList.toggle('active', true);
|
||||
}
|
||||
while (target) {
|
||||
while (target && !(target.classList && target.classList.contains('nav-list-item'))) {
|
||||
target = target.parentNode;
|
||||
}
|
||||
if (target) {
|
||||
target.classList.toggle('active', true);
|
||||
target = target.parentNode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Document ready
|
||||
|
||||
jtd.onReady(function(){
|
||||
if (document.getElementById('site-nav')) {
|
||||
initNav();
|
||||
activateNav();
|
||||
scrollNav();
|
||||
}
|
||||
initSearch();
|
||||
});
|
||||
|
||||
// Copy button on code
|
||||
|
||||
jtd.onReady(function(){
|
||||
|
||||
if (!window.isSecureContext) {
|
||||
console.log('Window does not have a secure context, therefore code clipboard copy functionality will not be available. For more details see https://web.dev/async-clipboard/#security-and-permissions');
|
||||
return;
|
||||
}
|
||||
|
||||
var codeBlocks = document.querySelectorAll('div.highlighter-rouge, div.listingblock > div.content, figure.highlight');
|
||||
|
||||
// note: the SVG svg-copied and svg-copy is only loaded as a Jekyll include if site.enable_copy_code_button is true; see _includes/icons/icons.html
|
||||
var svgCopied = '<svg viewBox="0 0 24 24" class="copy-icon"><use xlink:href="#svg-copied"></use></svg>';
|
||||
var svgCopy = '<svg viewBox="0 0 24 24" class="copy-icon"><use xlink:href="#svg-copy"></use></svg>';
|
||||
|
||||
codeBlocks.forEach(codeBlock => {
|
||||
var copyButton = document.createElement('button');
|
||||
var timeout = null;
|
||||
copyButton.type = 'button';
|
||||
copyButton.ariaLabel = 'Copy code to clipboard';
|
||||
copyButton.innerHTML = svgCopy;
|
||||
codeBlock.append(copyButton);
|
||||
|
||||
copyButton.addEventListener('click', function () {
|
||||
if(timeout === null) {
|
||||
var code = (codeBlock.querySelector('pre:not(.lineno, .highlight)') || codeBlock.querySelector('code')).innerText;
|
||||
window.navigator.clipboard.writeText(code);
|
||||
|
||||
copyButton.innerHTML = svgCopied;
|
||||
|
||||
var timeoutSetting = 4000;
|
||||
|
||||
timeout = setTimeout(function () {
|
||||
copyButton.innerHTML = svgCopy;
|
||||
timeout = null;
|
||||
}, timeoutSetting);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
})(window.jtd = window.jtd || {});
|
||||
|
||||
|
||||
2
docs/_site/assets/js/search-data.json
Normal file
@ -0,0 +1,2 @@
|
||||
{
|
||||
}
|
||||
61
docs/_site/assets/js/vendor/lunr.min.js
vendored
Normal file
154
docs/_site/batman-adv-setup.md
Normal file
@ -0,0 +1,154 @@
|
||||
# Setup batman-adv
|
||||
B.A.T.M.A.N. advanced (often referenced as batman-adv) is an implementation of the B.A.T.M.A.N. routing protocol in form of a linux kernel module operating on layer 2.
|
||||
|
||||
B.A.T.M.A.N. is a proactive routing protocol for Wireless Ad-hoc Mesh Networks, including (but not limited to) Mobile Ad-hoc Networks (MANETs). The protocol proactively maintains information about the existence of all nodes in the mesh that are accessible via single-hop or multi-hop communication links. The strategy of B.A.T.M.A.N. is to determine for each destination in the mesh one single-hop neighbor, which can be utilized as best gateway to communicate with the destination node. In order to perform multi-hop IP-based routing, the routing table of a node must contain a link-local gateway for each host or network route. To learn about the best next-hop for each destination is all that the B.A.T.M.A.N. algorithm cares about. There is no need to find out or calculate the complete route, which makes a very fast and efficient implementation possible.
|
||||
|
||||
**Important: Follow the 802.11s Wizard through the WebUI BEFORE setting up batman-adv**
|
||||
|
||||
## Setup Server Gateway
|
||||
The "Server" Gateway node type is used to uplink internet access or connect another non mesh network to your 802.11s mesh. You will want to follow the 802.11s Mesh Wizard using the `Mesh Gate` mode.
|
||||
|
||||
Navigate to the LuCI web interface in your web browser, typically at http://10.42.0.1
|
||||
|
||||
From the sidebar menu goto **Network > Interfaces**
|
||||
|
||||

|
||||
|
||||
Click on **Add New Interface** Button
|
||||
|
||||

|
||||
|
||||
From the drop-down menu `Protocol` select `Batman Device`. Give the device a name like **bat0**. Then click Create interface. This will create a tunnel device called `bat0`. OSI Layer 2 packets/frames sent over the the tunnel will be routed using the batman protocol. Next, we will be prompted to configure some batman-specific options. You can edit these options again later by clicking the Edit button next to the bat0 interface.
|
||||
|
||||

|
||||
|
||||
You can accept most of the default options. Navigate to the `Mesh Routing` tab to make a few recommended changes.
|
||||
|
||||

|
||||
|
||||
First, select the Routing Algorithm you want. You want to use BATMAN_V. All mesh nodes must be configured to use the same version of the batman protocol. Next, check the box to Avoid Bridge Loops. Set Gateway Mode to `Server` to explicitly tell batman that this router will be a gateway to the Internet. (Later, when configuring client nodes, we will set Gateway Mode to Client.)
|
||||
|
||||
Save your changes. You are finished setting up the bat0 device. Next we will create an alias interface for associating a radio with the batman mesh. Click Add new interface... again.
|
||||
|
||||

|
||||
|
||||
Select Batman Interface from the dropdown menu. Give the interface a name like batmesh. Important: do not associate the interface with a device in this menu. You will associate the interface with the bat0 batman device in the next step. For now, leave Device as unspecified and click Create Interface.
|
||||
|
||||

|
||||
|
||||
After you create the `batmesh_radio0` interface you will be prompted to configure its settings. Here you can set the Batman Device to bat0. You should not need to change any of the other default options.
|
||||
|
||||
`Save & Apply` your interface changes.
|
||||
|
||||

|
||||
|
||||
### Bridging batman to the mesh
|
||||
You will most likely want to bridge the `bat0` tunnel interface with the halow network. This is required to have batman-adv handle all of the routing across the 802.11s network.
|
||||
|
||||
Navigate to `Network > Interaces` and then click on the `Devices` tab.
|
||||
|
||||

|
||||
|
||||
**Note**: if the `bat0` device does not appear in the list of devices and you have just created it in the previous step, you may have to click `Save & Apply` for the `bat0` device to actually be created. Once you can see the `bat0` device in the list, click the Configure... button next to the `br-ahwlan` bridge device.
|
||||
|
||||

|
||||
|
||||
Check boxes next to the interfaces you want to be bridged. Due to an idiosyncracy of OpenWRT, you will not see any wireless interfaces here. Don't worry, we will connect them to the LAN in a later step. For now, as in this example, check the `bat0` device in addition to any lan ethernet ports. Then click `Save`.
|
||||
|
||||
### Configurating a radio for mesh
|
||||
|
||||
Navigate to `Network > Wireless`
|
||||
|
||||

|
||||
|
||||
Create a **New** Wireless network on your 802.11ah wireless card. This will most likely be the wireless card labeled `radio0`
|
||||
|
||||
Configure your mesh mode, Mesh ID, and the network to associate it with. Mode should be `802.11s`, Your `Mesh ID` can be anything you want, but all nodes in your mesh **must** be the same `Mesh ID`. Your network should be `batmesh_radio0` or whatever you called your interface.
|
||||
|
||||
You can also configure your operating frequency bandwith and channel from here. **Your bandwidth and channel must match on all mesh nodes**
|
||||
|
||||

|
||||
|
||||
Click on the `Wireless Security` tab to configure your `Encryption` type and `Key`. I strongly recommend using WPA3-SAE.
|
||||
|
||||

|
||||
|
||||
Click on the `Mesh Settings` tab. You will want to **uncheck** `Forward mesh peer traffic`. This is required to allow traffic to be routed by batman-adv. You also want to check `Mesh Gate` since this is your gateway node.
|
||||
|
||||

|
||||
|
||||
`Save & Apply` all of your settings.
|
||||
|
||||
## Setup Client Node
|
||||
On your client node follow the 802.11s Mesh setup wizard for a `Mesh Point`
|
||||
|
||||
From the sidebar menu goto **Network > Interfaces**
|
||||
|
||||

|
||||
|
||||
Click on **Add New Interface** Button
|
||||
|
||||

|
||||
|
||||
From the drop-down menu `Protocol` select `Batman Device`. Give the device a name like **bat0**. Then click Create interface. This will create a tunnel device called `bat0`. OSI Layer 2 packets/frames sent over the the tunnel will be routed using the batman protocol. Next, we will be prompted to configure some batman-specific options. You can edit these options again later by clicking the Edit button next to the bat0 interface.
|
||||
|
||||

|
||||
|
||||
You can accept most of the default options. Navigate to the `Mesh Routing` tab to make a few recommended changes.
|
||||
|
||||

|
||||
|
||||
First, select the Routing Algorithm you want. You want to use BATMAN_V. All mesh nodes must be configured to use the same version of the batman protocol. Next, check the box to Avoid Bridge Loops. Set Gateway Mode to `Client` to explicitly tell batman that this is a client node.
|
||||
|
||||
Save your changes. You are finished setting up the bat0 device. Next we will create an alias interface for associating a radio with the batman mesh. Click Add new interface... again.
|
||||
|
||||

|
||||
|
||||
Select Batman Interface from the dropdown menu. Give the interface a name like batmesh. Important: do not associate the interface with a device in this menu. You will associate the interface with the bat0 batman device in the next step. For now, leave Device as unspecified and click Create Interface.
|
||||
|
||||

|
||||
|
||||
After you create the `batmesh_radio0` interface you will be prompted to configure its settings. Here you can set the Batman Device to bat0. You should not need to change any of the other default options.
|
||||
|
||||
`Save & Apply` your interface changes.
|
||||
|
||||

|
||||
|
||||
### Bridging batman to the mesh
|
||||
You will most likely want to bridge the `bat0` tunnel interface with the halow network. This is required to have batman-adv handle all of the routing across the 802.11s network.
|
||||
|
||||
Navigate to `Network > Interaces` and then click on the `Devices` tab.
|
||||
|
||||

|
||||
|
||||
**Note**: if the `bat0` device does not appear in the list of devices and you have just created it in the previous step, you may have to click `Save & Apply` for the `bat0` device to actually be created. Once you can see the `bat0` device in the list, click the Configure... button next to the `br-ahwlan` bridge device.
|
||||
|
||||

|
||||
|
||||
Check boxes next to the interfaces you want to be bridged. Due to an idiosyncracy of OpenWRT, you will not see any wireless interfaces here. Don't worry, we will connect them to the LAN in a later step. For now, as in this example, check the `bat0` device in addition to any lan ethernet ports. Then click `Save`.
|
||||
|
||||
### Configurating a radio for mesh
|
||||
|
||||
Navigate to `Network > Wireless`
|
||||
|
||||

|
||||
|
||||
Create a **New** Wireless network on your 802.11ah wireless card. This will most likely be the wireless card labeled `radio0`
|
||||
|
||||
Configure your mesh mode, Mesh ID, and the network to associate it with. Mode should be `802.11s`, Your `Mesh ID` can be anything you want, but all nodes in your mesh **must** be the same `Mesh ID`. Your network should be `batmesh_radio0` or whatever you called your interface.
|
||||
|
||||
You can also configure your operating frequency bandwith and channel from here. **Your bandwidth and channel must match on all mesh nodes**
|
||||
|
||||

|
||||
|
||||
Click on the `Wireless Security` tab to configure your `Encryption` type and `Key`. I strongly recommend using WPA3-SAE.
|
||||
|
||||

|
||||
|
||||
Click on the `Mesh Settings` tab. You will want to **uncheck** `Forward mesh peer traffic`. This is required to allow traffic to be routed by batman-adv. You also want to check `Mesh Gate` since this is your gateway node.
|
||||
|
||||

|
||||
|
||||
`Save & Apply` all of your settings.
|
||||
|
||||
Once you have both a server and client node configured and assuming they are in range you should see them automatically associate with each other and you have successfully created an 802.11s mesh with B.A.T.M.A.N ADV. Congratulations!
|
||||
1
docs/_site/feed.xml
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="http://localhost:4000/feed.xml" rel="self" type="application/atom+xml" /><link href="http://localhost:4000/" rel="alternate" type="text/html" /><updated>2025-09-11T14:57:38-06:00</updated><id>http://localhost:4000/feed.xml</id><title type="html">OpenMANET</title><subtitle>OpenMANET is a Raspberry Pi–based MANET radio built on Wi-Fi HaLow (802.11ah). Designed around Morse Micro HATs/SDIO boards and OpenWrt, it supports 802.11s + batman-adv, GPSD-driven range tests, and a PTT app—aiming for a rugged, field-ready kit. A custom BCF enables ~27 dBm TX power vs Seeed defaults.</subtitle></feed>
|
||||
BIN
docs/_site/img/Add-bat0-device.png
Normal file
|
After Width: | Height: | Size: 80 KiB |
BIN
docs/_site/img/bat0-device-config.png
Normal file
|
After Width: | Height: | Size: 57 KiB |
BIN
docs/_site/img/bat0-interface.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
docs/_site/img/bat0-mesh-routing-client.png
Normal file
|
After Width: | Height: | Size: 250 KiB |
BIN
docs/_site/img/bat0-mesh-routing-server.png
Normal file
|
After Width: | Height: | Size: 225 KiB |
BIN
docs/_site/img/batman-inferface-create-detail.png
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
docs/_site/img/batman-interface-creation.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
docs/_site/img/batman-save-interfaces.png
Normal file
|
After Width: | Height: | Size: 135 KiB |
BIN
docs/_site/img/bridge-bat0-to-halow.png
Normal file
|
After Width: | Height: | Size: 107 KiB |
BIN
docs/_site/img/bridge-lan-to-mesh.png
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
docs/_site/img/configure-mesh-security.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
docs/_site/img/configure-mesh-settings-client.png
Normal file
|
After Width: | Height: | Size: 107 KiB |
BIN
docs/_site/img/configure-mesh-settings-server.png
Normal file
|
After Width: | Height: | Size: 95 KiB |
BIN
docs/_site/img/configure-mesh-wifi-for-batman.png
Normal file
|
After Width: | Height: | Size: 133 KiB |
BIN
docs/_site/img/wireless-overview.png
Normal file
|
After Width: | Height: | Size: 83 KiB |