2509 lines
217 KiB
HTML
2509 lines
217 KiB
HTML
|
<!DOCTYPE html><html lang="fr">
|
|||
|
<head><meta charset="utf-8">
|
|||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|||
|
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"><title>EndeavourOS Virt-Manager Complete Edition (VMM KVM QEMU) - YannStatic</title>
|
|||
|
|
|||
|
<meta name="description" content="">
|
|||
|
<link rel="canonical" href="https://static.rnmkcy.eu/2023/05/31/EndeavourOS-Virt-Manager_Complete_Edition.html"><link rel="alternate" type="application/rss+xml" title="YannStatic" href="/feed.xml">
|
|||
|
|
|||
|
<!-- - include head/favicon.html - -->
|
|||
|
<link rel="shortcut icon" type="image/png" href="/assets/favicon/favicon.png"><link rel="stylesheet" href="/assets/css/main.css"><link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.13/css/all.css" ><!-- start custom head snippets --><link rel="stylesheet" href="/assets/css/expand.css">
|
|||
|
<!-- end custom head snippets --><script>(function() {
|
|||
|
window.isArray = function(val) {
|
|||
|
return Object.prototype.toString.call(val) === '[object Array]';
|
|||
|
};
|
|||
|
window.isString = function(val) {
|
|||
|
return typeof val === 'string';
|
|||
|
};
|
|||
|
|
|||
|
window.hasEvent = function(event) {
|
|||
|
return 'on'.concat(event) in window.document;
|
|||
|
};
|
|||
|
|
|||
|
window.isOverallScroller = function(node) {
|
|||
|
return node === document.documentElement || node === document.body || node === window;
|
|||
|
};
|
|||
|
|
|||
|
window.isFormElement = function(node) {
|
|||
|
var tagName = node.tagName;
|
|||
|
return tagName === 'INPUT' || tagName === 'SELECT' || tagName === 'TEXTAREA';
|
|||
|
};
|
|||
|
|
|||
|
window.pageLoad = (function () {
|
|||
|
var loaded = false, cbs = [];
|
|||
|
window.addEventListener('load', function () {
|
|||
|
var i;
|
|||
|
loaded = true;
|
|||
|
if (cbs.length > 0) {
|
|||
|
for (i = 0; i < cbs.length; i++) {
|
|||
|
cbs[i]();
|
|||
|
}
|
|||
|
}
|
|||
|
});
|
|||
|
return {
|
|||
|
then: function(cb) {
|
|||
|
cb && (loaded ? cb() : (cbs.push(cb)));
|
|||
|
}
|
|||
|
};
|
|||
|
})();
|
|||
|
})();
|
|||
|
(function() {
|
|||
|
window.throttle = function(func, wait) {
|
|||
|
var args, result, thisArg, timeoutId, lastCalled = 0;
|
|||
|
|
|||
|
function trailingCall() {
|
|||
|
lastCalled = new Date;
|
|||
|
timeoutId = null;
|
|||
|
result = func.apply(thisArg, args);
|
|||
|
}
|
|||
|
return function() {
|
|||
|
var now = new Date,
|
|||
|
remaining = wait - (now - lastCalled);
|
|||
|
|
|||
|
args = arguments;
|
|||
|
thisArg = this;
|
|||
|
|
|||
|
if (remaining <= 0) {
|
|||
|
clearTimeout(timeoutId);
|
|||
|
timeoutId = null;
|
|||
|
lastCalled = now;
|
|||
|
result = func.apply(thisArg, args);
|
|||
|
} else if (!timeoutId) {
|
|||
|
timeoutId = setTimeout(trailingCall, remaining);
|
|||
|
}
|
|||
|
return result;
|
|||
|
};
|
|||
|
};
|
|||
|
})();
|
|||
|
(function() {
|
|||
|
var Set = (function() {
|
|||
|
var add = function(item) {
|
|||
|
var i, data = this._data;
|
|||
|
for (i = 0; i < data.length; i++) {
|
|||
|
if (data[i] === item) {
|
|||
|
return;
|
|||
|
}
|
|||
|
}
|
|||
|
this.size ++;
|
|||
|
data.push(item);
|
|||
|
return data;
|
|||
|
};
|
|||
|
|
|||
|
var Set = function(data) {
|
|||
|
this.size = 0;
|
|||
|
this._data = [];
|
|||
|
var i;
|
|||
|
if (data.length > 0) {
|
|||
|
for (i = 0; i < data.length; i++) {
|
|||
|
add.call(this, data[i]);
|
|||
|
}
|
|||
|
}
|
|||
|
};
|
|||
|
Set.prototype.add = add;
|
|||
|
Set.prototype.get = function(index) { return this._data[index]; };
|
|||
|
Set.prototype.has = function(item) {
|
|||
|
var i, data = this._data;
|
|||
|
for (i = 0; i < data.length; i++) {
|
|||
|
if (this.get(i) === item) {
|
|||
|
return true;
|
|||
|
}
|
|||
|
}
|
|||
|
return false;
|
|||
|
};
|
|||
|
Set.prototype.is = function(map) {
|
|||
|
if (map._data.length !== this._data.length) { return false; }
|
|||
|
var i, j, flag, tData = this._data, mData = map._data;
|
|||
|
for (i = 0; i < tData.length; i++) {
|
|||
|
for (flag = false, j = 0; j < mData.length; j++) {
|
|||
|
if (tData[i] === mData[j]) {
|
|||
|
flag = true;
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
if (!flag) { return false; }
|
|||
|
}
|
|||
|
return true;
|
|||
|
};
|
|||
|
Set.prototype.values = function() {
|
|||
|
return this._data;
|
|||
|
};
|
|||
|
return Set;
|
|||
|
})();
|
|||
|
|
|||
|
window.Lazyload = (function(doc) {
|
|||
|
var queue = {js: [], css: []}, sources = {js: {}, css: {}}, context = this;
|
|||
|
var createNode = function(name, attrs) {
|
|||
|
var node = doc.createElement(name), attr;
|
|||
|
for (attr in attrs) {
|
|||
|
if (attrs.hasOwnProperty(attr)) {
|
|||
|
node.setAttribute(attr, attrs[attr]);
|
|||
|
}
|
|||
|
}
|
|||
|
return node;
|
|||
|
};
|
|||
|
var end = function(type, url) {
|
|||
|
var s, q, qi, cbs, i, j, cur, val, flag;
|
|||
|
if (type === 'js' || type ==='css') {
|
|||
|
s = sources[type], q = queue[type];
|
|||
|
s[url] = true;
|
|||
|
for (i = 0; i < q.length; i++) {
|
|||
|
cur = q[i];
|
|||
|
if (cur.urls.has(url)) {
|
|||
|
qi = cur, val = qi.urls.values();
|
|||
|
qi && (cbs = qi.callbacks);
|
|||
|
for (flag = true, j = 0; j < val.length; j++) {
|
|||
|
cur = val[j];
|
|||
|
if (!s[cur]) {
|
|||
|
flag = false;
|
|||
|
}
|
|||
|
}
|
|||
|
if (flag && cbs && cbs.length > 0) {
|
|||
|
for (j = 0; j < cbs.length; j++) {
|
|||
|
cbs[j].call(context);
|
|||
|
}
|
|||
|
qi.load = true;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
};
|
|||
|
var load = function(type, urls, callback) {
|
|||
|
var s, q, qi, node, i, cur,
|
|||
|
_urls = typeof urls === 'string' ? new Set([urls]) : new Set(urls), val, url;
|
|||
|
if (type === 'js' || type ==='css') {
|
|||
|
s = sources[type], q = queue[type];
|
|||
|
for (i = 0; i < q.length; i++) {
|
|||
|
cur = q[i];
|
|||
|
if (_urls.is(cur.urls)) {
|
|||
|
qi = cur;
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
val = _urls.values();
|
|||
|
if (qi) {
|
|||
|
callback && (qi.load || qi.callbacks.push(callback));
|
|||
|
callback && (qi.load && callback());
|
|||
|
} else {
|
|||
|
q.push({
|
|||
|
urls: _urls,
|
|||
|
callbacks: callback ? [callback] : [],
|
|||
|
load: false
|
|||
|
});
|
|||
|
for (i = 0; i < val.length; i++) {
|
|||
|
node = null, url = val[i];
|
|||
|
if (s[url] === undefined) {
|
|||
|
(type === 'js' ) && (node = createNode('script', { src: url }));
|
|||
|
(type === 'css') && (node = createNode('link', { rel: 'stylesheet', href: url }));
|
|||
|
if (node) {
|
|||
|
node.onload = (function(type, url) {
|
|||
|
return function() {
|
|||
|
end(type, url);
|
|||
|
};
|
|||
|
})(type, url);
|
|||
|
(doc.head || doc.body).appendChild(node);
|
|||
|
s[url] = false;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
};
|
|||
|
return {
|
|||
|
js: function(url, callback) {
|
|||
|
load('js', url, callback);
|
|||
|
},
|
|||
|
css: function(url, callback) {
|
|||
|
load('css', url, callback);
|
|||
|
}
|
|||
|
};
|
|||
|
})(this.document);
|
|||
|
})();
|
|||
|
</script><script>
|
|||
|
(function() {
|
|||
|
var TEXT_VARIABLES = {
|
|||
|
version: '2.2.6',
|
|||
|
sources: {
|
|||
|
font_awesome: 'https://use.fontawesome.com/releases/v5.0.13/css/all.css',
|
|||
|
jquery: '/assets/js/jquery.min.js',
|
|||
|
leancloud_js_sdk: '//cdn.jsdelivr.net/npm/leancloud-storage@3.13.2/dist/av-min.js',
|
|||
|
chart: 'https://cdn.bootcss.com/Chart.js/2.7.2/Chart.bundle.min.js',
|
|||
|
gitalk: {
|
|||
|
js: 'https://cdn.bootcss.com/gitalk/1.2.2/gitalk.min.js',
|
|||
|
css: 'https://cdn.bootcss.com/gitalk/1.2.2/gitalk.min.css'
|
|||
|
},
|
|||
|
valine: 'https://unpkg.com/valine/dist/Valine.min.js'
|
|||
|
},
|
|||
|
site: {
|
|||
|
toc: {
|
|||
|
selectors: 'h1,h2,h3'
|
|||
|
}
|
|||
|
},
|
|||
|
paths: {
|
|||
|
search_js: '/assets/search.js'
|
|||
|
}
|
|||
|
};
|
|||
|
window.TEXT_VARIABLES = TEXT_VARIABLES;
|
|||
|
})();
|
|||
|
</script>
|
|||
|
</head>
|
|||
|
<body>
|
|||
|
<div class="root" data-is-touch="false">
|
|||
|
<div class="layout--page js-page-root">
|
|||
|
<!----><div class="page__main js-page-main page__viewport hide-footer has-aside has-aside cell cell--auto">
|
|||
|
|
|||
|
<div class="page__main-inner">
|
|||
|
<div class="page__header d-print-none">
|
|||
|
<header class="header"><div class="main">
|
|||
|
<div class="header__title">
|
|||
|
<div class="header__brand">
|
|||
|
<svg id="svg" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="400" height="478.9473684210526" viewbox="0, 0, 400,478.9473684210526"><g id="svgg"><path id="path0" d="M308.400 56.805 C 306.970 56.966,303.280 57.385,300.200 57.738 C 290.906 58.803,278.299 59.676,269.200 59.887 L 260.600 60.085 259.400 61.171 C 258.010 62.428,256.198 63.600,255.645 63.600 C 255.070 63.600,252.887 65.897,252.598 66.806 C 252.460 67.243,252.206 67.600,252.034 67.600 C 251.397 67.600,247.206 71.509,247.202 72.107 C 247.201 72.275,246.390 73.190,245.400 74.138 C 243.961 75.517,243.598 76.137,243.592 77.231 C 243.579 79.293,241.785 83.966,240.470 85.364 C 239.176 86.740,238.522 88.365,237.991 91.521 C 237.631 93.665,236.114 97.200,235.554 97.200 C 234.938 97.200,232.737 102.354,232.450 104.472 C 232.158 106.625,230.879 109.226,229.535 110.400 C 228.933 110.926,228.171 113.162,226.434 119.500 C 226.178 120.435,225.795 121.200,225.584 121.200 C 225.373 121.200,225.200 121.476,225.200 121.813 C 225.200 122.149,224.885 122.541,224.500 122.683 C 223.606 123.013,223.214 123.593,223.204 124.600 C 223.183 126.555,220.763 132.911,219.410 134.562 C 218.443 135.742,217.876 136.956,217.599 138.440 C 217.041 141.424,215.177 146.434,214.532 146.681 C 214.240 146.794,214.000 147.055,214.000 147.261 C 214.000 147.467,213.550 148.086,213.000 148.636 C 212.450 149.186,212.000 149.893,212.000 150.208 C 212.000 151.386,208.441 154.450,207.597 153.998 C 206.319 153.315,204.913 150.379,204.633 147.811 C 204.365 145.357,202.848 142.147,201.759 141.729 C 200.967 141.425,199.200 137.451,199.200 135.974 C 199.200 134.629,198.435 133.224,196.660 131.311 C 195.363 129.913,194.572 128.123,193.870 125.000 C 193.623 123.900,193.236 122.793,193.010 122.540 C 190.863 120.133,190.147 118.880,188.978 115.481 C 188.100 112.928,187.151 111.003,186.254 109.955 C 185.358 108.908,184.518 107.204,183.847 105.073 C 183.280 103.273,182.497 101.329,182.108 100.753 C 181.719 100.177,180.904 98.997,180.298 98.131 C 179.693 97.265,178.939 95.576,178.624 94.378 C 178.041 92.159,177.125 90.326,175.023 87.168 C 174.375 86.196,173.619 84.539,173.342 83.486 C 172.800 81.429,171.529 79.567,170.131 78.785 C 169.654 78.517,168.697 77.511,168.006 76.549 C 167.316 75.587,166.594 74.800,166.402 74.800 C 166.210 74.800,164.869 73.633,163.421 72.206 C 160.103 68.936,161.107 69.109,146.550 69.301 C 133.437 69.474,128.581 70.162,126.618 72.124 C 126.248 72.495,125.462 72.904,124.872 73.033 C 124.282 73.163,123.088 73.536,122.219 73.863 C 121.349 74.191,119.028 74.638,117.061 74.858 C 113.514 75.254,109.970 76.350,108.782 77.419 C 107.652 78.436,100.146 80.400,97.388 80.400 C 95.775 80.400,93.167 81.360,91.200 82.679 C 90.430 83.195,89.113 83.804,88.274 84.031 C 85.875 84.681,78.799 90.910,74.400 96.243 L 73.400 97.456 73.455 106.028 C 73.526 117.055,74.527 121.238,77.820 124.263 C 78.919 125.273,80.400 127.902,80.400 128.842 C 80.400 129.202,81.075 130.256,81.900 131.186 C 83.563 133.059,85.497 136.346,86.039 138.216 C 86.233 138.886,87.203 140.207,88.196 141.153 C 89.188 142.098,90.000 143.104,90.000 143.388 C 90.000 144.337,92.129 148.594,92.869 149.123 C 93.271 149.410,93.600 149.831,93.600 150.059 C 93.600 150.286,93.932 150.771,94.337 151.136 C 94.743 151.501,95.598 153.004,96.237 154.475 C 96.877 155.947,97.760 157.351,98.200 157.596 C 98.640 157.841,99.900 159.943,101.000 162.267 C 102.207 164.817,103.327 166.644,103.825 166.876 C 104.278 167.087,105.065 168.101,105.573 169.130 C 107.658 173.348,108.097 174.093,110.006 176.647 C 111.103 178.114,112.000 179.725,112.000 180.227 C 112.000 181.048,113.425 183.163,114.678 184.200 C 115.295 184.711,117.396 188.733,117.720 190.022 C 117.855 190.562,118.603 191.633,119.381 192.402 C 120.160 193.171,121.496 195.258,122.351 197.039 C 123.206 198.820,124.167 200.378,124.487 200.501 C 124.807 200.624,125.953 202.496,127.034 204.662 C 128.114 206.828,129.676 209.299,130.505 210.153 C 131.333 211.007,132.124 212.177,132.262 212.753 C 132.618 214.239,134.291 217.048,136.288 219.516 C 137.230 220.679,138.000 221.92
|
|||
|
" href="/">YannStatic</a>
|
|||
|
</div>
|
|||
|
<!--<button class="button button--secondary button--circle search-button js-search-toggle"><i class="fas fa-search"></i></button>--><!-- <li><button class="button button--secondary button--circle search-button js-search-toggle"><i class="fas fa-search"></i></button></li> -->
|
|||
|
<!-- Champ de recherche -->
|
|||
|
<div id="searchbox" class="search search--dark" style="visibility: visible">
|
|||
|
<div class="main">
|
|||
|
<div class="search__header"></div>
|
|||
|
<div class="search-bar">
|
|||
|
<div class="search-box js-search-box">
|
|||
|
<div class="search-box__icon-search"><i class="fas fa-search"></i></div>
|
|||
|
<input id="search-input" type="text">
|
|||
|
<!-- <div class="search-box__icon-clear js-icon-clear">
|
|||
|
<a><i class="fas fa-times"></i></a>
|
|||
|
</div> -->
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
<!-- Script pointing to search-script.js -->
|
|||
|
<script>/*!
|
|||
|
* Simple-Jekyll-Search
|
|||
|
* Copyright 2015-2020, Christian Fei
|
|||
|
* Licensed under the MIT License.
|
|||
|
*/
|
|||
|
|
|||
|
(function(){
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$Templater_7 = {
|
|||
|
compile: compile,
|
|||
|
setOptions: setOptions
|
|||
|
}
|
|||
|
|
|||
|
const options = {}
|
|||
|
options.pattern = /\{(.*?)\}/g
|
|||
|
options.template = ''
|
|||
|
options.middleware = function () {}
|
|||
|
|
|||
|
function setOptions (_options) {
|
|||
|
options.pattern = _options.pattern || options.pattern
|
|||
|
options.template = _options.template || options.template
|
|||
|
if (typeof _options.middleware === 'function') {
|
|||
|
options.middleware = _options.middleware
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function compile (data) {
|
|||
|
return options.template.replace(options.pattern, function (match, prop) {
|
|||
|
const value = options.middleware(prop, data[prop], options.template)
|
|||
|
if (typeof value !== 'undefined') {
|
|||
|
return value
|
|||
|
}
|
|||
|
return data[prop] || match
|
|||
|
})
|
|||
|
}
|
|||
|
|
|||
|
'use strict';
|
|||
|
|
|||
|
function fuzzysearch (needle, haystack) {
|
|||
|
var tlen = haystack.length;
|
|||
|
var qlen = needle.length;
|
|||
|
if (qlen > tlen) {
|
|||
|
return false;
|
|||
|
}
|
|||
|
if (qlen === tlen) {
|
|||
|
return needle === haystack;
|
|||
|
}
|
|||
|
outer: for (var i = 0, j = 0; i < qlen; i++) {
|
|||
|
var nch = needle.charCodeAt(i);
|
|||
|
while (j < tlen) {
|
|||
|
if (haystack.charCodeAt(j++) === nch) {
|
|||
|
continue outer;
|
|||
|
}
|
|||
|
}
|
|||
|
return false;
|
|||
|
}
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
var _$fuzzysearch_1 = fuzzysearch;
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
/* removed: const _$fuzzysearch_1 = require('fuzzysearch') */;
|
|||
|
|
|||
|
var _$FuzzySearchStrategy_5 = new FuzzySearchStrategy()
|
|||
|
|
|||
|
function FuzzySearchStrategy () {
|
|||
|
this.matches = function (string, crit) {
|
|||
|
return _$fuzzysearch_1(crit.toLowerCase(), string.toLowerCase())
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$LiteralSearchStrategy_6 = new LiteralSearchStrategy()
|
|||
|
|
|||
|
function LiteralSearchStrategy () {
|
|||
|
this.matches = function (str, crit) {
|
|||
|
if (!str) return false
|
|||
|
|
|||
|
str = str.trim().toLowerCase()
|
|||
|
crit = crit.trim().toLowerCase()
|
|||
|
|
|||
|
return crit.split(' ').filter(function (word) {
|
|||
|
return str.indexOf(word) >= 0
|
|||
|
}).length === crit.split(' ').length
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$Repository_4 = {
|
|||
|
put: put,
|
|||
|
clear: clear,
|
|||
|
search: search,
|
|||
|
setOptions: __setOptions_4
|
|||
|
}
|
|||
|
|
|||
|
/* removed: const _$FuzzySearchStrategy_5 = require('./SearchStrategies/FuzzySearchStrategy') */;
|
|||
|
/* removed: const _$LiteralSearchStrategy_6 = require('./SearchStrategies/LiteralSearchStrategy') */;
|
|||
|
|
|||
|
function NoSort () {
|
|||
|
return 0
|
|||
|
}
|
|||
|
|
|||
|
const data = []
|
|||
|
let opt = {}
|
|||
|
|
|||
|
opt.fuzzy = false
|
|||
|
opt.limit = 10
|
|||
|
opt.searchStrategy = opt.fuzzy ? _$FuzzySearchStrategy_5 : _$LiteralSearchStrategy_6
|
|||
|
opt.sort = NoSort
|
|||
|
opt.exclude = []
|
|||
|
|
|||
|
function put (data) {
|
|||
|
if (isObject(data)) {
|
|||
|
return addObject(data)
|
|||
|
}
|
|||
|
if (isArray(data)) {
|
|||
|
return addArray(data)
|
|||
|
}
|
|||
|
return undefined
|
|||
|
}
|
|||
|
function clear () {
|
|||
|
data.length = 0
|
|||
|
return data
|
|||
|
}
|
|||
|
|
|||
|
function isObject (obj) {
|
|||
|
return Boolean(obj) && Object.prototype.toString.call(obj) === '[object Object]'
|
|||
|
}
|
|||
|
|
|||
|
function isArray (obj) {
|
|||
|
return Boolean(obj) && Object.prototype.toString.call(obj) === '[object Array]'
|
|||
|
}
|
|||
|
|
|||
|
function addObject (_data) {
|
|||
|
data.push(_data)
|
|||
|
return data
|
|||
|
}
|
|||
|
|
|||
|
function addArray (_data) {
|
|||
|
const added = []
|
|||
|
clear()
|
|||
|
for (let i = 0, len = _data.length; i < len; i++) {
|
|||
|
if (isObject(_data[i])) {
|
|||
|
added.push(addObject(_data[i]))
|
|||
|
}
|
|||
|
}
|
|||
|
return added
|
|||
|
}
|
|||
|
|
|||
|
function search (crit) {
|
|||
|
if (!crit) {
|
|||
|
return []
|
|||
|
}
|
|||
|
return findMatches(data, crit, opt.searchStrategy, opt).sort(opt.sort)
|
|||
|
}
|
|||
|
|
|||
|
function __setOptions_4 (_opt) {
|
|||
|
opt = _opt || {}
|
|||
|
|
|||
|
opt.fuzzy = _opt.fuzzy || false
|
|||
|
opt.limit = _opt.limit || 10
|
|||
|
opt.searchStrategy = _opt.fuzzy ? _$FuzzySearchStrategy_5 : _$LiteralSearchStrategy_6
|
|||
|
opt.sort = _opt.sort || NoSort
|
|||
|
opt.exclude = _opt.exclude || []
|
|||
|
}
|
|||
|
|
|||
|
function findMatches (data, crit, strategy, opt) {
|
|||
|
const matches = []
|
|||
|
for (let i = 0; i < data.length && matches.length < opt.limit; i++) {
|
|||
|
const match = findMatchesInObject(data[i], crit, strategy, opt)
|
|||
|
if (match) {
|
|||
|
matches.push(match)
|
|||
|
}
|
|||
|
}
|
|||
|
return matches
|
|||
|
}
|
|||
|
|
|||
|
function findMatchesInObject (obj, crit, strategy, opt) {
|
|||
|
for (const key in obj) {
|
|||
|
if (!isExcluded(obj[key], opt.exclude) && strategy.matches(obj[key], crit)) {
|
|||
|
return obj
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function isExcluded (term, excludedTerms) {
|
|||
|
for (let i = 0, len = excludedTerms.length; i < len; i++) {
|
|||
|
const excludedTerm = excludedTerms[i]
|
|||
|
if (new RegExp(excludedTerm).test(term)) {
|
|||
|
return true
|
|||
|
}
|
|||
|
}
|
|||
|
return false
|
|||
|
}
|
|||
|
|
|||
|
/* globals ActiveXObject:false */
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$JSONLoader_2 = {
|
|||
|
load: load
|
|||
|
}
|
|||
|
|
|||
|
function load (location, callback) {
|
|||
|
const xhr = getXHR()
|
|||
|
xhr.open('GET', location, true)
|
|||
|
xhr.onreadystatechange = createStateChangeListener(xhr, callback)
|
|||
|
xhr.send()
|
|||
|
}
|
|||
|
|
|||
|
function createStateChangeListener (xhr, callback) {
|
|||
|
return function () {
|
|||
|
if (xhr.readyState === 4 && xhr.status === 200) {
|
|||
|
try {
|
|||
|
callback(null, JSON.parse(xhr.responseText))
|
|||
|
} catch (err) {
|
|||
|
callback(err, null)
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function getXHR () {
|
|||
|
return window.XMLHttpRequest ? new window.XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP')
|
|||
|
}
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$OptionsValidator_3 = function OptionsValidator (params) {
|
|||
|
if (!validateParams(params)) {
|
|||
|
throw new Error('-- OptionsValidator: required options missing')
|
|||
|
}
|
|||
|
|
|||
|
if (!(this instanceof OptionsValidator)) {
|
|||
|
return new OptionsValidator(params)
|
|||
|
}
|
|||
|
|
|||
|
const requiredOptions = params.required
|
|||
|
|
|||
|
this.getRequiredOptions = function () {
|
|||
|
return requiredOptions
|
|||
|
}
|
|||
|
|
|||
|
this.validate = function (parameters) {
|
|||
|
const errors = []
|
|||
|
requiredOptions.forEach(function (requiredOptionName) {
|
|||
|
if (typeof parameters[requiredOptionName] === 'undefined') {
|
|||
|
errors.push(requiredOptionName)
|
|||
|
}
|
|||
|
})
|
|||
|
return errors
|
|||
|
}
|
|||
|
|
|||
|
function validateParams (params) {
|
|||
|
if (!params) {
|
|||
|
return false
|
|||
|
}
|
|||
|
return typeof params.required !== 'undefined' && params.required instanceof Array
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$utils_9 = {
|
|||
|
merge: merge,
|
|||
|
isJSON: isJSON
|
|||
|
}
|
|||
|
|
|||
|
function merge (defaultParams, mergeParams) {
|
|||
|
const mergedOptions = {}
|
|||
|
for (const option in defaultParams) {
|
|||
|
mergedOptions[option] = defaultParams[option]
|
|||
|
if (typeof mergeParams[option] !== 'undefined') {
|
|||
|
mergedOptions[option] = mergeParams[option]
|
|||
|
}
|
|||
|
}
|
|||
|
return mergedOptions
|
|||
|
}
|
|||
|
|
|||
|
function isJSON (json) {
|
|||
|
try {
|
|||
|
if (json instanceof Object && JSON.parse(JSON.stringify(json))) {
|
|||
|
return true
|
|||
|
}
|
|||
|
return false
|
|||
|
} catch (err) {
|
|||
|
return false
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
var _$src_8 = {};
|
|||
|
(function (window) {
|
|||
|
'use strict'
|
|||
|
|
|||
|
let options = {
|
|||
|
searchInput: null,
|
|||
|
resultsContainer: null,
|
|||
|
json: [],
|
|||
|
success: Function.prototype,
|
|||
|
searchResultTemplate: '<li><a href="{url}" title="{desc}">{title}</a></li>',
|
|||
|
templateMiddleware: Function.prototype,
|
|||
|
sortMiddleware: function () {
|
|||
|
return 0
|
|||
|
},
|
|||
|
noResultsText: 'No results found',
|
|||
|
limit: 10,
|
|||
|
fuzzy: false,
|
|||
|
debounceTime: null,
|
|||
|
exclude: []
|
|||
|
}
|
|||
|
|
|||
|
let debounceTimerHandle
|
|||
|
const debounce = function (func, delayMillis) {
|
|||
|
if (delayMillis) {
|
|||
|
clearTimeout(debounceTimerHandle)
|
|||
|
debounceTimerHandle = setTimeout(func, delayMillis)
|
|||
|
} else {
|
|||
|
func.call()
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
const requiredOptions = ['searchInput', 'resultsContainer', 'json']
|
|||
|
|
|||
|
/* removed: const _$Templater_7 = require('./Templater') */;
|
|||
|
/* removed: const _$Repository_4 = require('./Repository') */;
|
|||
|
/* removed: const _$JSONLoader_2 = require('./JSONLoader') */;
|
|||
|
const optionsValidator = _$OptionsValidator_3({
|
|||
|
required: requiredOptions
|
|||
|
})
|
|||
|
/* removed: const _$utils_9 = require('./utils') */;
|
|||
|
|
|||
|
window.SimpleJekyllSearch = function (_options) {
|
|||
|
const errors = optionsValidator.validate(_options)
|
|||
|
if (errors.length > 0) {
|
|||
|
throwError('You must specify the following required options: ' + requiredOptions)
|
|||
|
}
|
|||
|
|
|||
|
options = _$utils_9.merge(options, _options)
|
|||
|
|
|||
|
_$Templater_7.setOptions({
|
|||
|
template: options.searchResultTemplate,
|
|||
|
middleware: options.templateMiddleware
|
|||
|
})
|
|||
|
|
|||
|
_$Repository_4.setOptions({
|
|||
|
fuzzy: options.fuzzy,
|
|||
|
limit: options.limit,
|
|||
|
sort: options.sortMiddleware,
|
|||
|
exclude: options.exclude
|
|||
|
})
|
|||
|
|
|||
|
if (_$utils_9.isJSON(options.json)) {
|
|||
|
initWithJSON(options.json)
|
|||
|
} else {
|
|||
|
initWithURL(options.json)
|
|||
|
}
|
|||
|
|
|||
|
const rv = {
|
|||
|
search: search
|
|||
|
}
|
|||
|
|
|||
|
typeof options.success === 'function' && options.success.call(rv)
|
|||
|
return rv
|
|||
|
}
|
|||
|
|
|||
|
function initWithJSON (json) {
|
|||
|
_$Repository_4.put(json)
|
|||
|
registerInput()
|
|||
|
}
|
|||
|
|
|||
|
function initWithURL (url) {
|
|||
|
_$JSONLoader_2.load(url, function (err, json) {
|
|||
|
if (err) {
|
|||
|
throwError('failed to get JSON (' + url + ')')
|
|||
|
}
|
|||
|
initWithJSON(json)
|
|||
|
})
|
|||
|
}
|
|||
|
|
|||
|
function emptyResultsContainer () {
|
|||
|
options.resultsContainer.innerHTML = ''
|
|||
|
}
|
|||
|
|
|||
|
function appendToResultsContainer (text) {
|
|||
|
options.resultsContainer.innerHTML += text
|
|||
|
}
|
|||
|
|
|||
|
function registerInput () {
|
|||
|
options.searchInput.addEventListener('input', function (e) {
|
|||
|
if (isWhitelistedKey(e.which)) {
|
|||
|
emptyResultsContainer()
|
|||
|
debounce(function () { search(e.target.value) }, options.debounceTime)
|
|||
|
}
|
|||
|
})
|
|||
|
}
|
|||
|
|
|||
|
function search (query) {
|
|||
|
if (isValidQuery(query)) {
|
|||
|
emptyResultsContainer()
|
|||
|
render(_$Repository_4.search(query), query)
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function render (results, query) {
|
|||
|
const len = results.length
|
|||
|
if (len === 0) {
|
|||
|
return appendToResultsContainer(options.noResultsText)
|
|||
|
}
|
|||
|
for (let i = 0; i < len; i++) {
|
|||
|
results[i].query = query
|
|||
|
appendToResultsContainer(_$Templater_7.compile(results[i]))
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function isValidQuery (query) {
|
|||
|
return query && query.length > 0
|
|||
|
}
|
|||
|
|
|||
|
function isWhitelistedKey (key) {
|
|||
|
return [13, 16, 20, 37, 38, 39, 40, 91].indexOf(key) === -1
|
|||
|
}
|
|||
|
|
|||
|
function throwError (message) {
|
|||
|
throw new Error('SimpleJekyllSearch --- ' + message)
|
|||
|
}
|
|||
|
})(window)
|
|||
|
|
|||
|
}());
|
|||
|
</script>
|
|||
|
|
|||
|
<!-- Configuration -->
|
|||
|
<script>
|
|||
|
SimpleJekyllSearch({
|
|||
|
searchInput: document.getElementById('search-input'),
|
|||
|
resultsContainer: document.getElementById('results-container'),
|
|||
|
json: '/search.json',
|
|||
|
//searchResultTemplate: '<li><a href="https://static.rnmkcy.eu{url}">{date} {title}</a></li>'
|
|||
|
searchResultTemplate: '<li><a href="{url}">{date} {title}</a></li>'
|
|||
|
})
|
|||
|
</script>
|
|||
|
<!-- Fin déclaration champ de recherche -->
|
|||
|
</div>
|
|||
|
<nav class="navigation">
|
|||
|
<ul>
|
|||
|
<li class="navigation__item"><a href="/archive.html">Etiquettes</a></li>
|
|||
|
<li class="navigation__item"><a href="/htmldoc.html">Documents</a></li>
|
|||
|
<li class="navigation__item"><a href="/liens_ttrss.html">Liens</a></li>
|
|||
|
<li class="navigation__item"><a href="/aide-jekyll-text-theme.html">Aide</a></li>
|
|||
|
</ul>
|
|||
|
</nav>
|
|||
|
</div>
|
|||
|
</header>
|
|||
|
|
|||
|
</div>
|
|||
|
<div class="page__content"><div class="main"><div class="grid grid--reverse">
|
|||
|
<div class="col-main cell cell--auto">
|
|||
|
<!-- start custom main top snippet --><div id="results-container" class="search-result js-search-result"></div>
|
|||
|
<!-- end custom main top snippet -->
|
|||
|
<article itemscope itemtype="http://schema.org/Article"><div class="article__header"><header><h1 style="color:Tomato;">EndeavourOS Virt-Manager Complete Edition (VMM KVM QEMU)</h1></header></div>
|
|||
|
<meta itemprop="headline" content="EndeavourOS Virt-Manager Complete Edition (VMM KVM QEMU)">
|
|||
|
<div class="article__info clearfix">
|
|||
|
<ul class="left-col menu">
|
|||
|
<li>
|
|||
|
<a class="button button--secondary button--pill button--sm" href="/archive.html?tag=virtuel">virtuel</a>
|
|||
|
</li>
|
|||
|
<li>
|
|||
|
<a class="button button--secondary button--pill button--sm" href="/archive.html?tag=network">network</a>
|
|||
|
</li>
|
|||
|
</ul>
|
|||
|
<ul class="right-col menu"><li>
|
|||
|
<i class="far fa-calendar-alt"></i> <span title="Création" style="color:#FF00FF">31 mai 2023</span>
|
|||
|
|
|||
|
<span title="Modification" style="color:#00FF7F"> 7 oct. 2024</span>
|
|||
|
</li></ul>
|
|||
|
</div>
|
|||
|
<meta itemprop="datePublished" content="2024-10-07T00:00:00+02:00">
|
|||
|
<meta itemprop="keywords" content="virtuel,network">
|
|||
|
<div class="js-article-content">
|
|||
|
<div class="layout--article">
|
|||
|
<!-- start custom article top snippet -->
|
|||
|
<style>
|
|||
|
#myBtn {
|
|||
|
display: none;
|
|||
|
position: fixed;
|
|||
|
bottom: 10px;
|
|||
|
right: 10px;
|
|||
|
z-index: 99;
|
|||
|
font-size: 12px;
|
|||
|
font-weight: bold;
|
|||
|
border: none;
|
|||
|
outline: none;
|
|||
|
background-color: white;
|
|||
|
color: black;
|
|||
|
cursor: pointer;
|
|||
|
padding: 5px;
|
|||
|
border-radius: 4px;
|
|||
|
}
|
|||
|
|
|||
|
#myBtn:hover {
|
|||
|
background-color: #555;
|
|||
|
}
|
|||
|
</style>
|
|||
|
|
|||
|
<button onclick="topFunction()" id="myBtn" title="Haut de page">⇧</button>
|
|||
|
|
|||
|
<script>
|
|||
|
//Get the button
|
|||
|
var mybutton = document.getElementById("myBtn");
|
|||
|
|
|||
|
// When the user scrolls down 20px from the top of the document, show the button
|
|||
|
window.onscroll = function() {scrollFunction()};
|
|||
|
|
|||
|
function scrollFunction() {
|
|||
|
if (document.body.scrollTop > 20 || document.documentElement.scrollTop > 20) {
|
|||
|
mybutton.style.display = "block";
|
|||
|
} else {
|
|||
|
mybutton.style.display = "none";
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// When the user clicks on the button, scroll to the top of the document
|
|||
|
function topFunction() {
|
|||
|
document.body.scrollTop = 0;
|
|||
|
document.documentElement.scrollTop = 0;
|
|||
|
}
|
|||
|
</script>
|
|||
|
|
|||
|
|
|||
|
<!-- end custom article top snippet -->
|
|||
|
<div class="article__content" itemprop="articleBody">
|
|||
|
<details>
|
|||
|
<summary><b>Afficher/cacher Sommaire</b></summary>
|
|||
|
<!-- affichage sommaire -->
|
|||
|
<div class="toc-aside js-toc-root"></div>
|
|||
|
</details><p><img src="/images/vmm-logo.png" alt=""></p>
|
|||
|
|
|||
|
<p><em>Depuis son introduction, la virtualisation a parcouru un long chemin. De nos jours, les hyperviseurs sont utilisés pour presque tout, de l’exécution de serveurs au niveau de l’entreprise au test de différents systèmes d’exploitation sur un ordinateur utilisateur local.<br>
|
|||
|
Nous allons installer l’un des meilleurs ensembles d’outils gratuits utilisés pour la virtualisation qui se compose de Virt-Manager , KVM et QEMU .(<a href="https://discovery.endeavouros.com/applications/how-to-install-virt-manager-complete-edition/2021/09/">How to install Virt-Manager Complete Edition</a>)</em></p>
|
|||
|
|
|||
|
<h2 id="virt-manager-complete-edition">Virt-Manager Complete Edition</h2>
|
|||
|
|
|||
|
<h3 id="installation-simplifiée">Installation simplifiée</h3>
|
|||
|
|
|||
|
<p>Regroupement des commandes pour une installation rapide</p>
|
|||
|
|
|||
|
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>pacman <span class="nt">-Syu</span> <span class="nt">--needed</span> virt-manager qemu-desktop libvirt edk2-ovmf dnsmasq vde2 bridge-utils iptables-nft dmidecode libguestfs
|
|||
|
<span class="c"># activer et lancer le service</span>
|
|||
|
<span class="nb">sudo </span>systemctl <span class="nb">enable</span> <span class="nt">--now</span> libvirtd.service
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Vérifier existance des groupes : <code class="language-plaintext highlighter-rouge">grep -E '^kvm|^libvirt|^qemu|^swtpm' /etc/group</code></p>
|
|||
|
|
|||
|
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>kvm:x:992:libvirt-qemu,qemu
|
|||
|
libvirt:x:961:
|
|||
|
libvirt-qemu:x:959:
|
|||
|
qemu:x:958:
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Autoriser Libvirt pour les comptes utilisateurs standards</p>
|
|||
|
|
|||
|
<p>Puisque nous voulons utiliser notre compte utilisateur Linux standard pour gérer KVM, configurons KVM pour l’autoriser.</p>
|
|||
|
|
|||
|
<p>Ouvrez le fichier /etc/libvirt/libvirtd.conf pour l’éditer.</p>
|
|||
|
|
|||
|
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>nano /etc/libvirt/libvirtd.conf
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Définissez la propriété du groupe UNIX domain socket à libvirt, (autour de la ligne 85)</p>
|
|||
|
|
|||
|
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>unix_sock_group <span class="o">=</span> <span class="s2">"libvirt"</span>
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Définissez les permissions de socket UNIX pour le socket R/W (autour de la ligne 108).</p>
|
|||
|
|
|||
|
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>unix_sock_rw_perms <span class="o">=</span> <span class="s2">"0770"</span>
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Ajoutez votre compte utilisateur au groupe libvirt.</p>
|
|||
|
|
|||
|
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>usermod <span class="nt">-a</span> <span class="nt">-G</span> libvirt <span class="si">$(</span><span class="nb">whoami</span><span class="si">)</span>
|
|||
|
newgrp libvirt
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Redémarrez le démon libvirt.</p>
|
|||
|
|
|||
|
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>systemctl restart libvirtd.service
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>sudo systemctl restart libvirtd.service</p>
|
|||
|
|
|||
|
<p>Activer la virtualisation imbriquée (facultatif)</p>
|
|||
|
|
|||
|
<p>La fonction de virtualisation imbriquée vous permet d’exécuter des machines virtuelles à l’intérieur d’une VM.<br>
|
|||
|
Activez la virtualisation imbriquée pour kvm_intel / kvm_amd en activant le module du noyau comme indiqué.</p>
|
|||
|
|
|||
|
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">### Intel Processor ###</span>
|
|||
|
<span class="nb">sudo </span>modprobe <span class="nt">-r</span> kvm_intel
|
|||
|
<span class="nb">sudo </span>modprobe kvm_intel <span class="nv">nested</span><span class="o">=</span>1
|
|||
|
|
|||
|
<span class="c">### AMD Processor ###</span>
|
|||
|
<span class="nb">sudo </span>modprobe <span class="nt">-r</span> kvm_amd
|
|||
|
<span class="nb">sudo </span>modprobe kvm_amd <span class="nv">nested</span><span class="o">=</span>1
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Pour rendre cette configuration persistante, exécutez :</p>
|
|||
|
|
|||
|
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">echo</span> <span class="s2">"options kvm-intel nested=1"</span> | <span class="nb">sudo tee</span> /etc/modprobe.d/kvm-intel.conf
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Confirmez que la virtualisation imbriquée est réglée sur Oui :</p>
|
|||
|
|
|||
|
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">### Intel Processor ###</span>
|
|||
|
<span class="nv">$ </span>systool <span class="nt">-m</span> kvm_intel <span class="nt">-v</span> | <span class="nb">grep </span>nested
|
|||
|
nested <span class="o">=</span> <span class="s2">"Y"</span>
|
|||
|
nested_early_check <span class="o">=</span> <span class="s2">"N"</span>
|
|||
|
<span class="nv">$ </span><span class="nb">cat</span> /sys/module/kvm_intel/parameters/nested
|
|||
|
Y
|
|||
|
|
|||
|
<span class="c">### AMD Processor ###</span>
|
|||
|
<span class="nv">$ </span>systool <span class="nt">-m</span> kvm_amd <span class="nt">-v</span> | <span class="nb">grep </span>nested
|
|||
|
nested <span class="o">=</span> <span class="s2">"Y"</span>
|
|||
|
nested_early_check <span class="o">=</span> <span class="s2">"N"</span>
|
|||
|
<span class="nv">$ </span><span class="nb">cat</span> /sys/module/kvm_amd/parameters/nested
|
|||
|
Y
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<h3 id="virt-manager-">Virt-Manager ?</h3>
|
|||
|
|
|||
|
<p><a href="https://virt-manager.org/">Virt-Manager</a> est un frontal utilisateur graphique pour la bibliothèque qui fournit des services de gestion de machines virtuelles. L’interface Virt-manager permet à l’utilisateur de créer, supprimer et manipuler facilement des machines virtuelles sans passer par le terminal.<br>
|
|||
|
<img src="/images/vmm001.png" alt=""><br>
|
|||
|
<em>Virt-manager prend principalement en charge KVM mais il peut également fonctionner avec d’autres hyperviseurs tels que Xen et LXC</em></p>
|
|||
|
|
|||
|
<p>Lorsque virt-manager est installé, il est livré avec l’ensemble d’outils répertorié ci-dessous.</p>
|
|||
|
|
|||
|
<ul>
|
|||
|
<li>
|
|||
|
<strong>virt-install</strong> : utilitaire de ligne de commande pour provisionner le système d’exploitation</li>
|
|||
|
<li>
|
|||
|
<strong>virt-viewer</strong> : L’interface utilisateur avec des fonctionnalités graphiques</li>
|
|||
|
<li>
|
|||
|
<strong>virt-clone</strong> : outil en ligne de commande pour cloner des hôtes inactifs existants</li>
|
|||
|
<li>
|
|||
|
<strong>virt-xml</strong> : outil de ligne de commande pour éditer facilement le XML du domaine libvirt à l’aide des options de ligne de commande de virt-install.</li>
|
|||
|
<li>
|
|||
|
<strong>virt-bootstrap</strong> : outil de commande fournissant un moyen simple de configurer le système de fichiers racine pour les conteneurs basés sur libvirt.</li>
|
|||
|
</ul>
|
|||
|
|
|||
|
<h3 id="kvm">KVM</h3>
|
|||
|
|
|||
|
<p>Les lettres <strong><a href="https://www.linux-kvm.org/page/Main_Page">KVM</a></strong> signifient <strong>K</strong> ernel-based <strong>V</strong> irtual <strong>M</strong> achines. KVM est une solution de virtualisation complète Linux pour les processeurs d’architecture x86 qui a l’extension de virtualisation (Intel VT et AMD-V).</p>
|
|||
|
|
|||
|
<p>KVM est un logiciel gratuit et open-source. La prise en charge de KVM est incluse dans tous les nouveaux noyaux Linux par conception.</p>
|
|||
|
|
|||
|
<h3 id="qemu">QEMU</h3>
|
|||
|
|
|||
|
<p><strong><a href="https://www.qemu.org/">QEMU</a></strong> est la version abrégée de <strong>Quick EMU</strong>lator, un émulateur open source gratuit capable d’effectuer une virtualisation matérielle . Il émule le processeur de la machine hôte via une traduction binaire dynamique. Cela fournit différents ensembles de modèles de matériel et de périphérique pour la machine hôte, ce qui lui permet d’exécuter une variété de systèmes invités.</p>
|
|||
|
|
|||
|
<p>KVM peut être utilisé avec QEMU, ce qui permet aux machines virtuelles d’être exécutées presque à des vitesses natives. Non seulement l’émulation matérielle, QEMU est capable d’émuler des processeurs de niveau utilisateur qui permettent aux applications compilées pour une architecture de s’exécuter sur une autre.</p>
|
|||
|
|
|||
|
<h3 id="installer-virt-manager-complete-edition">Installer Virt-Manager Complete Edition</h3>
|
|||
|
|
|||
|
<p>Installation de tous les packages pour exécuter virt-manager.</p>
|
|||
|
|
|||
|
<p>Installation de base</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo pacman -Syu virt-manager qemu-desktop dnsmasq iptables-nft
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Installation complète</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo pacman -Syu --needed virt-manager qemu-desktop libvirt edk2-ovmf dnsmasq vde2 bridge-utils iptables-nft dmidecode swtpm
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<ul>
|
|||
|
<li>
|
|||
|
<a href="https://archlinux.org/packages/extra/any/edk2-ovmf/">edk2-ovmf</a> : ovmf est un projet basé sur <a href="https://github.com/tianocore/tianocore.github.io/wiki/EDK-II">EDK</a> II pour activer le support <a href="https://github.com/tianocore/tianocore.github.io/wiki/UEFI">UEFI</a> pour les machines virtuelles.</li>
|
|||
|
<li>
|
|||
|
<a href="http://edk2-ovmf:%20%20ovmf%20%20is%20an%20EDK%20II%20based%20project%20to%20enable%20UEFI%20support%20for%20Virtual%20Machines.%20iptables-nft%20https://archlinux.org/packages/core/x86_64/iptables-nft/">iptables-nft</a> : outil de contrôle des paquets du noyau Linux (utilisant l’interface nft).</li>
|
|||
|
<li>
|
|||
|
<a href="https://archlinux.org/packages/extra/x86_64/bridge-utils/">bridge-utils</a> : utilitaires de pont Ethernet.</li>
|
|||
|
</ul>
|
|||
|
|
|||
|
<p>Une fois l’installation terminée, vous devez activer le service libvirtd</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo systemctl enable --now libvirtd.service
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Vérifiez l’état pour vous assurer que le service est en cours d’exécution.</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl status libvirtd.service
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Vous pourrez maintenant commencer à créer votre configuration de machine virtuelle sur l’application.</p>
|
|||
|
|
|||
|
<p>La connexion QEMU ne nécessite pas d’exécuter libvirtd.service !</p>
|
|||
|
|
|||
|
<p><strong>Exécutez Virt-Manager en tant qu’utilisateur normal (recommandé)</strong></p>
|
|||
|
|
|||
|
<ol>
|
|||
|
<li>
|
|||
|
<p>Créez des groupes kvm et libvirt s’ils ne sont pas présents<br>
|
|||
|
sudo groupadd -f kvm<br>
|
|||
|
sudo groupadd -f libvirt</p>
|
|||
|
</li>
|
|||
|
<li>
|
|||
|
<p>Ajouter l’utilisateur actuel aux groupes kvm et libvirt<br>
|
|||
|
sudo usermod -aG libvirt $USER<br>
|
|||
|
sudo usermod -aG kvm $USER</p>
|
|||
|
</li>
|
|||
|
<li>
|
|||
|
<p>Ajoutez les lignes suivantes à la fin de <code class="language-plaintext highlighter-rouge">/etc/libvirt/libvirtd.conf</code><br>
|
|||
|
unix_sock_group = “libvirt”<br>
|
|||
|
unix_sock_ro_perms = “0777”<br>
|
|||
|
unix_sock_rw_perms = “0770”</p>
|
|||
|
</li>
|
|||
|
<li>
|
|||
|
<p>Ajoutez les lignes suivantes à la fin de /etc/libvirt/qemu.conf<br>
|
|||
|
swtpm_user = “swtpm”<br>
|
|||
|
swtpm_group = “swtpm”<br>
|
|||
|
user = “yann”<br>
|
|||
|
group = “yann”</p>
|
|||
|
</li>
|
|||
|
</ol>
|
|||
|
|
|||
|
<p>Création d’un utilisateur système swtpm</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo useradd -r swtpm
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p class="info">Maintenant, redémarrez le système pour vous assurer que les modifications sont appliquées ou au moins déconnectez complètement votre utilisateur en vous déconnectant et en revenant sur le bureau.</p>
|
|||
|
|
|||
|
<p>Créer et donner les droits au dossier</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo mkdir -p /var/lib/swtpm-localca
|
|||
|
sudo chown swtpm:swtpm /var/lib/swtpm-localca
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<h4 id="gestionnaire-de-session-utilisateur-inactif">Gestionnaire de session utilisateur (INACTIF)</h4>
|
|||
|
|
|||
|
<p>Pour terminer la configuration, la dernière étape consiste à remplacer le gestionnaire de session qemu par défaut par le gestionnaire de session utilisateur qemu :<br>
|
|||
|
<img src="/images/vmm002.png" alt=""><br>
|
|||
|
Ouvrez virt-manager à partir du menu de l’application. Supprimer la connexion par défaut QEMU/KVM System/Root Session”</p>
|
|||
|
|
|||
|
<p>Et ajoutez-en une nouvelle à l’aide de QEMU/KVM User Session :<br>
|
|||
|
Fichier → Ajouter une connexion → Hypervieur : QEMU/KVM Session utilisateur → Connexion auto → Enable<br>
|
|||
|
<img src="/images/vmm003.png" alt=""> <br>
|
|||
|
Vous pouvez maintenant démarrer virt-manager et créer des machines virtuelles en tant qu’utilisateur non root !</p>
|
|||
|
|
|||
|
<h4 id="libguestfs-guestfs-tools">libguestfs guestfs-tools</h4>
|
|||
|
|
|||
|
<p>Si vous souhaitez modifier les images de disque de la machine virtuelle créées, vous pouvez installer libguestfs .
|
|||
|
libguestfs est un ensemble d’outils utilisés pour accéder aux images disques des machines virtuelles (VM) et les modifier. Vous pouvez l’utiliser pour :</p>
|
|||
|
|
|||
|
<ul>
|
|||
|
<li>visualiser et modifier des fichiers à l’intérieur des invités</li>
|
|||
|
<li>effectuer des changements de script sur les VMs</li>
|
|||
|
<li>surveiller les statistiques sur les disques utilisés/libres</li>
|
|||
|
<li>créer des invités</li>
|
|||
|
<li>P2V</li>
|
|||
|
<li>V2V</li>
|
|||
|
<li>effectuer des sauvegardes, etc.</li>
|
|||
|
</ul>
|
|||
|
|
|||
|
<p>Installation</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yay -S libguestfs guestfs-tools
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<h4 id="fonctionnalité-facultative">Fonctionnalité facultative</h4>
|
|||
|
|
|||
|
<p>Forfait pour des fonctionnalités supplémentaires :</p>
|
|||
|
|
|||
|
<ul>
|
|||
|
<li>
|
|||
|
<a href="https://www.archlinux.org/packages/?name=qemu-arch-extra">qemu-arch-extra</a> - prise en charge d’architectures supplémentaires</li>
|
|||
|
<li>
|
|||
|
<a href="https://www.archlinux.org/packages/?name=qemu-block-gluster">qemu-block-gluster</a> – Prise en charge des blocs <a href="https://wiki.archlinux.org/index.php/Glusterfs">Glusterfs</a>
|
|||
|
</li>
|
|||
|
<li>
|
|||
|
<a href="https://www.archlinux.org/packages/?name=qemu-block-iscsi">qemu-block-iscsi</a> - Prise en charge des blocs <a href="https://wiki.archlinux.org/index.php/ISCSI">iSCSI</a>
|
|||
|
</li>
|
|||
|
<li>
|
|||
|
<a href="https://www.archlinux.org/packages/?name=qemu-block-rbd">qemu-block-rbd</a> - Prise en charge du bloc RBD</li>
|
|||
|
</ul>
|
|||
|
|
|||
|
<p><strong>qemu-emulators-full</strong><br>
|
|||
|
Tous les émulateurs de mode utilisateur et de système QEMU</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yay -S qemu-emulators-full
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p><strong>qemu-bloc-gluster</strong><br>
|
|||
|
Glusterfs est un système de fichiers réseau évolutif. Cela ajoute la prise en charge du bloc Glusterfs à QEMU.</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yay -S qemu-block-gluster
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p><strong>qemu-block-iscsi</strong><br>
|
|||
|
iSCI permet l’accès au stockage via un réseau. qemu-block-iscsi permet à QEMU de bloquer cela.</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yay -S qemu-block-iscsi
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p><strong>samba</strong><br>
|
|||
|
Cela ajouterait la prise en charge de SMB/CIFS à QEMU.</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yay -S samba
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<h4 id="résumé---installation-complète-avec-outis">Résumé - Installation complète avec outis</h4>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo pacman -Syu --needed virt-manager qemu-desktop libvirt edk2-ovmf dnsmasq vde2 bridge-utils iptables-nft dmidecode swtpm libguestfs guestfs-tools
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<h3 id="activation-des-graphiques-virtio-avec-accélération-3d">Activation des graphiques virtio avec accélération 3D</h3>
|
|||
|
|
|||
|
<p>Il existe plusieurs modes d’accélérations graphiques pris en charge par virt-manager.</p>
|
|||
|
|
|||
|
<ul>
|
|||
|
<li>QXL - Carte graphique paravirtuelle QXL. Il est compatible VGA (y compris le support VESA 2.0 VBE). Fonctionne mieux avec les pilotes invités QXL installés. Choix recommandé lors de l’utilisation du protocole d’épices.</li>
|
|||
|
<li>Bochs - Le BIOS VGA Bochs prend en charge, dans une certaine mesure, la spécification VBE.</li>
|
|||
|
<li>Ramfb - Un tampon de trame simple qui vit dans la RAM invitée et est configuré via fw_cfg.</li>
|
|||
|
<li>VGA – Carte VGA standard</li>
|
|||
|
<li>Virtio – Virtio-gpu est un pilote graphique accéléré 3D paravirtualisé.</li>
|
|||
|
</ul>
|
|||
|
|
|||
|
<p>Lors de l’utilisation de virtio avec l’accélération 3D, virt-manager ne fonctionne parfois pas et génère une erreur. Ce n’est en fait pas virt-manager, c’est qemu qui plante.<br>
|
|||
|
<img src="/images/vmm004.png" alt=""> <br>
|
|||
|
Erreur QEMU lorsque l’accélération 3D est activée</p>
|
|||
|
|
|||
|
<p>Pour utiliser virtio avec l’accélération 3D, vous devez configurer virt-manager comme indiqué ci-dessous.</p>
|
|||
|
|
|||
|
<p class="warning">REMARQUE : Au moment de la rédaction, il existe un problème lors de l’utilisation de virtio avec l’accélération 3D avec XFCE DE, ce qui entraîne un écran noir pour les invités. Cela est dû au compositeur xfwm4+compositing par défaut. La solution de contournement connue consiste à utiliser ‘QXL’ ou virtio sans accélération 3D.</p>
|
|||
|
|
|||
|
<p>Accédez à Display Spice et cochez la case Open GL et sélectionnez Auto dans le menu déroulant.<br>
|
|||
|
<img src="/images/vmm005.png" alt=""> <br>
|
|||
|
Avant de configurer</p>
|
|||
|
|
|||
|
<p><img src="/images/vmm006.png" alt=""> <br>
|
|||
|
Après avoir configuré</p>
|
|||
|
|
|||
|
<p>Allez maintenant dans Vidéo et sélectionnez virtio puis cochez la case Accélération 3D.<br>
|
|||
|
<img src="/images/vmm007.png" alt=""> <br>
|
|||
|
Après avoir configuré les options vidéo</p>
|
|||
|
|
|||
|
<p>Après une telle configuration, vous pouvez profiter d’une VM invitée entièrement accélérée en 3D. Pensez également à cliquer sur Appliquer .</p>
|
|||
|
|
|||
|
<p>Installation des pilotes invités virtio pour Windows</p>
|
|||
|
|
|||
|
<p>RedHat fournit un ensemble de pilotes invités pour virtio qui couvre les pilotes graphiques du système invité. Vous pouvez télécharger les derniers pilotes depuis leur page GitHub virtio-win-pkg-scripts <a href="https://github.com/virtio-win/virtio-win-pkg-scripts/blob/master/README.md">ici</a> .</p>
|
|||
|
|
|||
|
<h3 id="modifier-emplacement-pool-stockage">Modifier emplacement pool stockage</h3>
|
|||
|
|
|||
|
<p><em>Modifier l’emplacement du pool de stockage par défaut de KVM Libvirt à l’aide de Virt-manager</em></p>
|
|||
|
|
|||
|
<p>Virt-manager, abréviation de Virtual Machine Manager, est une interface graphique utilisée pour gérer les machines virtuelles via libvirt.</p>
|
|||
|
|
|||
|
<p>Ouvrir l’application Virt-manager. Faire un clic droit sur <strong>QEMU/KVM</strong> et cliquer sur l’option <strong>Détails</strong>.<br>
|
|||
|
<img src="/images/vmm010.png" alt=""><br>
|
|||
|
Voir les détails de la connexion KVMVoir les détails de la connexion KVM<br>
|
|||
|
Vous pouvez aussi cliquer sur Edition → Details de la connexion à partir de l’interface Virt-manager.</p>
|
|||
|
|
|||
|
<p>Sous la section Stockage, vous verrez l’emplacement du pool de stockage par défaut.<br>
|
|||
|
<img src="/images/vmm011.png" alt=""><br>
|
|||
|
Emplacement du pool de stockage par défaut de KVM Libvirt</p>
|
|||
|
|
|||
|
<p>Arrêter et supprimer le pool de stockage par défaut KVM Libvirt<br>
|
|||
|
<img src="/images/vmm012.png" alt=""><br>
|
|||
|
Ceci désactivera et supprimera le pool par défaut.</p>
|
|||
|
|
|||
|
<p>Cliquez sur le signe plus (+) dans le volet inférieur gauche pour créer un nouveau pool de stockage à utiliser par les machines virtuelles.</p>
|
|||
|
|
|||
|
<p>Saisissez le nom du pool de stockage (par exemple, default dans mon cas).<br>
|
|||
|
<img src="/images/vmm013.png" alt=""><br>
|
|||
|
Créer un nouveau pool de stockage KVM Libvirt</p>
|
|||
|
|
|||
|
<p>Le nouveau stockage est maintenant actif. Cochez la case Démarrage automatique pour démarrer automatiquement le nouveau pool de stockage au démarrage du système.<br>
|
|||
|
<img src="/images/vmm014.png" alt=""><br>
|
|||
|
Nouvel emplacement du pool de stockage KVM Libvirt</p>
|
|||
|
|
|||
|
<blockquote>
|
|||
|
<p>Si nécessaire, déplacez toutes les images VM de l’ancien répertoire de stockage vers le nouveau.</p>
|
|||
|
</blockquote>
|
|||
|
|
|||
|
<p>Redémarrez le service libvirtd</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo systemctl restart libvirtd
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Vérification</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>virsh pool-info default
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Nom : default
|
|||
|
UUID : 23c3aee6-57bb-4c98-93f8-4c15ab3c9fae
|
|||
|
État : en cours d’exécution
|
|||
|
Persistant : oui
|
|||
|
Démarrage automatique : oui
|
|||
|
Capacité : 58,76 GiB
|
|||
|
Allocation : 17,95 GiB
|
|||
|
Disponible : 40,81 GiB
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<h2 id="réseau">Réseau</h2>
|
|||
|
|
|||
|
<p>La connexion des machines virtuelles (VM) à d’autres périphériques et emplacements sur un réseau doit être facilitée par le matériel hôte. Les sections suivantes expliquent les mécanismes des connexions réseau des VM et décrivent le paramètre réseau VM par défaut.</p>
|
|||
|
|
|||
|
<h3 id="fonctionnement-des-réseaux-virtuels">Fonctionnement des réseaux virtuels</h3>
|
|||
|
|
|||
|
<p>Le réseau virtuel utilise le concept de commutateur de réseau virtuel. Un commutateur de réseau virtuel est une construction logicielle qui fonctionne sur une machine hôte.<br>
|
|||
|
Les machines virtuelles se connectent au réseau par l’intermédiaire du commutateur de réseau virtuel. En fonction de la configuration du commutateur virtuel, une machine virtuelle peut utiliser un réseau virtuel existant géré par l’hyperviseur ou une méthode de connexion réseau différente.</p>
|
|||
|
|
|||
|
<p>La figure suivante montre un commutateur de réseau virtuel qui connecte deux machines virtuelles au réseau :<br>
|
|||
|
<img src="/images/virtual-network01.png" alt=""></p>
|
|||
|
|
|||
|
<p>Du point de vue d’un système d’exploitation invité, une connexion réseau virtuelle est identique à une connexion réseau physique.<br>
|
|||
|
Les machines hôtes considèrent les commutateurs réseau virtuels comme des interfaces réseau.<br>
|
|||
|
Lorsque le service <strong>libvirtd</strong> est installé et démarré pour la première fois, il crée <strong>virbr0</strong>, l’interface réseau par défaut des machines virtuelles.</p>
|
|||
|
|
|||
|
<p>Pour afficher des informations sur cette interface, utilisez l’utilitaire ip sur l’hôte.</p>
|
|||
|
|
|||
|
<p class="info">Par défaut, toutes les machines virtuelles d’un même hôte sont connectées au même réseau virtuel de type NAT, appelé <strong>default</strong>, qui utilise l’interface <strong>virbr0</strong></p>
|
|||
|
|
|||
|
<p>Si vous avez besoin d’une fonctionnalité réseau différente, vous pouvez créer des réseaux virtuels et des interfaces réseau supplémentaires et configurer vos machines virtuelles pour qu’elles les utilisent. En plus du NAT par défaut, ces réseaux et interfaces peuvent être configurés pour utiliser l’un des modes suivants :</p>
|
|||
|
|
|||
|
<h4 id="mise-en-réseau-virtuelle-en-mode-routé">Mise en réseau virtuelle en mode routé</h4>
|
|||
|
|
|||
|
<p>En mode routé, le commutateur virtuel se connecte au réseau local physique connecté à la machine hôte et transmet le trafic dans les deux sens sans utiliser de NAT. Le commutateur virtuel peut examiner l’ensemble du trafic et utiliser les informations contenues dans les paquets réseau pour prendre des décisions de routage.<br>
|
|||
|
Dans ce mode, les machines virtuelles (VM) se trouvent toutes dans un sous-réseau unique, séparé de la machine hôte.<br>
|
|||
|
Le sous-réseau des machines virtuelles est acheminé via un commutateur virtuel, qui existe sur la machine hôte. Cela permet d’établir des connexions entrantes, mais nécessite des entrées supplémentaires dans la table de routage pour les systèmes sur le réseau externe.</p>
|
|||
|
|
|||
|
<p>Le mode routé utilise le routage basé sur l’adresse IP :<br>
|
|||
|
<img src="/images/virtual-network02.png" alt=""></p>
|
|||
|
|
|||
|
<p>L’hébergement de serveurs virtuels (VSH) est une topologie courante qui utilise le mode routé. Un fournisseur VSH peut avoir plusieurs machines hôtes, chacune avec deux connexions réseau physiques. Une interface est utilisée pour la gestion et la comptabilité, l’autre pour la connexion des machines virtuelles. Chaque VM possède sa propre adresse IP publique, mais les machines hôtes utilisent des adresses IP privées afin que seuls les administrateurs internes puissent gérer les VM.</p>
|
|||
|
|
|||
|
<h4 id="mise-en-réseau-virtuelle-en-mode-ponté">Mise en réseau virtuelle en mode ponté</h4>
|
|||
|
|
|||
|
<p>Dans la plupart des modes de mise en réseau de VM, les VM créent automatiquement le pont virtuel virbr0 et s’y connectent.<br>
|
|||
|
En revanche, en <strong>mode ponté</strong>, la VM se connecte à un pont Linux existant sur l’hôte.<br>
|
|||
|
Par conséquent, la VM est directement visible sur le réseau physique.<br>
|
|||
|
Cela permet les connexions entrantes, mais ne nécessite pas d’entrées supplémentaires dans la table de routage.</p>
|
|||
|
|
|||
|
<p>Le mode ponté utilise la commutation de connexion basée sur l’adresse MAC :<br>
|
|||
|
<img src="/images/virtual-network03.png" alt=""></p>
|
|||
|
|
|||
|
<p>En mode ponté, la VM apparaît dans le même sous-réseau que la machine hôte.<br>
|
|||
|
Toutes les autres machines physiques sur le même réseau physique peuvent détecter la VM et y accéder.</p>
|
|||
|
|
|||
|
<p><strong>Liaison entre réseaux pontés</strong></p>
|
|||
|
|
|||
|
<p>Il est possible d’utiliser plusieurs interfaces de pont physique sur l’hyperviseur en les reliant par un lien. Le lien peut ensuite être ajouté à un pont, après quoi les machines virtuelles peuvent également être ajoutées au pont. Cependant, le pilote de liaison a plusieurs modes de fonctionnement, et tous ces modes ne fonctionnent pas avec une passerelle où des machines virtuelles sont en cours d’utilisation.</p>
|
|||
|
|
|||
|
<p>Les modes de liaison (<a href="https://access.redhat.com/solutions/67546">bonding modes</a>) suivants sont utilisables :</p>
|
|||
|
|
|||
|
<ul>
|
|||
|
<li>mode 1</li>
|
|||
|
<li>mode 2</li>
|
|||
|
<li>mode 4</li>
|
|||
|
</ul>
|
|||
|
|
|||
|
<p>En revanche, l’utilisation des modes 0, 3, 5 ou 6 risque de faire échouer la connexion. Notez également que la surveillance de l’interface indépendante du média (MII) doit être utilisée pour surveiller les modes de liaison, car la surveillance du protocole de résolution d’adresses (ARP) ne fonctionne pas correctement.</p>
|
|||
|
|
|||
|
<p>Pour plus d’informations sur les modes de liaison, reportez-vous à la base de connaissances de Red Hat ( <a href="https://access.redhat.com/solutions/67546">Red Hat Knowledgebase</a>)</p>
|
|||
|
|
|||
|
<p><strong>Scénarios courants</strong></p>
|
|||
|
|
|||
|
<p>Les cas d’utilisation les plus courants du mode ponté sont les suivants :</p>
|
|||
|
|
|||
|
<ul>
|
|||
|
<li>Déployer des machines virtuelles dans un réseau existant aux côtés de machines hôtes, en rendant la différence entre les machines virtuelles et physiques invisible pour l’utilisateur final.</li>
|
|||
|
<li>Déploiement de machines virtuelles sans modification des paramètres de configuration du réseau physique existant.</li>
|
|||
|
<li>Déployer des machines virtuelles qui doivent être facilement accessibles à un réseau physique existant. Placer des machines virtuelles sur un réseau physique où elles doivent accéder aux services DHCP.</li>
|
|||
|
<li>Connecter des machines virtuelles à un réseau existant où des réseaux locaux virtuels (VLAN) sont utilisés.</li>
|
|||
|
<li>Un réseau de zone démilitarisée (DMZ). Pour un déploiement DMZ avec des machines virtuelles, Red Hat recommande de configurer la DMZ au niveau du routeur et des commutateurs du réseau physique, et de connecter les machines virtuelles au réseau physique en utilisant le mode ponté.</li>
|
|||
|
</ul>
|
|||
|
|
|||
|
<h4 id="mise-en-réseau-virtuelle-en-mode-isolé">Mise en réseau virtuelle en mode isolé</h4>
|
|||
|
|
|||
|
<p>En mode isolé, les machines virtuelles connectées au commutateur virtuel peuvent communiquer entre elles et avec la machine hôte, mais leur trafic ne passe pas en dehors de la machine hôte et elles ne peuvent pas recevoir de trafic en provenance de l’extérieur de la machine hôte. L’utilisation de dnsmasq dans ce mode est nécessaire pour les fonctionnalités de base telles que DHCP.<br>
|
|||
|
<img src="/images/virtual-network04.png" alt=""></p>
|
|||
|
|
|||
|
<h4 id="réseau-virtuel-en-mode-ouvert">Réseau virtuel en mode ouvert</h4>
|
|||
|
|
|||
|
<p>Lors de l’utilisation du mode ouvert pour la mise en réseau, libvirt ne génère aucune règle de pare-feu pour le réseau. Par conséquent, libvirt n’écrase pas les règles de pare-feu fournies par l’hôte, et l’utilisateur peut donc gérer manuellement les règles de pare-feu de la VM.</p>
|
|||
|
|
|||
|
<h4 id="comparaison-des-types-de-connexion-des-machines-virtuelles">Comparaison des types de connexion des machines virtuelles</h4>
|
|||
|
|
|||
|
<p>Le tableau suivant fournit des informations sur les emplacements auxquels certains types de configurations réseau de machines virtuelles (VM) peuvent se connecter et sur lesquels elles sont visibles.</p>
|
|||
|
|
|||
|
<p>Types de connexion de machine virtuelle</p>
|
|||
|
|
|||
|
<table>
|
|||
|
<thead>
|
|||
|
<tr>
|
|||
|
<th><strong>__</strong></th>
|
|||
|
<th>Connexion à l’hôte</th>
|
|||
|
<th>Connexion à d’autres VM sur l’hôte</th>
|
|||
|
<th>Connexion aux sites extérieurs</th>
|
|||
|
<th>Visible aux sites extérieurs</th>
|
|||
|
</tr>
|
|||
|
</thead>
|
|||
|
<tbody>
|
|||
|
<tr>
|
|||
|
<td>Mode ponté</td>
|
|||
|
<td>Oui</td>
|
|||
|
<td>Oui</td>
|
|||
|
<td>Oui</td>
|
|||
|
<td>Oui</td>
|
|||
|
</tr>
|
|||
|
<tr>
|
|||
|
<td>NAT</td>
|
|||
|
<td>Oui</td>
|
|||
|
<td>Oui</td>
|
|||
|
<td>Oui</td>
|
|||
|
<td>Non</td>
|
|||
|
</tr>
|
|||
|
<tr>
|
|||
|
<td>Mode routé</td>
|
|||
|
<td>Oui</td>
|
|||
|
<td>Oui</td>
|
|||
|
<td>Oui</td>
|
|||
|
<td>Oui</td>
|
|||
|
</tr>
|
|||
|
<tr>
|
|||
|
<td>Mode isolé</td>
|
|||
|
<td>Oui</td>
|
|||
|
<td>Oui</td>
|
|||
|
<td>Non</td>
|
|||
|
<td>Non</td>
|
|||
|
</tr>
|
|||
|
</tbody>
|
|||
|
</table>
|
|||
|
|
|||
|
<p>Mode ouvert : Dépend des règles du pare-feu de l’hôte</p>
|
|||
|
|
|||
|
<h3 id="hôte---networkmanager-bridge-network-br0-sur-interface-par-défaut">Hôte - NetworkManager Bridge Network br0 sur interface par défaut</h3>
|
|||
|
|
|||
|
<p>En mode su</p>
|
|||
|
|
|||
|
<p>Les connexions actives</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nmcli connection show --active
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p><img src="/images/nmcli-100.png" alt="nmcli"></p>
|
|||
|
|
|||
|
<p>J’ai une «Ethernet automatique» qui utilise l’interface Ethernet <strong>enp0s31f6</strong>.<br>
|
|||
|
Je vais configurer une interface de pont nommée br0 et ajouter (ou asservir) l’interface <strong>enp0s31f6</strong>.</p>
|
|||
|
|
|||
|
<h4 id="créer-un-pont-br0">Créer un Pont br0</h4>
|
|||
|
|
|||
|
<p><strong>Créer un pont nommé br0</strong></p>
|
|||
|
|
|||
|
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>nmcli con add ifname br0 <span class="nb">type </span>bridge con-name br0
|
|||
|
<span class="nb">sudo </span>nmcli con modify br0 bridge.stp no
|
|||
|
<span class="c"># désactiver IPV6 sur br0</span>
|
|||
|
<span class="nb">sudo </span>nmcli connection modify br0 ipv6.method <span class="s2">"disabled"</span>
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Affichez les interfaces réseau et notez le(s) nom(s) des interfaces que vous souhaitez ajouter au pont</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nmcli device status
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p><img src="/images/nmcli-101.png" alt="nmcli"></p>
|
|||
|
|
|||
|
<p>Affectez les interfaces à la passerelle.<br>
|
|||
|
Si les interfaces que vous souhaitez affecter à la passerelle ne sont pas configurées, créez de nouveaux profils de connexion pour elles</p>
|
|||
|
|
|||
|
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>nmcli con add <span class="nb">type </span>bridge-slave ifname enp0s31f6 master br0
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Définir la connexion existante, celle utilise enp0s31f6, comme “down” (vous pouvez l’obtenir avec nmcli connection show –active) :</p>
|
|||
|
|
|||
|
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>nmcli con down e7d9d5a6-8d41-3015-9c5a-3ea38d3d8c78
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Mettre en place le nouveau pont :</p>
|
|||
|
|
|||
|
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>nmcli con up br0
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<h4 id="box---bail-statique">Box - bail statique</h4>
|
|||
|
|
|||
|
<p><strong>Box - bail statique</strong><br>
|
|||
|
Paramétrer un bail statique sur la box avec l’adresse mac de la connexion br0<br>
|
|||
|
<img src="/images/nmcli-105.png" alt="nmcli"></p>
|
|||
|
|
|||
|
<p>Utilisez l’utilitaire ip pour afficher l’état des liens des périphériques Ethernet qui sont des ports d’un pont spécifique :</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ip a show br0
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>5: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
|
|||
|
link/ether 5e:95:2e:b1:9f:c4 brd ff:ff:ff:ff:ff:ff
|
|||
|
inet 192.168.0.20/24 brd 192.168.0.255 scope global dynamic noprefixroute br0
|
|||
|
valid_lft 42855sec preferred_lft 42855sec
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<h4 id="pont-réseau-virtuel-host-bridge">Pont réseau virtuel “host-bridge”</h4>
|
|||
|
|
|||
|
<p><strong>Les commandes <code class="language-plaintext highlighter-rouge">virsh</code> en mode su</strong></p>
|
|||
|
|
|||
|
<p>Déclarer le pont (bridge) à KVM.<br>
|
|||
|
Créer un fichier de définition de réseau au format XML :</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nano host-bridge.xml
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><network></span>
|
|||
|
<span class="nt"><name></span>host-bridge<span class="nt"></name></span>
|
|||
|
<span class="nt"><forward</span> <span class="na">mode=</span><span class="s">"bridge"</span><span class="nt">/></span>
|
|||
|
<span class="nt"><bridge</span> <span class="na">name=</span><span class="s">"br0"</span> <span class="nt">/></span>
|
|||
|
<span class="nt"></network></span>
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Appliquer la configuration :</p>
|
|||
|
|
|||
|
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>virsh net-define host-bridge.xml <span class="c"># -> Réseau host-bridge défini depuis host-bridge.xml</span>
|
|||
|
<span class="nb">sudo </span>virsh net-start host-bridge <span class="c"># -> Réseau host-bridge démarré</span>
|
|||
|
<span class="nb">sudo </span>virsh net-autostart host-bridge <span class="c"># -> Réseau host-bridge marqué en démarrage automatique</span>
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Vérification</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo virsh net-list --all
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Nom État Démarrage automatique Persistant
|
|||
|
-------------------------------------------------------------
|
|||
|
host-bridge actif oui oui
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p><img src="/images/virtual-network05.png" alt=""></p>
|
|||
|
|
|||
|
<h3 id="hôte---networkmanager-bridge-network-br1-sur-autre-interface">Hôte - NetworkManager Bridge Network br1 sur autre interface</h3>
|
|||
|
|
|||
|
<p>On dispose d’une seconde ou plus interface réseau</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>2: enp0s31f6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
|
|||
|
link/ether 38:d5:47:7c:a0:6c brd ff:ff:ff:ff:ff:ff
|
|||
|
3: enp3s0f0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq master br0 state UP mode DEFAULT group default qlen 1000
|
|||
|
link/ether 6c:b3:11:32:04:c8 brd ff:ff:ff:ff:ff:ff
|
|||
|
5: enp3s0f1: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc mq state DOWN mode DEFAULT group default qlen 1000
|
|||
|
link/ether 6c:b3:11:32:04:c9 brd ff:ff:ff:ff:ff:ff
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Dans ce cas, 3 interfaces réseau, enp0s31f6 est l’interface affectée à br0 et utilisée par l’hôte</p>
|
|||
|
|
|||
|
<h4 id="créer-un-pont-br1">Créer un Pont br1</h4>
|
|||
|
|
|||
|
<p><strong>Créer un pont nommé br1</strong></p>
|
|||
|
|
|||
|
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>nmcli con add ifname br1 <span class="nb">type </span>bridge con-name br1
|
|||
|
<span class="nb">sudo </span>nmcli con modify br1 bridge.stp no
|
|||
|
<span class="c"># désactiver IPV6 sur br1</span>
|
|||
|
<span class="nb">sudo </span>nmcli connection modify br1 ipv6.method <span class="s2">"disabled"</span>
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Affichez les interfaces réseau et notez le(s) nom(s) des interfaces que vous souhaitez ajouter au pont</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nmcli device status
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p><img src="/images/nmcli-101a.png" alt="nmcli"></p>
|
|||
|
|
|||
|
<p>Affectez les interfaces à la passerelle.<br>
|
|||
|
Si les interfaces que vous souhaitez affecter à la passerelle ne sont pas configurées, créez de nouveaux profils de connexion pour elles</p>
|
|||
|
|
|||
|
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>nmcli con add <span class="nb">type </span>bridge-slave ifname enp3s0f0 master br1
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Définir la connexion existante, celle qui utilise enp3s0f0, comme “down” (vous pouvez l’obtenir avec nmcli connection show –active) :</p>
|
|||
|
|
|||
|
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>nmcli con down 15950896-b8dc-39c8-9f21-f20f13086975
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Mettre en place le nouveau pont :</p>
|
|||
|
|
|||
|
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>nmcli con up br1
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Utilisez l’utilitaire ip pour afficher l’état des liens des périphériques Ethernet qui sont des ports d’un pont spécifique :</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ip a show br1
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>9: br1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
|
|||
|
link/ether 26:89:96:2c:1d:9d brd ff:ff:ff:ff:ff:ff
|
|||
|
inet 192.168.10.180/24 brd 192.168.10.255 scope global dynamic noprefixroute br1
|
|||
|
valid_lft 85978sec preferred_lft 85978sec
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Au final<br>
|
|||
|
<img src="/images/nmcli-101b.png" alt="nmcli"></p>
|
|||
|
|
|||
|
<h4 id="pont-réseau-virtuel-host-tenda">Pont réseau virtuel “host-tenda”</h4>
|
|||
|
|
|||
|
<p><strong>Les commandes <code class="language-plaintext highlighter-rouge">virsh</code> en mode su</strong></p>
|
|||
|
|
|||
|
<p>Déclarer le pont (bridge) à KVM.<br>
|
|||
|
Créer un fichier de définition de réseau au format XML :</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nano host-tenda.xml
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><network></span>
|
|||
|
<span class="nt"><name></span>host-tenda<span class="nt"></name></span>
|
|||
|
<span class="nt"><forward</span> <span class="na">mode=</span><span class="s">"bridge"</span><span class="nt">/></span>
|
|||
|
<span class="nt"><bridge</span> <span class="na">name=</span><span class="s">"br1"</span> <span class="nt">/></span>
|
|||
|
<span class="nt"></network></span>
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Appliquer la configuration :</p>
|
|||
|
|
|||
|
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>virsh net-define host-tenda.xml <span class="c"># -> Réseau host-tenda défini depuis host-tenda.xml</span>
|
|||
|
<span class="nb">sudo </span>virsh net-start host-tenda <span class="c"># -> Réseau host-tenda démarré</span>
|
|||
|
<span class="nb">sudo </span>virsh net-autostart host-tenda <span class="c"># -> Réseau host-tenda marqué en démarrage automatique</span>
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Vérification</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo virsh net-list --all
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Nom État Démarrage automatique Persistant
|
|||
|
-------------------------------------------------------------
|
|||
|
host-tenda actif oui oui
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p><img src="/images/virtual-network05a.png" alt=""></p>
|
|||
|
|
|||
|
|
|||
|
</div>
|
|||
|
|
|||
|
|
|||
|
|
|||
|
<div class="d-print-none">
|
|||
|
<footer class="article__footer"><meta itemprop="dateModified" content="2023-05-31T00:00:00+02:00">
|
|||
|
<!-- start custom article footer snippet -->
|
|||
|
|
|||
|
<!-- end custom article footer snippet -->
|
|||
|
<!--
|
|||
|
<div align="right"><a type="application/rss+xml" href="/feed.xml" title="S'abonner"><i class="fa fa-rss fa-2x"></i></a>
|
|||
|
|
|||
|
 </div>
|
|||
|
-->
|
|||
|
</footer>
|
|||
|
<div class="article__section-navigator clearfix">
|
|||
|
<div class="previous">
|
|||
|
<span>PRÉCÉDENT</span><a href="/2023/05/29/PC1-EndeavourOS-XFCE-ASUS_H110M_A_conteneur_nspawn_debian_bullseye_nspyan.html">PC1 Ordinateur Bureau EndeavourOS xfce --> conteneur nspawn debian bullseye nspyan</a>
|
|||
|
</div>
|
|||
|
<div class="next">
|
|||
|
<span>SUIVANT</span><a href="/2023/06/02/Qemu-KVM-Machine_virtuelle_debian_11_image_cloud_Qcow2.html">PC1 Qemu/KVM - Machine virtuelle vm-bullseyes debian 11 (image cloud Qcow2)</a>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
|
|||
|
</div>
|
|||
|
|
|||
|
<script>(function() {
|
|||
|
var SOURCES = window.TEXT_VARIABLES.sources;
|
|||
|
window.Lazyload.js(SOURCES.jquery, function() {
|
|||
|
$(function() {
|
|||
|
var $this ,$scroll;
|
|||
|
var $articleContent = $('.js-article-content');
|
|||
|
var hasSidebar = $('.js-page-root').hasClass('layout--page--sidebar');
|
|||
|
var scroll = hasSidebar ? '.js-page-main' : 'html, body';
|
|||
|
$scroll = $(scroll);
|
|||
|
|
|||
|
$articleContent.find('.highlight').each(function() {
|
|||
|
$this = $(this);
|
|||
|
$this.attr('data-lang', $this.find('code').attr('data-lang'));
|
|||
|
});
|
|||
|
$articleContent.find('h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]').each(function() {
|
|||
|
$this = $(this);
|
|||
|
$this.append($('<a class="anchor d-print-none" aria-hidden="true"></a>').html('<i class="fas fa-anchor"></i>'));
|
|||
|
});
|
|||
|
$articleContent.on('click', '.anchor', function() {
|
|||
|
$scroll.scrollToAnchor('#' + $(this).parent().attr('id'), 400);
|
|||
|
});
|
|||
|
});
|
|||
|
});
|
|||
|
})();
|
|||
|
</script>
|
|||
|
|
|||
|
</div>
|
|||
|
<section class="page__comments d-print-none"></section></article><!-- start custom main bottom snippet -->
|
|||
|
<!-- end custom main bottom snippet -->
|
|||
|
</div>
|
|||
|
</div></div></div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
<script>(function() {
|
|||
|
var SOURCES = window.TEXT_VARIABLES.sources;
|
|||
|
window.Lazyload.js(SOURCES.jquery, function() {
|
|||
|
var $body = $('body'), $window = $(window);
|
|||
|
var $pageRoot = $('.js-page-root'), $pageMain = $('.js-page-main');
|
|||
|
var activeCount = 0;
|
|||
|
function modal(options) {
|
|||
|
var $root = this, visible, onChange, hideWhenWindowScroll = false;
|
|||
|
var scrollTop;
|
|||
|
function setOptions(options) {
|
|||
|
var _options = options || {};
|
|||
|
visible = _options.initialVisible === undefined ? false : show;
|
|||
|
onChange = _options.onChange;
|
|||
|
hideWhenWindowScroll = _options.hideWhenWindowScroll;
|
|||
|
}
|
|||
|
function init() {
|
|||
|
setState(visible);
|
|||
|
}
|
|||
|
function setState(isShow) {
|
|||
|
if (isShow === visible) {
|
|||
|
return;
|
|||
|
}
|
|||
|
visible = isShow;
|
|||
|
if (visible) {
|
|||
|
activeCount++;
|
|||
|
scrollTop = $(window).scrollTop() || $pageMain.scrollTop();
|
|||
|
$root.addClass('modal--show');
|
|||
|
$pageMain.scrollTop(scrollTop);
|
|||
|
activeCount === 1 && ($pageRoot.addClass('show-modal'), $body.addClass('of-hidden'));
|
|||
|
hideWhenWindowScroll && window.hasEvent('touchstart') && $window.on('scroll', hide);
|
|||
|
$window.on('keyup', handleKeyup);
|
|||
|
} else {
|
|||
|
activeCount > 0 && activeCount--;
|
|||
|
$root.removeClass('modal--show');
|
|||
|
$window.scrollTop(scrollTop);
|
|||
|
activeCount === 0 && ($pageRoot.removeClass('show-modal'), $body.removeClass('of-hidden'));
|
|||
|
hideWhenWindowScroll && window.hasEvent('touchstart') && $window.off('scroll', hide);
|
|||
|
$window.off('keyup', handleKeyup);
|
|||
|
}
|
|||
|
onChange && onChange(visible);
|
|||
|
}
|
|||
|
function show() {
|
|||
|
setState(true);
|
|||
|
}
|
|||
|
function hide() {
|
|||
|
setState(false);
|
|||
|
}
|
|||
|
function handleKeyup(e) {
|
|||
|
// Char Code: 27 ESC
|
|||
|
if (e.which === 27) {
|
|||
|
hide();
|
|||
|
}
|
|||
|
}
|
|||
|
setOptions(options);
|
|||
|
init();
|
|||
|
return {
|
|||
|
show: show,
|
|||
|
hide: hide,
|
|||
|
$el: $root
|
|||
|
};
|
|||
|
}
|
|||
|
$.fn.modal = modal;
|
|||
|
});
|
|||
|
})();
|
|||
|
</script><div class="modal modal--overflow page__search-modal d-print-none js-page-search-modal">
|
|||
|
<script>
|
|||
|
(function () {
|
|||
|
var SOURCES = window.TEXT_VARIABLES.sources;
|
|||
|
window.Lazyload.js(SOURCES.jquery, function() {
|
|||
|
// search panel
|
|||
|
var search = (window.search || (window.search = {}));
|
|||
|
var useDefaultSearchBox = window.useDefaultSearchBox === undefined ?
|
|||
|
true : window.useDefaultSearchBox ;
|
|||
|
|
|||
|
var $searchModal = $('.js-page-search-modal');
|
|||
|
var $searchToggle = $('.js-search-toggle');
|
|||
|
var searchModal = $searchModal.modal({ onChange: handleModalChange, hideWhenWindowScroll: true });
|
|||
|
var modalVisible = false;
|
|||
|
search.searchModal = searchModal;
|
|||
|
|
|||
|
var $searchBox = null;
|
|||
|
var $searchInput = null;
|
|||
|
var $searchClear = null;
|
|||
|
|
|||
|
function getModalVisible() {
|
|||
|
return modalVisible;
|
|||
|
}
|
|||
|
search.getModalVisible = getModalVisible;
|
|||
|
|
|||
|
function handleModalChange(visible) {
|
|||
|
modalVisible = visible;
|
|||
|
if (visible) {
|
|||
|
search.onShow && search.onShow();
|
|||
|
useDefaultSearchBox && $searchInput[0] && $searchInput[0].focus();
|
|||
|
} else {
|
|||
|
search.onShow && search.onHide();
|
|||
|
useDefaultSearchBox && $searchInput[0] && $searchInput[0].blur();
|
|||
|
setTimeout(function() {
|
|||
|
useDefaultSearchBox && ($searchInput.val(''), $searchBox.removeClass('not-empty'));
|
|||
|
search.clear && search.clear();
|
|||
|
window.pageAsideAffix && window.pageAsideAffix.refresh();
|
|||
|
}, 400);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
$searchToggle.on('click', function() {
|
|||
|
modalVisible ? searchModal.hide() : searchModal.show();
|
|||
|
});
|
|||
|
// Char Code: 83 S, 191 /
|
|||
|
$(window).on('keyup', function(e) {
|
|||
|
if (!modalVisible && !window.isFormElement(e.target || e.srcElement) && (e.which === 83 || e.which === 191)) {
|
|||
|
modalVisible || searchModal.show();
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
if (useDefaultSearchBox) {
|
|||
|
$searchBox = $('.js-search-box');
|
|||
|
$searchInput = $searchBox.children('input');
|
|||
|
$searchClear = $searchBox.children('.js-icon-clear');
|
|||
|
search.getSearchInput = function() {
|
|||
|
return $searchInput.get(0);
|
|||
|
};
|
|||
|
search.getVal = function() {
|
|||
|
return $searchInput.val();
|
|||
|
};
|
|||
|
search.setVal = function(val) {
|
|||
|
$searchInput.val(val);
|
|||
|
};
|
|||
|
|
|||
|
$searchInput.on('focus', function() {
|
|||
|
$(this).addClass('focus');
|
|||
|
});
|
|||
|
$searchInput.on('blur', function() {
|
|||
|
$(this).removeClass('focus');
|
|||
|
});
|
|||
|
$searchInput.on('input', window.throttle(function() {
|
|||
|
var val = $(this).val();
|
|||
|
if (val === '' || typeof val !== 'string') {
|
|||
|
search.clear && search.clear();
|
|||
|
} else {
|
|||
|
$searchBox.addClass('not-empty');
|
|||
|
search.onInputNotEmpty && search.onInputNotEmpty(val);
|
|||
|
}
|
|||
|
}, 400));
|
|||
|
$searchClear.on('click', function() {
|
|||
|
$searchInput.val(''); $searchBox.removeClass('not-empty');
|
|||
|
search.clear && search.clear();
|
|||
|
});
|
|||
|
}
|
|||
|
});
|
|||
|
})();
|
|||
|
</script><div class="search search--dark">
|
|||
|
<div class="main">
|
|||
|
<div class="search__header">Recherche</div>
|
|||
|
<div class="search-bar">
|
|||
|
<div class="search-box js-search-box">
|
|||
|
<div class="search-box__icon-search"><i class="fas fa-search"></i></div>
|
|||
|
<input id="search-input" type="text">
|
|||
|
<div class="search-box__icon-clear js-icon-clear">
|
|||
|
<a><i class="fas fa-times"></i></a>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
<button class="button button--theme-dark button--pill search__cancel js-search-toggle">
|
|||
|
Annuler</button>
|
|||
|
</div>
|
|||
|
<div id="results-container" class="search-result js-search-result"></div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
<!-- Script pointing to search-script.js -->
|
|||
|
<script>/*!
|
|||
|
* Simple-Jekyll-Search
|
|||
|
* Copyright 2015-2020, Christian Fei
|
|||
|
* Licensed under the MIT License.
|
|||
|
*/
|
|||
|
|
|||
|
(function(){
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$Templater_7 = {
|
|||
|
compile: compile,
|
|||
|
setOptions: setOptions
|
|||
|
}
|
|||
|
|
|||
|
const options = {}
|
|||
|
options.pattern = /\{(.*?)\}/g
|
|||
|
options.template = ''
|
|||
|
options.middleware = function () {}
|
|||
|
|
|||
|
function setOptions (_options) {
|
|||
|
options.pattern = _options.pattern || options.pattern
|
|||
|
options.template = _options.template || options.template
|
|||
|
if (typeof _options.middleware === 'function') {
|
|||
|
options.middleware = _options.middleware
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function compile (data) {
|
|||
|
return options.template.replace(options.pattern, function (match, prop) {
|
|||
|
const value = options.middleware(prop, data[prop], options.template)
|
|||
|
if (typeof value !== 'undefined') {
|
|||
|
return value
|
|||
|
}
|
|||
|
return data[prop] || match
|
|||
|
})
|
|||
|
}
|
|||
|
|
|||
|
'use strict';
|
|||
|
|
|||
|
function fuzzysearch (needle, haystack) {
|
|||
|
var tlen = haystack.length;
|
|||
|
var qlen = needle.length;
|
|||
|
if (qlen > tlen) {
|
|||
|
return false;
|
|||
|
}
|
|||
|
if (qlen === tlen) {
|
|||
|
return needle === haystack;
|
|||
|
}
|
|||
|
outer: for (var i = 0, j = 0; i < qlen; i++) {
|
|||
|
var nch = needle.charCodeAt(i);
|
|||
|
while (j < tlen) {
|
|||
|
if (haystack.charCodeAt(j++) === nch) {
|
|||
|
continue outer;
|
|||
|
}
|
|||
|
}
|
|||
|
return false;
|
|||
|
}
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
var _$fuzzysearch_1 = fuzzysearch;
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
/* removed: const _$fuzzysearch_1 = require('fuzzysearch') */;
|
|||
|
|
|||
|
var _$FuzzySearchStrategy_5 = new FuzzySearchStrategy()
|
|||
|
|
|||
|
function FuzzySearchStrategy () {
|
|||
|
this.matches = function (string, crit) {
|
|||
|
return _$fuzzysearch_1(crit.toLowerCase(), string.toLowerCase())
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$LiteralSearchStrategy_6 = new LiteralSearchStrategy()
|
|||
|
|
|||
|
function LiteralSearchStrategy () {
|
|||
|
this.matches = function (str, crit) {
|
|||
|
if (!str) return false
|
|||
|
|
|||
|
str = str.trim().toLowerCase()
|
|||
|
crit = crit.trim().toLowerCase()
|
|||
|
|
|||
|
return crit.split(' ').filter(function (word) {
|
|||
|
return str.indexOf(word) >= 0
|
|||
|
}).length === crit.split(' ').length
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$Repository_4 = {
|
|||
|
put: put,
|
|||
|
clear: clear,
|
|||
|
search: search,
|
|||
|
setOptions: __setOptions_4
|
|||
|
}
|
|||
|
|
|||
|
/* removed: const _$FuzzySearchStrategy_5 = require('./SearchStrategies/FuzzySearchStrategy') */;
|
|||
|
/* removed: const _$LiteralSearchStrategy_6 = require('./SearchStrategies/LiteralSearchStrategy') */;
|
|||
|
|
|||
|
function NoSort () {
|
|||
|
return 0
|
|||
|
}
|
|||
|
|
|||
|
const data = []
|
|||
|
let opt = {}
|
|||
|
|
|||
|
opt.fuzzy = false
|
|||
|
opt.limit = 10
|
|||
|
opt.searchStrategy = opt.fuzzy ? _$FuzzySearchStrategy_5 : _$LiteralSearchStrategy_6
|
|||
|
opt.sort = NoSort
|
|||
|
opt.exclude = []
|
|||
|
|
|||
|
function put (data) {
|
|||
|
if (isObject(data)) {
|
|||
|
return addObject(data)
|
|||
|
}
|
|||
|
if (isArray(data)) {
|
|||
|
return addArray(data)
|
|||
|
}
|
|||
|
return undefined
|
|||
|
}
|
|||
|
function clear () {
|
|||
|
data.length = 0
|
|||
|
return data
|
|||
|
}
|
|||
|
|
|||
|
function isObject (obj) {
|
|||
|
return Boolean(obj) && Object.prototype.toString.call(obj) === '[object Object]'
|
|||
|
}
|
|||
|
|
|||
|
function isArray (obj) {
|
|||
|
return Boolean(obj) && Object.prototype.toString.call(obj) === '[object Array]'
|
|||
|
}
|
|||
|
|
|||
|
function addObject (_data) {
|
|||
|
data.push(_data)
|
|||
|
return data
|
|||
|
}
|
|||
|
|
|||
|
function addArray (_data) {
|
|||
|
const added = []
|
|||
|
clear()
|
|||
|
for (let i = 0, len = _data.length; i < len; i++) {
|
|||
|
if (isObject(_data[i])) {
|
|||
|
added.push(addObject(_data[i]))
|
|||
|
}
|
|||
|
}
|
|||
|
return added
|
|||
|
}
|
|||
|
|
|||
|
function search (crit) {
|
|||
|
if (!crit) {
|
|||
|
return []
|
|||
|
}
|
|||
|
return findMatches(data, crit, opt.searchStrategy, opt).sort(opt.sort)
|
|||
|
}
|
|||
|
|
|||
|
function __setOptions_4 (_opt) {
|
|||
|
opt = _opt || {}
|
|||
|
|
|||
|
opt.fuzzy = _opt.fuzzy || false
|
|||
|
opt.limit = _opt.limit || 10
|
|||
|
opt.searchStrategy = _opt.fuzzy ? _$FuzzySearchStrategy_5 : _$LiteralSearchStrategy_6
|
|||
|
opt.sort = _opt.sort || NoSort
|
|||
|
opt.exclude = _opt.exclude || []
|
|||
|
}
|
|||
|
|
|||
|
function findMatches (data, crit, strategy, opt) {
|
|||
|
const matches = []
|
|||
|
for (let i = 0; i < data.length && matches.length < opt.limit; i++) {
|
|||
|
const match = findMatchesInObject(data[i], crit, strategy, opt)
|
|||
|
if (match) {
|
|||
|
matches.push(match)
|
|||
|
}
|
|||
|
}
|
|||
|
return matches
|
|||
|
}
|
|||
|
|
|||
|
function findMatchesInObject (obj, crit, strategy, opt) {
|
|||
|
for (const key in obj) {
|
|||
|
if (!isExcluded(obj[key], opt.exclude) && strategy.matches(obj[key], crit)) {
|
|||
|
return obj
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function isExcluded (term, excludedTerms) {
|
|||
|
for (let i = 0, len = excludedTerms.length; i < len; i++) {
|
|||
|
const excludedTerm = excludedTerms[i]
|
|||
|
if (new RegExp(excludedTerm).test(term)) {
|
|||
|
return true
|
|||
|
}
|
|||
|
}
|
|||
|
return false
|
|||
|
}
|
|||
|
|
|||
|
/* globals ActiveXObject:false */
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$JSONLoader_2 = {
|
|||
|
load: load
|
|||
|
}
|
|||
|
|
|||
|
function load (location, callback) {
|
|||
|
const xhr = getXHR()
|
|||
|
xhr.open('GET', location, true)
|
|||
|
xhr.onreadystatechange = createStateChangeListener(xhr, callback)
|
|||
|
xhr.send()
|
|||
|
}
|
|||
|
|
|||
|
function createStateChangeListener (xhr, callback) {
|
|||
|
return function () {
|
|||
|
if (xhr.readyState === 4 && xhr.status === 200) {
|
|||
|
try {
|
|||
|
callback(null, JSON.parse(xhr.responseText))
|
|||
|
} catch (err) {
|
|||
|
callback(err, null)
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function getXHR () {
|
|||
|
return window.XMLHttpRequest ? new window.XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP')
|
|||
|
}
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$OptionsValidator_3 = function OptionsValidator (params) {
|
|||
|
if (!validateParams(params)) {
|
|||
|
throw new Error('-- OptionsValidator: required options missing')
|
|||
|
}
|
|||
|
|
|||
|
if (!(this instanceof OptionsValidator)) {
|
|||
|
return new OptionsValidator(params)
|
|||
|
}
|
|||
|
|
|||
|
const requiredOptions = params.required
|
|||
|
|
|||
|
this.getRequiredOptions = function () {
|
|||
|
return requiredOptions
|
|||
|
}
|
|||
|
|
|||
|
this.validate = function (parameters) {
|
|||
|
const errors = []
|
|||
|
requiredOptions.forEach(function (requiredOptionName) {
|
|||
|
if (typeof parameters[requiredOptionName] === 'undefined') {
|
|||
|
errors.push(requiredOptionName)
|
|||
|
}
|
|||
|
})
|
|||
|
return errors
|
|||
|
}
|
|||
|
|
|||
|
function validateParams (params) {
|
|||
|
if (!params) {
|
|||
|
return false
|
|||
|
}
|
|||
|
return typeof params.required !== 'undefined' && params.required instanceof Array
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$utils_9 = {
|
|||
|
merge: merge,
|
|||
|
isJSON: isJSON
|
|||
|
}
|
|||
|
|
|||
|
function merge (defaultParams, mergeParams) {
|
|||
|
const mergedOptions = {}
|
|||
|
for (const option in defaultParams) {
|
|||
|
mergedOptions[option] = defaultParams[option]
|
|||
|
if (typeof mergeParams[option] !== 'undefined') {
|
|||
|
mergedOptions[option] = mergeParams[option]
|
|||
|
}
|
|||
|
}
|
|||
|
return mergedOptions
|
|||
|
}
|
|||
|
|
|||
|
function isJSON (json) {
|
|||
|
try {
|
|||
|
if (json instanceof Object && JSON.parse(JSON.stringify(json))) {
|
|||
|
return true
|
|||
|
}
|
|||
|
return false
|
|||
|
} catch (err) {
|
|||
|
return false
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
var _$src_8 = {};
|
|||
|
(function (window) {
|
|||
|
'use strict'
|
|||
|
|
|||
|
let options = {
|
|||
|
searchInput: null,
|
|||
|
resultsContainer: null,
|
|||
|
json: [],
|
|||
|
success: Function.prototype,
|
|||
|
searchResultTemplate: '<li><a href="{url}" title="{desc}">{title}</a></li>',
|
|||
|
templateMiddleware: Function.prototype,
|
|||
|
sortMiddleware: function () {
|
|||
|
return 0
|
|||
|
},
|
|||
|
noResultsText: 'No results found',
|
|||
|
limit: 10,
|
|||
|
fuzzy: false,
|
|||
|
debounceTime: null,
|
|||
|
exclude: []
|
|||
|
}
|
|||
|
|
|||
|
let debounceTimerHandle
|
|||
|
const debounce = function (func, delayMillis) {
|
|||
|
if (delayMillis) {
|
|||
|
clearTimeout(debounceTimerHandle)
|
|||
|
debounceTimerHandle = setTimeout(func, delayMillis)
|
|||
|
} else {
|
|||
|
func.call()
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
const requiredOptions = ['searchInput', 'resultsContainer', 'json']
|
|||
|
|
|||
|
/* removed: const _$Templater_7 = require('./Templater') */;
|
|||
|
/* removed: const _$Repository_4 = require('./Repository') */;
|
|||
|
/* removed: const _$JSONLoader_2 = require('./JSONLoader') */;
|
|||
|
const optionsValidator = _$OptionsValidator_3({
|
|||
|
required: requiredOptions
|
|||
|
})
|
|||
|
/* removed: const _$utils_9 = require('./utils') */;
|
|||
|
|
|||
|
window.SimpleJekyllSearch = function (_options) {
|
|||
|
const errors = optionsValidator.validate(_options)
|
|||
|
if (errors.length > 0) {
|
|||
|
throwError('You must specify the following required options: ' + requiredOptions)
|
|||
|
}
|
|||
|
|
|||
|
options = _$utils_9.merge(options, _options)
|
|||
|
|
|||
|
_$Templater_7.setOptions({
|
|||
|
template: options.searchResultTemplate,
|
|||
|
middleware: options.templateMiddleware
|
|||
|
})
|
|||
|
|
|||
|
_$Repository_4.setOptions({
|
|||
|
fuzzy: options.fuzzy,
|
|||
|
limit: options.limit,
|
|||
|
sort: options.sortMiddleware,
|
|||
|
exclude: options.exclude
|
|||
|
})
|
|||
|
|
|||
|
if (_$utils_9.isJSON(options.json)) {
|
|||
|
initWithJSON(options.json)
|
|||
|
} else {
|
|||
|
initWithURL(options.json)
|
|||
|
}
|
|||
|
|
|||
|
const rv = {
|
|||
|
search: search
|
|||
|
}
|
|||
|
|
|||
|
typeof options.success === 'function' && options.success.call(rv)
|
|||
|
return rv
|
|||
|
}
|
|||
|
|
|||
|
function initWithJSON (json) {
|
|||
|
_$Repository_4.put(json)
|
|||
|
registerInput()
|
|||
|
}
|
|||
|
|
|||
|
function initWithURL (url) {
|
|||
|
_$JSONLoader_2.load(url, function (err, json) {
|
|||
|
if (err) {
|
|||
|
throwError('failed to get JSON (' + url + ')')
|
|||
|
}
|
|||
|
initWithJSON(json)
|
|||
|
})
|
|||
|
}
|
|||
|
|
|||
|
function emptyResultsContainer () {
|
|||
|
options.resultsContainer.innerHTML = ''
|
|||
|
}
|
|||
|
|
|||
|
function appendToResultsContainer (text) {
|
|||
|
options.resultsContainer.innerHTML += text
|
|||
|
}
|
|||
|
|
|||
|
function registerInput () {
|
|||
|
options.searchInput.addEventListener('input', function (e) {
|
|||
|
if (isWhitelistedKey(e.which)) {
|
|||
|
emptyResultsContainer()
|
|||
|
debounce(function () { search(e.target.value) }, options.debounceTime)
|
|||
|
}
|
|||
|
})
|
|||
|
}
|
|||
|
|
|||
|
function search (query) {
|
|||
|
if (isValidQuery(query)) {
|
|||
|
emptyResultsContainer()
|
|||
|
render(_$Repository_4.search(query), query)
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function render (results, query) {
|
|||
|
const len = results.length
|
|||
|
if (len === 0) {
|
|||
|
return appendToResultsContainer(options.noResultsText)
|
|||
|
}
|
|||
|
for (let i = 0; i < len; i++) {
|
|||
|
results[i].query = query
|
|||
|
appendToResultsContainer(_$Templater_7.compile(results[i]))
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function isValidQuery (query) {
|
|||
|
return query && query.length > 0
|
|||
|
}
|
|||
|
|
|||
|
function isWhitelistedKey (key) {
|
|||
|
return [13, 16, 20, 37, 38, 39, 40, 91].indexOf(key) === -1
|
|||
|
}
|
|||
|
|
|||
|
function throwError (message) {
|
|||
|
throw new Error('SimpleJekyllSearch --- ' + message)
|
|||
|
}
|
|||
|
})(window)
|
|||
|
|
|||
|
}());
|
|||
|
</script>
|
|||
|
|
|||
|
<!-- Configuration -->
|
|||
|
<script>
|
|||
|
SimpleJekyllSearch({
|
|||
|
searchInput: document.getElementById('search-input'),
|
|||
|
resultsContainer: document.getElementById('results-container'),
|
|||
|
noResultsText: '<p>Aucun résultat!</p>',
|
|||
|
json: '/search.json',
|
|||
|
searchResultTemplate: '<li><a href="{url}">{date} {title}</a> (Création {create})</li>'
|
|||
|
})
|
|||
|
</script>
|
|||
|
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
|
|||
|
|
|||
|
<script>(function() {
|
|||
|
var SOURCES = window.TEXT_VARIABLES.sources;
|
|||
|
window.Lazyload.js(SOURCES.jquery, function() {
|
|||
|
function scrollToAnchor(anchor, duration, callback) {
|
|||
|
var $root = this;
|
|||
|
$root.animate({ scrollTop: $(anchor).position().top }, duration, function() {
|
|||
|
window.history.replaceState(null, '', window.location.href.split('#')[0] + anchor);
|
|||
|
callback && callback();
|
|||
|
});
|
|||
|
}
|
|||
|
$.fn.scrollToAnchor = scrollToAnchor;
|
|||
|
});
|
|||
|
})();
|
|||
|
(function() {
|
|||
|
var SOURCES = window.TEXT_VARIABLES.sources;
|
|||
|
window.Lazyload.js(SOURCES.jquery, function() {
|
|||
|
function affix(options) {
|
|||
|
var $root = this, $window = $(window), $scrollTarget, $scroll,
|
|||
|
offsetBottom = 0, scrollTarget = window, scroll = window.document, disabled = false, isOverallScroller = true,
|
|||
|
rootTop, rootLeft, rootHeight, scrollBottom, rootBottomTop,
|
|||
|
hasInit = false, curState;
|
|||
|
|
|||
|
function setOptions(options) {
|
|||
|
var _options = options || {};
|
|||
|
_options.offsetBottom && (offsetBottom = _options.offsetBottom);
|
|||
|
_options.scrollTarget && (scrollTarget = _options.scrollTarget);
|
|||
|
_options.scroll && (scroll = _options.scroll);
|
|||
|
_options.disabled !== undefined && (disabled = _options.disabled);
|
|||
|
$scrollTarget = $(scrollTarget);
|
|||
|
isOverallScroller = window.isOverallScroller($scrollTarget[0]);
|
|||
|
$scroll = $(scroll);
|
|||
|
}
|
|||
|
function preCalc() {
|
|||
|
top();
|
|||
|
rootHeight = $root.outerHeight();
|
|||
|
rootTop = $root.offset().top + (isOverallScroller ? 0 : $scrollTarget.scrollTop());
|
|||
|
rootLeft = $root.offset().left;
|
|||
|
}
|
|||
|
function calc(needPreCalc) {
|
|||
|
needPreCalc && preCalc();
|
|||
|
scrollBottom = $scroll.outerHeight() - offsetBottom - rootHeight;
|
|||
|
rootBottomTop = scrollBottom - rootTop;
|
|||
|
}
|
|||
|
function top() {
|
|||
|
if (curState !== 'top') {
|
|||
|
$root.removeClass('fixed').css({
|
|||
|
left: 0,
|
|||
|
top: 0
|
|||
|
});
|
|||
|
curState = 'top';
|
|||
|
}
|
|||
|
}
|
|||
|
function fixed() {
|
|||
|
if (curState !== 'fixed') {
|
|||
|
$root.addClass('fixed').css({
|
|||
|
left: rootLeft + 'px',
|
|||
|
top: 0
|
|||
|
});
|
|||
|
curState = 'fixed';
|
|||
|
}
|
|||
|
}
|
|||
|
function bottom() {
|
|||
|
if (curState !== 'bottom') {
|
|||
|
$root.removeClass('fixed').css({
|
|||
|
left: 0,
|
|||
|
top: rootBottomTop + 'px'
|
|||
|
});
|
|||
|
curState = 'bottom';
|
|||
|
}
|
|||
|
}
|
|||
|
function setState() {
|
|||
|
var scrollTop = $scrollTarget.scrollTop();
|
|||
|
if (scrollTop >= rootTop && scrollTop <= scrollBottom) {
|
|||
|
fixed();
|
|||
|
} else if (scrollTop < rootTop) {
|
|||
|
top();
|
|||
|
} else {
|
|||
|
bottom();
|
|||
|
}
|
|||
|
}
|
|||
|
function init() {
|
|||
|
if(!hasInit) {
|
|||
|
var interval, timeout;
|
|||
|
calc(true); setState();
|
|||
|
// run calc every 100 millisecond
|
|||
|
interval = setInterval(function() {
|
|||
|
calc();
|
|||
|
}, 100);
|
|||
|
timeout = setTimeout(function() {
|
|||
|
clearInterval(interval);
|
|||
|
}, 45000);
|
|||
|
window.pageLoad.then(function() {
|
|||
|
setTimeout(function() {
|
|||
|
clearInterval(interval);
|
|||
|
clearTimeout(timeout);
|
|||
|
}, 3000);
|
|||
|
});
|
|||
|
$scrollTarget.on('scroll', function() {
|
|||
|
disabled || setState();
|
|||
|
});
|
|||
|
$window.on('resize', function() {
|
|||
|
disabled || (calc(true), setState());
|
|||
|
});
|
|||
|
hasInit = true;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
setOptions(options);
|
|||
|
if (!disabled) {
|
|||
|
init();
|
|||
|
}
|
|||
|
$window.on('resize', window.throttle(function() {
|
|||
|
init();
|
|||
|
}, 200));
|
|||
|
return {
|
|||
|
setOptions: setOptions,
|
|||
|
refresh: function() {
|
|||
|
calc(true, { animation: false }); setState();
|
|||
|
}
|
|||
|
};
|
|||
|
}
|
|||
|
$.fn.affix = affix;
|
|||
|
});
|
|||
|
})();
|
|||
|
(function() {
|
|||
|
var SOURCES = window.TEXT_VARIABLES.sources;
|
|||
|
window.Lazyload.js(SOURCES.jquery, function() {
|
|||
|
function toc(options) {
|
|||
|
var $root = this, $window = $(window), $scrollTarget, $scroller, $tocUl = $('<ul class="toc toc--ellipsis"></ul>'), $tocLi, $headings, $activeLast, $activeCur,
|
|||
|
selectors = 'h1,h2,h3', container = 'body', scrollTarget = window, scroller = 'html, body', disabled = false,
|
|||
|
headingsPos, scrolling = false, hasRendered = false, hasInit = false;
|
|||
|
|
|||
|
function setOptions(options) {
|
|||
|
var _options = options || {};
|
|||
|
_options.selectors && (selectors = _options.selectors);
|
|||
|
_options.container && (container = _options.container);
|
|||
|
_options.scrollTarget && (scrollTarget = _options.scrollTarget);
|
|||
|
_options.scroller && (scroller = _options.scroller);
|
|||
|
_options.disabled !== undefined && (disabled = _options.disabled);
|
|||
|
$headings = $(container).find(selectors).filter('[id]');
|
|||
|
$scrollTarget = $(scrollTarget);
|
|||
|
$scroller = $(scroller);
|
|||
|
}
|
|||
|
function calc() {
|
|||
|
headingsPos = [];
|
|||
|
$headings.each(function() {
|
|||
|
headingsPos.push(Math.floor($(this).position().top));
|
|||
|
});
|
|||
|
}
|
|||
|
function setState(element, disabled) {
|
|||
|
var scrollTop = $scrollTarget.scrollTop(), i;
|
|||
|
if (disabled || !headingsPos || headingsPos.length < 1) { return; }
|
|||
|
if (element) {
|
|||
|
$activeCur = element;
|
|||
|
} else {
|
|||
|
for (i = 0; i < headingsPos.length; i++) {
|
|||
|
if (scrollTop >= headingsPos[i]) {
|
|||
|
$activeCur = $tocLi.eq(i);
|
|||
|
} else {
|
|||
|
$activeCur || ($activeCur = $tocLi.eq(i));
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
$activeLast && $activeLast.removeClass('active');
|
|||
|
($activeLast = $activeCur).addClass('active');
|
|||
|
}
|
|||
|
function render() {
|
|||
|
if(!hasRendered) {
|
|||
|
$root.append($tocUl);
|
|||
|
$headings.each(function() {
|
|||
|
var $this = $(this);
|
|||
|
$tocUl.append($('<li></li>').addClass('toc-' + $this.prop('tagName').toLowerCase())
|
|||
|
.append($('<a></a>').text($this.text()).attr('href', '#' + $this.prop('id'))));
|
|||
|
});
|
|||
|
$tocLi = $tocUl.children('li');
|
|||
|
$tocUl.on('click', 'a', function(e) {
|
|||
|
e.preventDefault();
|
|||
|
var $this = $(this);
|
|||
|
scrolling = true;
|
|||
|
setState($this.parent());
|
|||
|
$scroller.scrollToAnchor($this.attr('href'), 400, function() {
|
|||
|
scrolling = false;
|
|||
|
});
|
|||
|
});
|
|||
|
}
|
|||
|
hasRendered = true;
|
|||
|
}
|
|||
|
function init() {
|
|||
|
var interval, timeout;
|
|||
|
if(!hasInit) {
|
|||
|
render(); calc(); setState(null, scrolling);
|
|||
|
// run calc every 100 millisecond
|
|||
|
interval = setInterval(function() {
|
|||
|
calc();
|
|||
|
}, 100);
|
|||
|
timeout = setTimeout(function() {
|
|||
|
clearInterval(interval);
|
|||
|
}, 45000);
|
|||
|
window.pageLoad.then(function() {
|
|||
|
setTimeout(function() {
|
|||
|
clearInterval(interval);
|
|||
|
clearTimeout(timeout);
|
|||
|
}, 3000);
|
|||
|
});
|
|||
|
$scrollTarget.on('scroll', function() {
|
|||
|
disabled || setState(null, scrolling);
|
|||
|
});
|
|||
|
$window.on('resize', window.throttle(function() {
|
|||
|
if (!disabled) {
|
|||
|
render(); calc(); setState(null, scrolling);
|
|||
|
}
|
|||
|
}, 100));
|
|||
|
}
|
|||
|
hasInit = true;
|
|||
|
}
|
|||
|
|
|||
|
setOptions(options);
|
|||
|
if (!disabled) {
|
|||
|
init();
|
|||
|
}
|
|||
|
$window.on('resize', window.throttle(function() {
|
|||
|
init();
|
|||
|
}, 200));
|
|||
|
return {
|
|||
|
setOptions: setOptions
|
|||
|
};
|
|||
|
}
|
|||
|
$.fn.toc = toc;
|
|||
|
});
|
|||
|
})();
|
|||
|
/*(function () {
|
|||
|
|
|||
|
})();*/
|
|||
|
</script><script>
|
|||
|
/* toc must before affix, since affix need to konw toc' height. */(function() {
|
|||
|
var SOURCES = window.TEXT_VARIABLES.sources;
|
|||
|
var TOC_SELECTOR = window.TEXT_VARIABLES.site.toc.selectors;
|
|||
|
window.Lazyload.js(SOURCES.jquery, function() {
|
|||
|
var $window = $(window);
|
|||
|
var $articleContent = $('.js-article-content');
|
|||
|
var $tocRoot = $('.js-toc-root'), $col2 = $('.js-col-aside');
|
|||
|
var toc;
|
|||
|
var tocDisabled = false;
|
|||
|
var hasSidebar = $('.js-page-root').hasClass('layout--page--sidebar');
|
|||
|
var hasToc = $articleContent.find(TOC_SELECTOR).length > 0;
|
|||
|
|
|||
|
function disabled() {
|
|||
|
return $col2.css('display') === 'none' || !hasToc;
|
|||
|
}
|
|||
|
|
|||
|
tocDisabled = disabled();
|
|||
|
|
|||
|
toc = $tocRoot.toc({
|
|||
|
selectors: TOC_SELECTOR,
|
|||
|
container: $articleContent,
|
|||
|
scrollTarget: hasSidebar ? '.js-page-main' : null,
|
|||
|
scroller: hasSidebar ? '.js-page-main' : null,
|
|||
|
disabled: tocDisabled
|
|||
|
});
|
|||
|
|
|||
|
$window.on('resize', window.throttle(function() {
|
|||
|
tocDisabled = disabled();
|
|||
|
toc && toc.setOptions({
|
|||
|
disabled: tocDisabled
|
|||
|
});
|
|||
|
}, 100));
|
|||
|
|
|||
|
});
|
|||
|
})();
|
|||
|
(function() {
|
|||
|
var SOURCES = window.TEXT_VARIABLES.sources;
|
|||
|
window.Lazyload.js(SOURCES.jquery, function() {
|
|||
|
var $window = $(window), $pageFooter = $('.js-page-footer');
|
|||
|
var $pageAside = $('.js-page-aside');
|
|||
|
var affix;
|
|||
|
var tocDisabled = false;
|
|||
|
var hasSidebar = $('.js-page-root').hasClass('layout--page--sidebar');
|
|||
|
|
|||
|
affix = $pageAside.affix({
|
|||
|
offsetBottom: $pageFooter.outerHeight(),
|
|||
|
scrollTarget: hasSidebar ? '.js-page-main' : null,
|
|||
|
scroller: hasSidebar ? '.js-page-main' : null,
|
|||
|
scroll: hasSidebar ? $('.js-page-main').children() : null,
|
|||
|
disabled: tocDisabled
|
|||
|
});
|
|||
|
|
|||
|
$window.on('resize', window.throttle(function() {
|
|||
|
affix && affix.setOptions({
|
|||
|
disabled: tocDisabled
|
|||
|
});
|
|||
|
}, 100));
|
|||
|
|
|||
|
window.pageAsideAffix = affix;
|
|||
|
});
|
|||
|
})();
|
|||
|
</script><!---->
|
|||
|
|
|||
|
</div>
|
|||
|
<script>(function () {
|
|||
|
var $root = document.getElementsByClassName('root')[0];
|
|||
|
if (window.hasEvent('touchstart')) {
|
|||
|
$root.dataset.isTouch = true;
|
|||
|
document.addEventListener('touchstart', function(){}, false);
|
|||
|
}
|
|||
|
})();
|
|||
|
</script>
|
|||
|
</body>
|
|||
|
</html>
|
|||
|
|