2305 lines
202 KiB
HTML
2305 lines
202 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>freebox--IPv6-delegation - YannStatic</title>
|
|||
|
|
|||
|
<meta name="description" content="IPV6 Freebox">
|
|||
|
<link rel="canonical" href="https://static.rnmkcy.eu/2019/12/25/freebox-IPv6-delegation.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.5
|
|||
|
" 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;">freebox--IPv6-delegation</h1></header></div><meta itemprop="headline" content="freebox--IPv6-delegation"><div class="article__info clearfix"><ul class="left-col menu"><li>
|
|||
|
<a class="button button--secondary button--pill button--sm"
|
|||
|
href="/archive.html?tag=box">box</a>
|
|||
|
</li></ul><ul class="right-col menu"><li>
|
|||
|
<i class="far fa-calendar-alt"></i> <span title="Création" style="color:#FF00FF">25 déc. 2019</span>
|
|||
|
|
|||
|
<span title="Modification" style="color:#00FF7F">23 nov. 2018</span></li></ul></div><meta itemprop="datePublished" content="2018-11-23T00:00:00+01:00">
|
|||
|
<meta itemprop="keywords" content="box"><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><h1 id="ipv6-freebox">IPV6 Freebox</h1>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>De plus en plus de réseaux proposent une adresse en IPv6. Je ne détaillerai pas ce qu’est IPv6, le pourquoi et la nécessité de le développer...
|
|||
|
|
|||
|
Obtenir une IPv6 sur sa machine Yunohost
|
|||
|
|
|||
|
Dans les prérequis, il y a :
|
|||
|
|
|||
|
sur la Freebox, dans la partie administration, avoir activé l’IPv6.
|
|||
|
avoir IPv6 d’actif sur l’OS sur lequel se trouve Yunohost (Debian Jessie dans mon cas)
|
|||
|
|
|||
|
Avec l’IPv6, chaque machine se voit attribuer une IP publique, basée sur l’IP principale (celle de la Freebox) et l’adresse MAC de l’équipement concerné (pour faire simple). Une fois IPv6 activée sur la Freebox, la machine Yunohost étant réglée en DHCP (avec comme paramétrage au niveau de la Freebox de toujours associer la même IP), la machine se voit attribuer 2 IPv6.
|
|||
|
|
|||
|
Une adresse commençant par fe80:: qui est une adresse locale (fonctionnant uniquement sur le sous-réseau physique local : le switch et le wifi de la Freebox). Une adresse commençant par 2a01:exxx:xxxx:xxxx : qui est une adresse publique (La Freebox ayant l’adresse 2a01:exxx:xxxx:xxxx::1)
|
|||
|
|
|||
|
Pour chaque machine ayant une IPv6, la Freebox ouvre un tunnel vers Internet (indépendamment de la redirection de ports nécessaire pour l’adresse en IPv4 qui n’est que privée, liée au réseau local. Le NAT et la redirection de ports de l’IPv4 publique de la Freebox vers l’IPv4 locale de la machine sous Yunohost n’est utile qu’en IPv4). La redirection n’est donc plus nécessaire puisque la machine peut être attaquée via le tunnel V6. Comme indiqué, l’adresse IPv6 est publique : la machine est donc directement visible et accessible depuis Internet (d’où la nécessité de surveiller les logs, de faire les mises à jour, d’avoir un pare-feu activé, fail2ban... tout ce qui est installé et configuré par défaut dans Yunohost).
|
|||
|
|
|||
|
On peut voir ces deux adresses via un
|
|||
|
|
|||
|
sudo ifconfig /all
|
|||
|
|
|||
|
eth0 Link encap:Ethernet HWaddr 78:xx:xx:xx:xx:xx
|
|||
|
inet adr:192.168.0.1 Bcast:192.168.0.255 Masque:255.255.255.0
|
|||
|
adr inet6: 2a01:exxx:xxxx:xxxx:b81b/64 Scope:Global
|
|||
|
adr inet6: fe80::7a2b:cbff:fea8:b81b/64 Scope:Lien
|
|||
|
(...)
|
|||
|
|
|||
|
Configuration du DNS
|
|||
|
|
|||
|
Si on a un nom de domaine, par exemple mondomaine.fr, il faut noter l’adresse IPv6 publique de la machine sous Yunohost et dans la configuration des entrées DNS, en plus de l’entrée correspondant à l’association de l’IPv4 publique de la Freebox (ici représentée par XYZ.XYZ.XYZ.XYZ)
|
|||
|
|
|||
|
Nom Type Valeur
|
|||
|
@ A XYZ.XYZ.XYZ.XYZ
|
|||
|
|
|||
|
On ajoute une entrée AAAA avec l’adresse IPv6 publique de la machine sous Yunohost (ici représentée par 2a01:exxx:xxxx:xxxx:b81b)
|
|||
|
|
|||
|
Nom Type Valeur
|
|||
|
@ A XYZ.XYZ.XYZ.XYZ
|
|||
|
@ AAAA 2a01:exxx:xxxx:xxxx:b81b
|
|||
|
|
|||
|
Validation de l’accessibilité en IPv6
|
|||
|
|
|||
|
Depuis une autre machine, sur un réseau extérieur et pour lequel on a une adresse IPv6, on peut vérifier qu’un
|
|||
|
|
|||
|
ping -v6 mondomaine.fr
|
|||
|
|
|||
|
répond et renvoie bien l’adresse IPv6 de la machine Yunohost.
|
|||
|
|
|||
|
On peut aussi tester un traceroute, ou encore, depuis un navigateur, saisir dans la barre d’adresse (avec les crochets, contrairement à une IPv4)
|
|||
|
|
|||
|
http://[2a01:exxx:xxxx:xxxx:b81b]
|
|||
|
|
|||
|
et ça doit afficher la même page par défaut que si on va sur l’url http://mondomaine.fr1
|
|||
|
|
|||
|
Conclusion
|
|||
|
|
|||
|
Si ça marche, votre machine Yunohost est bien accessible en IPv6 de l’extérieur. Pratique quand on souhaite s’y connecter depuis un réseau sur lequel on n’a pas d’IPv4 (seulement de l’IPv6).
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<hr />
|
|||
|
|
|||
|
<p>2a01:e34:ee6a:b270::/64
|
|||
|
fe80::beae:c5ff:fe57:150c</p>
|
|||
|
|
|||
|
<p>root@yanspm:/home/yann# cat /etc/network/interfaces</p>
|
|||
|
<h1 id="this-file-describes-the-network-interfaces-available-on-your-system">This file describes the network interfaces available on your system</h1>
|
|||
|
<h1 id="and-how-to-activate-them-for-more-information-see-interfaces5">and how to activate them. For more information, see interfaces(5).</h1>
|
|||
|
|
|||
|
<p>source /etc/network/interfaces.d/*</p>
|
|||
|
|
|||
|
<h1 id="the-loopback-network-interface">The loopback network interface</h1>
|
|||
|
<p>auto lo
|
|||
|
iface lo inet loopback</p>
|
|||
|
|
|||
|
<h1 id="the-primary-network-interface">The primary network interface</h1>
|
|||
|
<p>allow-hotplug eth0
|
|||
|
iface eth0 inet dhcp</p>
|
|||
|
<h1 id="this-is-an-autoconfigured-ipv6-interface">This is an autoconfigured IPv6 interface</h1>
|
|||
|
<p>iface eth0 inet6 static
|
|||
|
address 2a01:e34:ee6a:b270::1
|
|||
|
netmask 64
|
|||
|
post-up ip -6 route add default via fe80::beae:c5ff:fe57:150c dev eth0</p>
|
|||
|
|
|||
|
<p>MODIFICATION</p>
|
|||
|
|
|||
|
<p>root@yanspm:/home/yann# cat /etc/network/interfaces</p>
|
|||
|
<h1 id="this-file-describes-the-network-interfaces-available-on-your-system-1">This file describes the network interfaces available on your system</h1>
|
|||
|
<h1 id="and-how-to-activate-them-for-more-information-see-interfaces5-1">and how to activate them. For more information, see interfaces(5).</h1>
|
|||
|
|
|||
|
<p>source /etc/network/interfaces.d/*</p>
|
|||
|
|
|||
|
<h1 id="the-loopback-network-interface-1">The loopback network interface</h1>
|
|||
|
<p>auto lo
|
|||
|
iface lo inet loopback</p>
|
|||
|
|
|||
|
<h1 id="the-primary-network-interface-1">The primary network interface</h1>
|
|||
|
<p>allow-hotplug eth0
|
|||
|
iface eth0 inet dhcp</p>
|
|||
|
<h1 id="this-is-an-autoconfigured-ipv6-interface-1">This is an autoconfigured IPv6 interface</h1>
|
|||
|
<p>iface eth0 inet6 static
|
|||
|
address 2a01:e34:ee6a:b270::1
|
|||
|
netmask 64
|
|||
|
post-up ip -6 route add default via fe80::224:d4ff:fea6:aa20 dev eth0</p>
|
|||
|
|
|||
|
<p>Il faut ensuite installer le paquet “radvd” et le configurer pour qu’il annonce le bon préfixe sur le réseau local. Dans le cas de notre exemple, cela donne les commandes suivantes (pensez a remplacer le préfixe par votre préfixe, celui que vous avez noté) :
|
|||
|
pi@raspberrypi ~ $ sudo apt-get install radvd</p>
|
|||
|
|
|||
|
<p>pi@raspberrypi ~ $ sudo cat «EOF >/etc/radvd.conf
|
|||
|
interface eth0
|
|||
|
{
|
|||
|
AdvSendAdvert on;
|
|||
|
prefix 2a01:e34:ee6a:b270::/64
|
|||
|
{
|
|||
|
};
|
|||
|
}; <br />
|
|||
|
EOF
|
|||
|
Une fois cela fait, il faut activer le routage des paquets IPv6 dans Linux.
|
|||
|
pi@raspberrypi ~ $ sudo echo “net.ipv6.conf.all.forwarding=1” » /etc/sysctl.conf</p>
|
|||
|
|
|||
|
<p>A partir de maintenant, si on redémarre la Raspberry Pi, elle va agir en tant que routeur IPv6, et l’IPv6 sera fonctionnel sur le réseau LAN, tout comme si on avait tout simplement activé IPv6 sur la Freebox.</p>
|
|||
|
|
|||
|
<hr />
|
|||
|
|
|||
|
<p>Adresse IPV6 lien local : fe80::224:d4ff:fea6:aa20
|
|||
|
Délégation de prefixe
|
|||
|
Attention si vous configurez un Next Hop pour le premier subnet, il ne sera plus annoncé par la Freebox sur votre réseau</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Prefixe : 2a01:e34:ee6a:b270::/64
|
|||
|
Next hop :
|
|||
|
|
|||
|
Prefixe : 2a01:e34:ee6a:b271::/64
|
|||
|
Next hop : fe80::beae:c5ff:fe57:150c eth0 shuttle
|
|||
|
|
|||
|
Prefixe : 2a01:e34:ee6a:b272::/64
|
|||
|
Next hop :
|
|||
|
|
|||
|
Prefixe : 2a01:e34:ee6a:b273::/64
|
|||
|
Next hop :
|
|||
|
|
|||
|
Prefixe : 2a01:e34:ee6a:b274::/64
|
|||
|
Next hop :
|
|||
|
|
|||
|
Prefixe : 2a01:e34:ee6a:b275::/64
|
|||
|
Next hop :
|
|||
|
|
|||
|
Prefixe : 2a01:e34:ee6a:b276::/64
|
|||
|
Next hop :
|
|||
|
|
|||
|
Prefixe : 2a01:e34:ee6a:b277::/64
|
|||
|
Next hop :
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>fe80::f:54ff:fe13:2400 wlan0 YanHotSpot<br />
|
|||
|
fe80::c2:9ff:fe40:f22b eth0 Olibox<br />
|
|||
|
fe80::a00:27ff:fecc:c8ba<br />
|
|||
|
fe80::20f:54ff:fe13:24b5</p>
|
|||
|
|
|||
|
<h1 id="ipv6-freecubiebox-olibox-shuttle">IPV6-free–cubiebox-olibox-shuttle</h1>
|
|||
|
|
|||
|
<h2 id="paramétrage-ipv6-free">Paramétrage IPV6 free</h2>
|
|||
|
|
|||
|
<p>Le lien <a href="https://utux.fr/index.php?tag/ipv6">https://utux.fr/index.php?tag/ipv6</a></p>
|
|||
|
|
|||
|
<p>La première chose à faire consiste à se rendre dans l’interface de configuration de la freebox grâce à l’adresse <mafreebox.free.fr> qui fonctionne même si vous êtes en bridge.</mafreebox.free.fr></p>
|
|||
|
|
|||
|
<p>Puis rendez-vous dans : <strong>Paramètres</strong> de la Freebox, onglet <strong>Mode avancé</strong>, <strong>Configuration IPv6</strong>.</p>
|
|||
|
|
|||
|
<p>Adresse IPV6 lien local : fe80::224:d4ff:fea6:aa20
|
|||
|
Délégation de prefixe
|
|||
|
Attention si vous configurez un Next Hop pour le premier subnet, il ne sera plus annoncé par la Freebox sur votre réseau</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Prefixe : 2a01:e34:ee6a:b270::/64
|
|||
|
Next hop :
|
|||
|
|
|||
|
Prefixe : 2a01:e34:ee6a:b271::/64
|
|||
|
Next hop : fe80::beae:c5ff:fe57:150c eth0 shuttle
|
|||
|
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Paraméter le réseau ipv6 sur “shuttle”</p>
|
|||
|
|
|||
|
<h1 id="this-is-an-autoconfigured-ipv6-interface-2">This is an autoconfigured IPv6 interface</h1>
|
|||
|
<p>iface eth0 inet6 static
|
|||
|
address 2a01:e34:ee6a:b271::1
|
|||
|
netmask 64
|
|||
|
post-up ip -6 route add default via fe80::beae:c5ff:fe57:150c dev eth0</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
|
|||
|
|
|||
|
|
|||
|
---
|
|||
|
|
|||
|
## IPv6 sécurisé sur Freebox Révolution avec une Raspberry Pi
|
|||
|
|
|||
|
[IPv6 sécurisé sur Freebox Révolution avec une Raspberry Pi](https://www.goudal.net/?p=6)
|
|||
|
Posted on October 3, 2015
|
|||
|
|
|||
|
IPv6 est le protocole du futur Internet. Il existe depuis longtemps, mais la transition massive n’a toujours pas eu lieu, malgré la pénurie d’adresses IPv4.
|
|||
|
|
|||
|
Free a été le premier FAI grand public à proposer l’IPv6 via leur offre Freebox.
|
|||
|
|
|||
|
N’importe quel abonné Freebox peut aujourd’hui activer l’IPv6. La Freebox va alors établir un tunnel 6to4rd pour fournir une connectivité IPv6 sur son LAN, et va se mettre a envoyer des paquets **Router Advertisement** pour annoncer le préfixe (un /64) qui a été alloué aux machines sur le LAN qui savent faire de l’IPv6.
|
|||
|
|
|||
|
Dès lors, toute machine connectée au LAN de la Freebox et sachant parler l’IPv6 va pouvoir s’auto-assigner une IPv6 “publique” (ayant un scope global) et parler en IPv6 avec le reste du monde.
|
|||
|
|
|||
|
**Problème** : Puisque chaque machine obtient une adresse IPv6 ayant un scope global, n’importe quel autre machine sur l’Internet IPv6 peut communiquer avec tous les devices connectés sur le LAN. La Freebox ne propose strictement aucun moyen de filtrer le traffic IPv6. Il faudrait donc mettre en place un filtrage sur chaque device, sans exception. Sauf qu’installer un firewall sur une machine Windows, Mac ou Linux, c’est facile, mais dans certains autres cas, c’est tout simplement impossible.
|
|||
|
|
|||
|
Par exemple, une imprimante réseau qui saurait faire de l’IPv6 serait alors potentiellement utilisable par n’importe qui, depuis l’autre bout du monde, et il n’y a aucun moyen d’empêcher cela.
|
|||
|
|
|||
|
Activer l’IPv6 sur sa Freebox est donc un pari risqué. Le nombre d’adresses disponibles fait qu’il est certes très peu probable qu’on arrive a trouver l’adresse IPv6 de votre imprimante. Mais le risque existe bel et bien, et l’imprimante n’est qu’un exemple pas trop “grave” du problème (au pire, on vous vide le bac de feuilles / la cartouche) car l’accès à d’autres devices pourrait être beaucoup plus compromettant (Camera IP, Serveur NAS, …).
|
|||
|
|
|||
|
Que faire, alors ?
|
|||
|
|
|||
|
Plusieurs solutions existent :
|
|||
|
|
|||
|
* Ne pas activer l’IPv6. Ca peut paraitre bête, mais pour l’instant, le commun des mortels n’a pas vraiment besoin d’IPv6. Donc a moins de savoir vraiment ce que l’on fait, mieux vaut ne pas l’activer tout simplement.
|
|||
|
* Utiliser la Freebox comme modem (en mode Bridge) et avoir un routeur/bridge externe. On trouve plusieurs tutos sur Internet pour faire cela, mais cette solution a plusieurs inconvénients, car lorsque la Freebox est en mode Bridge, on perd un certain nombre de fonctionnalités, dont le Wifi, l’UPnP, la Seedbox, la TNT en multiposte, … .(cf: <http://www.free.fr/assistance/5082.html>)
|
|||
|
* Conserver la Freebox en mode Routeur, et utiliser un deuxième routeur externe uniquement pour la gestion de l’IPv6. C’est cette solution que je vais décrire ici, et pour ce faire, je vais utiliser une Raspberry Pi, car c’est une solution qui ne coute pas cher, qui consomme peu et qui peut se brancher facilement à la Freebox. On peut cependant faire la meme chose avec n’importe quel device qui fait tourner Linux en adaptant un peu.
|
|||
|
|
|||
|
Depuis Freebox OS 2.0, la configuration IPv6 permet de configurer des “next hops” pour 8 prefixes /64 (Autrement dit, chaque abonné Freebox dispose d’un /61). De plus, lorsqu’un next hop est configuré sur le premier prefixe, la Freebox n’envoie plus de Router Advertisements sur le LAN.
|
|||
|
|
|||
|
L’objectif est donc de :
|
|||
|
|
|||
|
* Mettre une Raspberry Pi sur le LAN, et activer IPv6
|
|||
|
* Router le premier préfixe vers la Raspberry Pi
|
|||
|
* Configurer une adresse de ce préfixe sur l’interface LAN de la Raspberry Pi
|
|||
|
* Faire en sorte que la Raspberry Pi envoie les Router Advertisements pour ce préfixe sur son interface LAN
|
|||
|
* Activer le routage des paquets IPv6
|
|||
|
* Mettre en place des règles IP6Tables pour filtrer le traffic IPv6
|
|||
|
|
|||
|
Tout d’abord, il faut donc brancher la Raspberry Pi au LAN de la Freebox. Elle va obtenir une IPv4 automatiquement, grâce au DHCP.
|
|||
|
|
|||
|
La Raspberry Pi n’a pas IPv6 actif par défaut. Il faut donc l’activer avec les commandes suivantes :
|
|||
|
|
|||
|
pi@raspberrypi ~ $ sudo modprobe ipv6
|
|||
|
pi@raspberrypi ~ $ sudo echo ipv6 >> /etc/modules
|
|||
|
|
|||
|
Cela aura pour effet de rajouter une adresse IP de type Link-local a l’interface eth0. Cette adresse est automatiquement générée en se basant sur l’adresse MAC, elle sera donc toujours la même. Il faut la noter, on en aura besoin plus tard. Dans l’exemple ci dessous, il s’agit de fe80::ba27:ebff:fe36:cb4e
|
|||
|
|
|||
|
pi@raspberrypi ~ $ sudo ifconfig eth0
|
|||
|
|
|||
|
</code></pre></div></div>
|
|||
|
<p>eth0 Link encap:Ethernet HWaddr b8:27:eb:36:cb:4e<br />
|
|||
|
inet addr:192.168.42.34 Bcast:192.168.42.255 Mask:255.255.255.0
|
|||
|
inet6 addr: fe80::ba27:ebff:fe36:cb4e/64 Scope:Link
|
|||
|
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
|
|||
|
RX packets:82134 errors:0 dropped:3 overruns:0 frame:0
|
|||
|
TX packets:83989 errors:0 dropped:0 overruns:0 carrier:0
|
|||
|
collisions:0 txqueuelen:1000
|
|||
|
RX bytes:31899129 (30.4 MiB) TX bytes:34922782 (33.3 MiB)</p>
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
|
|||
|
* il faut activer IPv6 dans l’interface web de la Freebox, et configurer le Next Hop du premier préfixe avec l’adresse IP Link-local de la Raspberry Pi que l’on a notée plus haut.
|
|||
|
* Il faut également noter l’adresse IPv6 lien local de la Freebox (ici fe80::f6ca:e5ff:fe57:1ceb) ainsi que le préfixe (ici 2001:db8:dead:beef::/64). Le préfixe utilisé ici à été modifié pour rédiger ce tutoriel, n’essayez pas d’y accéder, c’est une IP qui n’existe pas vraiment.
|
|||
|
|
|||
|
![Screen-Shot-2015-09-27-at-23](file://media/1880561980.png)
|
|||
|
|
|||
|
|
|||
|
Il est possible que l’adresse IPv6 lien local ne s’affiche pas, juste après avoir activé l’IPv6. Il suffit de fermer et ouvrir la page de configuration a nouveau pour la voir s’afficher.
|
|||
|
|
|||
|
Il est bien important que ce soit le premier préfixe pour lequel on spécifie le next hop. Autrement, la Freebox va continuer d’envoyer des Router Advertisements sur le LAN, et le traffic IPv6 risque donc de passer directement par la Freebox, sans passer par la Raspberry Pi, et donc sans filtrage.
|
|||
|
|
|||
|
A partir de maintenant, on a donc un prefixe IPv6 public qui est routé de la Freebox vers la Raspberry Pi (a travers son IP Link-local sur le LAN).
|
|||
|
|
|||
|
Passons maintenant a la configuration IPv6 de la Raspberry Pi.
|
|||
|
|
|||
|
On va assigner la premiere adresse IPv6 du préfixe a l’interface eth0, et rajouter une route par défaut vers l’adresse IPv6 Link-local de la Freebox. Dans le cas de notre exemple, il faut donc executer la commande suivante. Pensez donc a remplacer les deux adresses IPv6 par celles notées à l’étape prédédente (pour construire l’adresse IPv6 de l’interface, il faut simplement retirer le “/64″ a la fin du préfixe, et le remplacer par “1”) :
|
|||
|
|
|||
|
</code></pre></div></div>
|
|||
|
<p>sudo cat «EOF »/etc/network/interfaces
|
|||
|
iface eth0 inet6 static
|
|||
|
address 2001:db8:dead:beef::1
|
|||
|
netmask 64
|
|||
|
post-up ip -6 route add default via fe80::f6ca:e5ff:fe57:1ceb dev eth0
|
|||
|
EOF</p>
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
|
|||
|
Il faut ensuite installer le paquet “radvd” et le configurer pour qu’il annonce le bon préfixe sur le réseau local. Dans le cas de notre exemple, cela donne les commandes suivantes (pensez a remplacer le préfixe par votre préfixe, celui que vous avez noté) :
|
|||
|
|
|||
|
pi@raspberrypi ~ $ sudo apt-get install radvd
|
|||
|
|
|||
|
</code></pre></div></div>
|
|||
|
<p>sudo cat «EOF >/etc/radvd.conf
|
|||
|
interface eth0
|
|||
|
{
|
|||
|
AdvSendAdvert on;
|
|||
|
prefix 2001:db8:dead:beef::/64
|
|||
|
{
|
|||
|
};
|
|||
|
}; <br />
|
|||
|
EOF</p>
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
|
|||
|
Une fois cela fait, il faut activer le routage des paquets IPv6 dans Linux.
|
|||
|
|
|||
|
pi@raspberrypi ~ $ sudo echo "net.ipv6.conf.all.forwarding=1" >> /etc/sysctl.conf
|
|||
|
|
|||
|
A partir de maintenant, si on redémarre la Raspberry Pi, elle va agir en tant que routeur IPv6, et l’IPv6 sera fonctionnel sur le réseau LAN, tout comme si on avait tout simplement activé IPv6 sur la Freebox. Mais le problème que l’on souhaitait résoudre a la base sera présent, puisqu’aucun filtrage n’est en place. Donc toutes les machines sur le LAN qui savent faire de l’IPv6 seront directement joignables depuis le reste de l’Internet IPv6.
|
|||
|
|
|||
|
Il nous reste donc a mettre en place un filtrage basique. L’idée est de retomber dans le même genre de protection que celle fournie implicitement en IPv4 par le NAT. A savoir, les machines vers le LAN peuvent accéder a toute machine vers l’Internet, mais l’inverse n’est pas possible. Pour cela, on crée un fichier de configuration pour ip6tables (le firewall IPv6 de Linux) :
|
|||
|
|
|||
|
</code></pre></div></div>
|
|||
|
<p>sudo cat «EOF >/etc/ip6tables.conf
|
|||
|
*filter
|
|||
|
:INPUT ACCEPT [14:960]
|
|||
|
:FORWARD DROP [34:3344]
|
|||
|
:OUTPUT ACCEPT [63:15248]
|
|||
|
-A FORWARD -s 2001:db8:dead:beef::/64 -j ACCEPT
|
|||
|
-A FORWARD -m state –state RELATED,ESTABLISHED -j ACCEPT
|
|||
|
COMMIT
|
|||
|
EOF</p>
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
|
|||
|
Encore une fois, n’oubliez pas d’adapter la commande a votre préfixe. Ce que l’on fait ici c’est qu’on va refuser de router les paquets qui viennent de l’internet vers notre LAN, sauf si on est capable de les associer a une session qui a été initiée depuis notre LAN vers l’Internet.
|
|||
|
|
|||
|
Reste maintenant a faire en sorte que ces règles soient mises en place au démarrage :
|
|||
|
|
|||
|
</code></pre></div></div>
|
|||
|
<p>pi@raspberrypi ~ $ sudo cat «EOF »/etc/network/interfaces
|
|||
|
post-up ip6tables-restore < /etc/ip6tables.conf
|
|||
|
EOF</p>
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
|
|||
|
|
|||
|
Puis a rebooter la Raspberry Pi afin d’appliquer tous ces nouveaux paramètres.
|
|||
|
|
|||
|
pi@raspberrypi ~ $ sudo reboot
|
|||
|
|
|||
|
Voila, c’est terminé !
|
|||
|
|
|||
|
Si tout se passe bien, vos machines sur le LAN devraient maintenant recevoir un router advertisement IPv6 de la Raspberry Pi, et donc s’auto-assigner une adresse IPv6 globale, et router leur traffic IPv6 vers la Raspberry Pi, qui va le faire suivre a la Freebox.
|
|||
|
|
|||
|
Vous pouvez verifier que tout fonctionne en allant visiter le site http://test-ipv6.com/ qui devrait normalement vous retourner un score de 10/10.
|
|||
|
|
|||
|
|
|||
|
This entry was posted in Freebox, IPv6, Linux, Networking, Raspberry Pi by Fanfwe. Bookmark the permalink.
|
|||
|
|
|||
|
## OpenVpn carte Olimex + MultiWebApp
|
|||
|
|
|||
|
Installation
|
|||
|
|
|||
|
sudo apt install openvpn
|
|||
|
|
|||
|
Configuration **/etc/openvpn/vpntest.conf**
|
|||
|
|
|||
|
Bash pour activer le VPN
|
|||
|
|
|||
|
sudo nano /usr/local/bin/startvpn
|
|||
|
|
|||
|
</code></pre></div></div>
|
|||
|
<p>#!/bin/bash</p>
|
|||
|
|
|||
|
<h1 id="lancement-openvpn">Lancement openvpn</h1>
|
|||
|
<p>if [ $(systemctl is-active openvpn@vpntest >/dev/null 2>&1 && echo true || echo false) = false ]; then
|
|||
|
echo “lancer openvpn et attendre 5 secondes …”
|
|||
|
sudo systemctl start openvpn@vpntest
|
|||
|
# attendre 5 secondes
|
|||
|
sleep 5
|
|||
|
else
|
|||
|
echo “démarré”
|
|||
|
exit 0
|
|||
|
fi</p>
|
|||
|
<h1 id="règles-iptables">règles iptables</h1>
|
|||
|
<p>if [ ! -e “/tmp/iptablesvpn” ]; then
|
|||
|
echo “ajout des règles iptables”
|
|||
|
sudo iptables -w -t nat -A POSTROUTING -o tun0 -j MASQUERADE -m comment –comment “Use VPN IP for tun0”<br />
|
|||
|
#sudo iptables -w -A FORWARD -s 10.118.111.0/24 -i wlan0 -o tun0 -m conntrack –ctstate NEW -j ACCEPT -m comment –comment “Allow only traffic from wlan0 clients to tun0”
|
|||
|
#sudo iptables -w -A FORWARD -s 192.168.0.0/24 -i eth0 -o tun0 -m conntrack –ctstate NEW -j ACCEPT -m comment –comment “Allow only traffic from eth0 clients to tun0”<br />
|
|||
|
# on a ajouté les règles iptables
|
|||
|
touch /tmp/iptablesvpn
|
|||
|
else
|
|||
|
echo “règles iptables existantes”
|
|||
|
fi</p>
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
|
|||
|
Droits en exécution
|
|||
|
|
|||
|
sudo chmod +x /usr/local/bin/startvpn
|
|||
|
|
|||
|
Démarrage
|
|||
|
|
|||
|
startvpn
|
|||
|
|
|||
|
Vérification
|
|||
|
|
|||
|
</code></pre></div></div>
|
|||
|
<p>debian@olibox:~$ ip link |grep tun
|
|||
|
6: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN mode DEFAULT group default qlen 100</p>
|
|||
|
|
|||
|
<p>debian@olibox:~$ ip route
|
|||
|
0.0.0.0/1 via 10.8.0.1 dev tun0
|
|||
|
default via 192.168.0.254 dev eth0
|
|||
|
10.0.242.0/24 dev wlan0 proto kernel scope link src 10.0.242.1
|
|||
|
10.8.0.0/24 dev tun0 proto kernel scope link src 10.8.0.2
|
|||
|
10.118.111.0/24 dev hotspot2 proto kernel scope link src 10.118.111.1
|
|||
|
10.231.51.0/24 dev hotspot1 proto kernel scope link src 10.231.51.1
|
|||
|
37.235.49.24 via 192.168.0.254 dev eth0
|
|||
|
128.0.0.0/1 via 10.8.0.1 dev tun0
|
|||
|
192.168.0.0/24 dev eth0 proto kernel scope link src 192.168.0.43</p>
|
|||
|
|
|||
|
<h1 id="règles">règles</h1>
|
|||
|
<p>$ sudo iptables -L -t nat</p>
|
|||
|
|
|||
|
<p>MASQUERADE all – anywhere anywhere /* Use VPN IP for tun0 */</p>
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
|
|||
|
#### Multi custom webapp (multi_webapp)
|
|||
|
|
|||
|
**multi_webapp** : <https://github.com/YunoHost-Apps/multi_webapp_ynh>
|
|||
|
|
|||
|
App vide sans accès FTP
|
|||
|
Dossier : /me
|
|||
|
Publique : non
|
|||
|
|
|||
|
Modification configuration **/etc/php5/fpm/pool.d/webapp_me.conf** pour autoriser le *php exec*
|
|||
|
|
|||
|
</code></pre></div></div>
|
|||
|
<p>[webapp_me]</p>
|
|||
|
|
|||
|
<p>listen = /var/run/php5-fpm-webapp_me.sock</p>
|
|||
|
|
|||
|
<p>listen.owner = www-data
|
|||
|
listen.group = www-data
|
|||
|
listen.mode = 0600</p>
|
|||
|
|
|||
|
<p>user = admin
|
|||
|
group = admins</p>
|
|||
|
|
|||
|
<p>pm = dynamic
|
|||
|
pm.max_children = 6
|
|||
|
pm.start_servers = 3
|
|||
|
pm.min_spare_servers = 3
|
|||
|
pm.max_spare_servers = 5
|
|||
|
pm.max_requests = 500
|
|||
|
pm.status_path = /fpm-status</p>
|
|||
|
|
|||
|
<p>ping.path = /ping</p>
|
|||
|
|
|||
|
<p>request_terminate_timeout = 600s</p>
|
|||
|
|
|||
|
<p>request_slowlog_timeout = 0</p>
|
|||
|
|
|||
|
<p>slowlog = /var/log/nginx/webapp_me.slow.log</p>
|
|||
|
|
|||
|
<p>rlimit_files = 4096</p>
|
|||
|
|
|||
|
<p>rlimit_core = 0</p>
|
|||
|
|
|||
|
<p>chdir = /var/www/webapp_yan/me</p>
|
|||
|
|
|||
|
<p>catch_workers_output = no</p>
|
|||
|
|
|||
|
<p>php_value[max_execution_time] = 600
|
|||
|
php_value[upload_max_filesize] = 10G
|
|||
|
php_value[post_max_size] = 10G</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
|
|||
|
Relancer php5-fpm
|
|||
|
|
|||
|
sudo systemctl restart php5-fpm
|
|||
|
|
|||
|
Le fichier de configuration nginx
|
|||
|
|
|||
|
/etc/nginx/conf.d/yan.me.d/webapp_me.conf
|
|||
|
|
|||
|
</code></pre></div></div>
|
|||
|
<p>location /me {
|
|||
|
alias /var/www/webapp_yan/me/ ;
|
|||
|
if ($scheme = http) {
|
|||
|
rewrite ^ https://$server_name$request_uri? permanent;
|
|||
|
}
|
|||
|
index index.html index.php ;
|
|||
|
try_files $uri $uri/ index.php;
|
|||
|
location ~ [^/].php(/|$) {
|
|||
|
fastcgi_split_path_info ^(.+?.php)(/.*)$;
|
|||
|
fastcgi_pass unix:/var/run/php5-fpm-webapp_me.sock;
|
|||
|
fastcgi_index index.php;
|
|||
|
include fastcgi_params;
|
|||
|
fastcgi_param REMOTE_USER $remote_user;
|
|||
|
fastcgi_param PATH_INFO $fastcgi_path_info;
|
|||
|
fastcgi_param SCRIPT_FILENAME $request_filename;
|
|||
|
}</p>
|
|||
|
|
|||
|
<p># Include SSOWAT user panel.
|
|||
|
include conf.d/yunohost_panel.conf.inc;
|
|||
|
}</p>
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
|
|||
|
On implémente le micro framework php limonade
|
|||
|
|
|||
|
Structure du dossier
|
|||
|
|
|||
|
tree -L 4 /var/www/webapp_yan/me/
|
|||
|
|
|||
|
</code></pre></div></div>
|
|||
|
<p>/var/www/webapp_yan/me/
|
|||
|
├── config.php
|
|||
|
├── entries
|
|||
|
├── index.php
|
|||
|
├── lib
|
|||
|
│ ├── limonade
|
|||
|
│ ├── limonade.php
|
|||
|
│ ├── vendors
|
|||
|
│ │ └── markdown
|
|||
|
│ │ ├── markdown.php
|
|||
|
│ │ └── PHP Markdown Readme.text
|
|||
|
│ └── vendors.php
|
|||
|
├── pages
|
|||
|
├── public
|
|||
|
│ ├── bootstrap
|
|||
|
│ │ ├── css
|
|||
|
│ │ │ ├── bootstrap.css
|
|||
|
│ │ │ ├── bootstrap.css.map
|
|||
|
│ │ │ ├── bootstrap.min.css
|
|||
|
│ │ │ ├── bootstrap-theme.css
|
|||
|
│ │ │ ├── bootstrap-theme.css.map
|
|||
|
│ │ │ └── bootstrap-theme.min.css
|
|||
|
│ │ ├── fonts
|
|||
|
│ │ │ ├── glyphicons-halflings-regular.eot
|
|||
|
│ │ │ ├── glyphicons-halflings-regular.svg
|
|||
|
│ │ │ ├── glyphicons-halflings-regular.ttf
|
|||
|
│ │ │ └── glyphicons-halflings-regular.woff
|
|||
|
│ │ └── js
|
|||
|
│ │ ├── bootstrap.js
|
|||
|
│ │ ├── bootstrap.min.js
|
|||
|
│ │ └── npm.js
|
|||
|
│ ├── css
|
|||
|
│ │ ├── bootstrap-toggle.min.css
|
|||
|
│ │ ├── screen.css
|
|||
|
│ │ └── style.css
|
|||
|
│ ├── img
|
|||
|
│ │ ├── A20-Olinuxino-MICRO.png
|
|||
|
│ │ ├── bg_header_wikir.jpg
|
|||
|
│ │ ├── github.png
|
|||
|
│ │ ├── loading.gif
|
|||
|
│ │ └── nice.ogg
|
|||
|
│ ├── jquery
|
|||
|
│ │ └── jquery-2.1.1.min.js
|
|||
|
│ └── js
|
|||
|
│ ├── bootstrap-toggle.min.js
|
|||
|
│ └── custom.js
|
|||
|
└── views
|
|||
|
├── about.html.php
|
|||
|
├── entry.html.php
|
|||
|
├── index.html.php
|
|||
|
├── layout.html.php
|
|||
|
└── posts.html.php
|
|||
|
```</p>
|
|||
|
|
|||
|
<p>Accès web <a href="https://yan.me/me/">https://yan.me/me/</a></p>
|
|||
|
|
|||
|
</div>
|
|||
|
|
|||
|
|
|||
|
|
|||
|
<div class="d-print-none"><footer class="article__footer"><meta itemprop="dateModified" content="2019-12-25T00:00:00+01: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="/2019/12/25/firstheberg-ip-failover.html">firstheberg-ip-failover</a></div><div class="next"><span>SUIVANT</span><a href="/2019/12/25/geoloc.html">geoloc</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>
|
|||
|
|