yannstatic/static/2024/11/06/Contabo_VPS_debian_bookworm-Yunohost.html

2570 lines
219 KiB
HTML
Raw Permalink Normal View History

2024-11-07 22:37:17 +01:00
<!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>Contabo VPS debian 12 (bookworm) - Yunohost xoyaz.xyz - YannStatic</title>
<meta name="description" content="YunoHost est une distribution basée sur Debian GNU/Linux composée de logiciels libres et ayant pour objectif de faciliter la pratique de lauto-hébergement">
<link rel="canonical" href="https://static.rnmkcy.eu/2024/11/06/Contabo_VPS_debian_bookworm-Yunohost.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}&nbsp;{title}</a></li>'
searchResultTemplate: '<li><a href="{url}">{date}&nbsp;{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;">Contabo VPS debian 12 (bookworm) - Yunohost xoyaz.xyz</h1></header></div><meta itemprop="headline" content="Contabo VPS debian 12 (bookworm) - Yunohost xoyaz.xyz"><div class="article__info clearfix"><ul class="left-col menu"><li>
2024-11-08 14:10:33 +01:00
<a class="button button--secondary button--pill button--sm" style="color:#00FFFF" href="/archive.html?tag=vps">vps</a>
2024-11-07 22:37:17 +01:00
</li><li>
2024-11-08 14:10:33 +01:00
<a class="button button--secondary button--pill button--sm" style="color:#00FFFF" href="/archive.html?tag=yunohost">yunohost</a>
2024-11-07 22:37:17 +01:00
</li></ul><ul class="right-col menu"><li>
2024-11-21 17:20:33 +01:00
<i class="far fa-calendar-alt"></i>&nbsp;<span title="Création" style="color:#FF00FF">&nbsp;6&nbsp;nov.&nbsp;&nbsp;2024</span>
<span title="Modification" style="color:#00FF7F">12&nbsp;nov.&nbsp;&nbsp;2024</span></li></ul></div><meta itemprop="datePublished" content="2024-11-12T00:00:00+01:00">
2024-11-07 22:37:17 +01:00
<meta itemprop="keywords" content="vps,yunohost"><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">&#8679;</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><em>YunoHost est une distribution basée sur Debian GNU/Linux composée de logiciels libres et ayant pour objectif de faciliter la pratique de lauto-hébergement</em></p>
<p><a href="https://contabo.com/en/"><img src="/images/contabo-logo-a.png" alt="Contabo" /></a></p>
<p><strong>VPS</strong> 1 SSD<br />
<strong>IP</strong>: 37.60.230.30<br />
<strong>IPv6</strong>: 2a02:c206:2162:8819::1<br />
<strong>Location</strong>: Dusseldorf<br />
<strong>OS</strong>: Debian 12</p>
<h2 id="debian-12-bookworm">Debian 12 (bookworm)</h2>
<p><img src="/images/debian12-logo.png" alt="" height="50" /></p>
<p>On se connecte en root sur le VPS</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh root@37.60.230.30
</code></pre></div></div>
<p>Le terminal</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Linux vmi1628819.contaboserver.net 6.1.0-10-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.37-1 (2023-07-03) x86_64
_____
/ ___/___ _ _ _____ _ ___ ___
| | / _ \| \| |_ _/ \ | _ )/ _ \
| |__| (_) | .` | | |/ _ \| _ \ (_) |
\____\___/|_|\_| |_/_/ \_|___/\___/
Welcome!
This server is hosted by Contabo. If you have any questions or need help,
please don't hesitate to contact us at support@contabo.com.
</code></pre></div></div>
<h3 id="mise-à-jour--réseau-ipv6">Mise à jour + Réseau IPV6</h3>
<p>Les commandes</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt update <span class="o">&amp;&amp;</span> apt upgrade <span class="nt">-y</span> <span class="c"># Mise à jour</span>
apt autoremove <span class="nt">-y</span> <span class="c"># supprimer les paquets inutilisés</span>
apt <span class="nb">install </span>rsync curl tmux jq figlet git tree <span class="nt">-y</span> <span class="c"># Quelques outils</span>
enable_ipv6 <span class="c"># Activation ipv6</span>
reboot <span class="c"># redémarrage</span>
</code></pre></div></div>
<p>se reconnecter ssh</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh root@37.60.230.30
</code></pre></div></div>
<p>Vérifier ladressage : <code class="language-plaintext highlighter-rouge">ip a</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1: lo: &lt;LOOPBACK,UP,LOWER_UP&gt; mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host noprefixroute
valid_lft forever preferred_lft forever
2: eth0: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc fq_codel state UP group default qlen 1000
link/ether 00:50:56:4e:73:83 brd ff:ff:ff:ff:ff:ff
altname enp0s18
altname ens18
inet 37.60.230.30/20 brd 37.60.239.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 2a02:c206:2162:8819::1/64 scope global
valid_lft forever preferred_lft forever
inet6 fe80::250:56ff:fe4e:7383/64 scope link
valid_lft forever preferred_lft forever
</code></pre></div></div>
<h3 id="date-et-heure--synchro">Date et heure + Synchro</h3>
<p>Activer le fuseau Europe/Paris</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>timedatectl set-timezone Europe/Paris
</code></pre></div></div>
<p>Horloge système synchronisée : <code class="language-plaintext highlighter-rouge">timedatectl</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Local time: Mon 2024-11-04 14:21:24 CET
Universal time: Mon 2024-11-04 13:21:24 UTC
RTC time: Mon 2024-11-04 13:21:24
Time zone: Europe/Paris (CET, +0100)
System clock synchronized: yes
NTP service: active
RTC in local TZ: no
</code></pre></div></div>
<h3 id="reconfigurer-locales">Reconfigurer locales</h3>
<p>Activer uniquement <strong>en_GB.UTF-8</strong>, <strong>en_US.UTF-8</strong> et <strong>fr_FR.UTF-8</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dpkg-reconfigure locales
</code></pre></div></div>
<p>Default en_US.UTF-8</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Generating locales (this might take a while)...
en_GB.UTF-8... done
en_US.UTF-8... done
fr_FR.UTF-8... done
Generation complete.
</code></pre></div></div>
<h3 id="reverse-dns">Reverse DNS</h3>
<p>Activer le reverse DNS IPV4 et IPV6 pour le domaine xoyaz.xyz : Control panel → Reverse DNS management<br />
<img src="/images/contabo-reverse-dns.png" alt="" width="400" /><br />
Désactiver VNC: Your services → Manage → VPS Control → Manage → Disable VNC et valider par un clic sur Disable</p>
<h2 id="yunohost-xoyazxyz">Yunohost xoyaz.xyz</h2>
<p><img src="/images/yunohost.png" alt=" " /></p>
<p><strong>Pré-requis</strong><br />
Un serveur dédié ou virtuel avec Debian 12 (Bookworm) pré-installé (avec un kernel &gt;= 6.1), avec au moins 512Mo de RAM et 16Go de capacité de stockage</p>
<h3 id="script-installation">Script installation</h3>
<p><strong>Lancer le script dinstallation</strong><br />
Ouvrez la ligne de commande sur votre serveur (soit directement, soit avec SSH)<br />
Vous devez être connecté en tant que root, curl installé (which curl)<br />
Lancez la commande suivante :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl https://install.yunohost.org | bash
</code></pre></div></div>
<p>Détails de linstallation dans <code class="language-plaintext highlighter-rouge">/var/log/yunohost-installation_20241104_150816.log</code></p>
<h3 id="post-installation">Post-installation</h3>
<p>post-installation de Yunohost.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yunohost tools postinstall
</code></pre></div></div>
<p>Suivre la procédure</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Main domain: xoyaz.xyz
Admin username: yani
Admin full name: yani bopro
New administration password: ********************
Confirm new administration password: ********************
Warning: The YunoHost project is a team of volunteers who have made common cause to create a free operating system for servers, called YunoHost. The YunoHost software is published under the AGPLv3 license (&lt;https://www.gnu.org/licenses/agpl-3.0.txt&gt;). In connection with this software, the project administers and makes available several technical and community services for various purposes. By using these services, you agree to be bound by the following Terms of Services: &lt;https://yunohost.org/terms_of_services&gt;.
I have read and understand the Terms of Services [Y/N]: Y
Info: Installing YunoHost…
Success! Self-signed certificate now installed for the domain 'xoyaz.xyz'
Success! Domain created
Info: The email alias 'root@xoyaz.xyz' will be added to the group 'admins'
Info: The email alias 'admin@xoyaz.xyz' will be added to the group 'admins'
Info: The email alias 'admins@xoyaz.xyz' will be added to the group 'admins'
Info: The email alias 'webmaster@xoyaz.xyz' will be added to the group 'admins'
Info: The email alias 'postmaster@xoyaz.xyz' will be added to the group 'admins'
Info: The email alias 'abuse@xoyaz.xyz' will be added to the group 'admins'
Info: Updating aliases for group 'admins'
Info: The app catalog cache is empty or obsolete.
Info: Updating application catalog…
Info: (Will fetch 537 logos, this may take a couple minutes)
</code></pre></div></div>
<h3 id="dns-ovh-xoyazxyz">DNS OVH xoyaz.xyz</h3>
<p><img src="/images/dns-logo.png" alt="" width="50" /></p>
<p>Configuration DNS domaine par défaut <strong>xoyaz.xyz</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yunohost domain dns suggest xoyaz.xyz
</code></pre></div></div>
<p>La configuration DNS du domaine</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Warning: No diagnosis cache yet for category 'dnsrecords'
Info: This command shows you the *recommended* configuration. It does not actually set up the DNS configuration for you. It is your responsability to configure your DNS zone in your registrar according to this recommendation.
; Basic ipv4/ipv6 records
@ 3600 IN A 37.60.230.30
@ 3600 IN AAAA 2a02:c206:2162:8819::1
; Mail
@ 3600 IN MX 10 xoyaz.xyz.
@ 3600 IN TXT "v=spf1 a mx -all"
mail._domainkey 3600 IN TXT "v=DKIM1; h=sha256; k=rsa; p=MIGfMA0GCSqGSIb....EQIDAQAB"
_dmarc 3600 IN TXT "v=DMARC1; p=none"
; Extra
* 3600 IN A 37.60.230.30
* 3600 IN AAAA 2a02:c206:2162:8819::1
@ 3600 IN CAA 0 issue "letsencrypt.org"
</code></pre></div></div>
<p><img src="/images/ovh-logo-a.png" alt="" height="20" /><br />
Se connecter à lespace client du site OVH : <strong>Web cloud → Domaines → xoyaz.xyz → Zone DNS</strong><br />
Cliquer sur <strong>“Modifier en mode textuel”</strong>, garder les 4 premières lignes :<br />
<img src="/images/dns-ovh-zone.png" alt="" width="600" /><br />
puis effacer tout ce quil y a en-dessous, et le remplacer par la configuration donnée par votre serveur avec la commande <code class="language-plaintext highlighter-rouge">yunohost domain dns suggest xoyaz.xyz</code></p>
<h3 id="certificats-ssl-lets-encrypt-xoyazxyz">Certificats SSL Lets Encrypt xoyaz.xyz</h3>
<p><img src="/images/LetsEncrypt-a.png" alt="" height="30" /><br />
On active les certificats SSL pour le domaine xoyaz.xyz</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yunohost domain cert install xoyaz.xyz --no-checks
</code></pre></div></div>
<p>Résultat</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[...]
Success! Configuration updated for 'nginx'
Success! Let's Encrypt certificate now installed for the domain 'xoyize.xyz'
</code></pre></div></div>
<h3 id="openssh-clé-et-motd">OpenSSH, clé et motd</h3>
<p><img src="/images/openssh-logo.png" alt="OpenSSH" height="40" /></p>
<p><u>Sur l'ordinateur de bureau</u><br />
Générer une paire de clé curve25519-sha256 (ECDH avec Curve25519 et SHA2) nommé <strong>xoyaz-ed25519</strong> pour une liaison SSH avec le serveur yunohost</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh-keygen -t ed25519 -o -a 100 -f ~/.ssh/xoyaz-ed25519
</code></pre></div></div>
<p>Envoyer les clés publiques sur le serveur yunohost en se se connectant avec lutilisateur administrateur</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh-copy-id -i ~/.ssh/xoyaz-ed25519.pub yani@37.60.230.30
</code></pre></div></div>
<p>On se connecte avec lutilisateur administrateur</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh yani@37.60.230.30
</code></pre></div></div>
<p><u>Sur le serveur Yunohost</u><br />
La liste des paramètres modifiables : <code class="language-plaintext highlighter-rouge">sudo yunohost settings list</code></p>
<p>Sur votre serveur, la modification du fichier de configuration SSH pour désactiver lauthentification par mot de passe est gérée par un paramètre système</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo yunohost settings set security.ssh.ssh_password_authentication -v no
</code></pre></div></div>
<p>Modifier le port SSH</p>
<p><em>Pour empêcher les tentatives de connexion SSH par des robots qui analysent Internet à la recherche de tout serveur sur lequel SSH est activé, vous pouvez modifier le port SSH. Ceci est géré par un paramètre système, qui prend en charge la mise à jour de la configuration SSH et Fail2Ban.</em></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo yunohost settings set security.ssh.ssh_port -v 55030
</code></pre></div></div>
<p>Accès depuis <u>l'ordinateur de bureau</u> avec la clé privée</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh <span class="nt">-p</span> 55030 <span class="nt">-i</span> ~/.ssh/xoyaz-ed25519 yani@37.60.230.30 <span class="c"># avec ip serveur</span>
ssh <span class="nt">-p</span> 55030 <span class="nt">-i</span> ~/.ssh/xoyaz-ed25519 yani@xoyaz.xyz <span class="c"># avec domaine serveur</span>
</code></pre></div></div>
<p><strong>Motd:</strong> <code class="language-plaintext highlighter-rouge">/etc/motd</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> __ __ _ _
\ \ / /_ _ _ _ ___ | |_ ___ ___| |_
\ V /| || || ' \ / _ \| ' \ / _ \(_-&lt;| _|
|_| \_,_||_||_|\___/|_||_|\___//__/ \__|
__ __ ___ _ _ __ _ ___ __ __ _ _ ___
\ \ // _ \| || |/ _` ||_ / _ \ \ /| || ||_ /
/_\_\\___/ \_, |\__,_|/__|(_)/_\_\ \_, |/__|
|__/ |__/
____ ____ __ __ ___ ____ __ ____ __
|__ /|__ | / / / \ |_ )|__ / / \ |__ / / \
|_ \ / /_ / _ \| () |_ / / |_ \| () |_ |_ \| () |
|___/ /_/(_)\___/ \__/(_)/___||___/ \__/(_)|___/ \__/
</code></pre></div></div>
<h3 id="historique-de-la-ligne-de-commande">Historique de la ligne de commande</h3>
<p>Ajoutez la recherche dhistorique de la ligne de commande au terminal
Se connecter en utilisateur debian
Tapez un début de commande précédent, puis utilisez shift + up (flèche haut) pour rechercher lhistorique filtré avec le début de la commande.</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Global, tout utilisateur</span>
<span class="nb">echo</span> <span class="s1">'"\e[1;2A": history-search-backward'</span> | <span class="nb">sudo tee</span> <span class="nt">-a</span> /etc/inputrc
<span class="nb">echo</span> <span class="s1">'"\e[1;2B": history-search-forward'</span> | <span class="nb">sudo tee</span> <span class="nt">-a</span> /etc/inputrc
</code></pre></div></div>
<h3 id="diagnostique">Diagnostique</h3>
<p>La machine ayant été redémarré après la configuration dns , on peut lancer une vérification</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo yunohost diagnosis run
</code></pre></div></div>
<p>Lecture après exécution</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo yunohost diagnosis show --human-readable
</code></pre></div></div>
<p>Résultat</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>=================================
Base system (basesystem)
=================================
[INFO] Server hardware architecture is kvm amd64
- Server model is QEMU Standard PC (i440FX + PIIX, 1996)
[INFO] Server is running Linux kernel 6.1.0-26-amd64
[INFO] Server is running Debian 12.7
[INFO] Server is running YunoHost 12.0.6 (stable)
- yunohost version: 12.0.6 (stable)
- yunohost-admin version: 12.0.3.5 (stable)
- yunohost-portal version: 12.0.6 (stable)
- moulinette version: 12.0.3 (stable)
- ssowat version: 12.0.3 (stable)
=================================
Internet connectivity (ip)
=================================
[SUCCESS] Domain name resolution is working!
[SUCCESS] The server is connected to the Internet through IPv4!
- Global IP: 37.60.230.30
- Local IP: 37.60.230.30
[SUCCESS] The server is connected to the Internet through IPv6!
- Global IP: 2a02:c206:2162:8819::1
- Local IP: 2a02:c206:2162:8819::1
=================================
DNS records (dnsrecords)
=================================
[SUCCESS] DNS records are correctly configured for domain xoyaz.xyz (category basic)
[SUCCESS] DNS records are correctly configured for domain xoyaz.xyz (category mail)
[SUCCESS] DNS records are correctly configured for domain xoyaz.xyz (category extra)
[SUCCESS] Your domains are registered and not going to expire anytime soon.
- xoyaz.xyz expires in 119 days.
=================================
Ports exposure (ports)
=================================
[SUCCESS] Port 25 is reachable from the outside.
- Exposing this port is needed for email features (service postfix)
[SUCCESS] Port 80 is reachable from the outside.
- Exposing this port is needed for web features (service nginx)
[SUCCESS] Port 443 is reachable from the outside.
- Exposing this port is needed for web features (service nginx)
[SUCCESS] Port 587 is reachable from the outside.
- Exposing this port is needed for email features (service postfix)
[SUCCESS] Port 993 is reachable from the outside.
- Exposing this port is needed for email features (service dovecot)
[SUCCESS] Port 55030 is reachable from the outside.
- Exposing this port is needed for admin features (service ssh)
=================================
Web (web)
=================================
[SUCCESS] Domain xoyaz.xyz is reachable through HTTP from outside the local network.
=================================
Email (mail)
=================================
[SUCCESS] The SMTP mail server is able to send emails (outgoing port 25 is not blocked).
[SUCCESS] The SMTP mail server is reachable from the outside and therefore is able to receive emails!
[SUCCESS] Your reverse DNS is correctly configured!
[SUCCESS] The IPs and domains used by this server do not appear to be blacklisted
[SUCCESS] 0 pending emails in the mail queues
=================================
Services status check (services)
=================================
[SUCCESS] Service dnsmasq is running!
[SUCCESS] Service dovecot is running!
[SUCCESS] Service fail2ban is running!
[SUCCESS] Service nginx is running!
[SUCCESS] Service opendkim is running!
[SUCCESS] Service postfix is running!
[SUCCESS] Service slapd is running!
[SUCCESS] Service ssh is running!
[SUCCESS] Service yunohost-api is running!
[SUCCESS] Service yunohost-firewall is running!
[SUCCESS] Service yunohost-portal-api is running!
[SUCCESS] Service yunomdns is running!
=================================
System resources (systemresources)
=================================
[SUCCESS] The system still has 5.2 GiB (90%) RAM available out of 5.8 GiB.
[INFO] The system has no swap at all. You should consider adding at least 512 MiB of swap to avoid situations where the system runs out of memory.
- Please be careful and aware that if the server is hosting swap on an SD card or SSD storage, it may drastically reduce the life expectancy of the device.
[SUCCESS] Storage / (on device /dev/sda3) still has 368 GiB (99.3%) space left (out of 371 GiB)!
[SUCCESS] Storage /boot (on device /dev/sda2) still has 1.7 GiB (94.5%) space left (out of 1.8 GiB)!
=================================
System configurations (regenconf)
=================================
[SUCCESS] All configuration files are in line with the recommended configuration!
=================================
Applications (apps)
=================================
[SUCCESS] All installed apps respect basic packaging practices
</code></pre></div></div>
<h3 id="ldap-admin-sudo">Ldap admin sudo</h3>
<p><em>On veut autoriser lutilisateur administrateur de Yunohost à exécuter des commandes root sans avoir à saisir le mot de passe</em></p>
<p>Ouvrir un terminal en mode administrateur</p>
<p>Lecture du paramétrage ldap admins sudo</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapsearch -H ldap://127.0.0.1:389 -x -LLL -b "cn=admins,ou=sudo,dc=yunohost,dc=org" -s sub -x "(objectclass=*)"
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dn: cn=admins,ou=sudo,dc=yunohost,dc=org
cn: admins
objectClass: sudoRole
objectClass: top
sudoCommand: ALL
sudoUser: %admins
sudoHost: ALL
</code></pre></div></div>
<p>Il faut ajouter loption <code class="language-plaintext highlighter-rouge">sudoOption</code> avec pour valeur <code class="language-plaintext highlighter-rouge">!authenticate</code> à la branche <code class="language-plaintext highlighter-rouge">cn=admins</code><br />
Créer un fichier <code class="language-plaintext highlighter-rouge">add-to-sudo-role.ldif</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat &gt; add-to-sudo-role.ldif &lt;&lt; 'EOL'
dn: cn=admins,ou=sudo,dc=yunohost,dc=org
changetype: modify
add: sudoOption
sudoOption: !authenticate
EOL
</code></pre></div></div>
<p>Exécuter la commande suivante</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapmodify -Y EXTERNAL -H ldapi:/// -f add-to-sudo-role.ldif
</code></pre></div></div>
<p>Résultat de la commande</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SASL/EXTERNAL authentication started
SASL username: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
SASL SSF: 0
modifying entry "cn=admins,ou=sudo,dc=yunohost,dc=org"
</code></pre></div></div>
<p>Lecture du paramétrage ldap admins sudo après modification</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ldapsearch -H ldap://127.0.0.1:389 -x -LLL -b "cn=admins,ou=sudo,dc=yunohost,dc=org" -s sub -x "(objectclass=*)"
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dn: cn=admins,ou=sudo,dc=yunohost,dc=org
cn: admins
objectClass: sudoRole
objectClass: top
sudoCommand: ALL
sudoUser: %admins
sudoHost: ALL
sudoOption: !authenticate
</code></pre></div></div>
<h2 id="domaine-cinayeu">Domaine cinay.eu</h2>
<p>Création domaine</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yunohost domain add cinay.eu
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Success! Self-signed certificate now installed for the domain 'cinay.eu'
Success! Configuration updated for 'nginx'
Success! Configuration updated for 'postfix'
Success! Configuration updated for 'dovecot'
Success! Configuration updated for 'dnsmasq'
Success! Domain created
</code></pre></div></div>
<h3 id="paramètres-dns-cinayeu">Paramètres DNS cinay.eu</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yunohost domain dns suggest cinay.eu
</code></pre></div></div>
<p>La configuration DNS du domaine</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Info: This command shows you the *recommended* configuration. It does not actually set up the DNS configuration for you. It is your responsability to configure your DNS zone in your registrar according to this recommendation.
; Basic ipv4/ipv6 records
@ 3600 IN A 37.60.230.30
@ 3600 IN AAAA 2a02:c206:2162:8819::1
; Mail
@ 3600 IN MX 10 cinay.eu.
@ 3600 IN TXT "v=spf1 a mx -all"
; Extra
* 3600 IN A 37.60.230.30
* 3600 IN AAAA 2a02:c206:2162:8819::1
@ 3600 IN CAA 0 issue "letsencrypt.org"
</code></pre></div></div>
<p><img src="/images/ovh-logo-a.png" alt="" height="20" /><br />
Se connecter à lespace client du site OVH : <strong>Web cloud → Domaines → cinay.eu → Zone DNS</strong><br />
Cliquer sur <strong>“Modifier en mode textuel”</strong>, garder les 4 premières lignes :<br />
<img src="/images/dns-ovh-zone.png" alt="" width="600" /><br />
puis effacer tout ce quil y a en-dessous, et le remplacer par la configuration donnée par votre serveur avec la commande <code class="language-plaintext highlighter-rouge">yunohost domain dns suggest cinay.eu</code></p>
<h3 id="certificats-ssl-lets-encrypt-cinayeu">Certificats SSL Lets Encrypt cinay.eu</h3>
<p><img src="/images/LetsEncrypt-a.png" alt="" height="30" /><br />
On active les certificats SSL pour le domaine cinay.eu</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yunohost domain cert install cinay.eu --no-checks
</code></pre></div></div>
<p>Résultat</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[...]
Success! Configuration updated for 'nginx'
Success! Let's Encrypt certificate now installed for the domain 'cinay.eu'
</code></pre></div></div>
<h3 id="utilisateur-domaine-cinayeu">Utilisateur domaine cinay.eu</h3>
<p>Créer un utilisateur dans le domaine cinay.eu</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yunohost user create yack -F 'yack cinay' -p Mot_de_passe_ou_passphrase -d cinay.eu
</code></pre></div></div>
<p>Ajout des alias</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yunohost user update yack <span class="nt">--add-mailalias</span> yann@cinay.eu
yunohost user update yack <span class="nt">--add-mailalias</span> boproyan@cinay.eu
yunohost user update yack <span class="nt">--add-mailalias</span> discord@cinay.eu
yunohost user update yack <span class="nt">--add-mailalias</span> wg@cinay.eu
yunohost user update yack <span class="nt">--add-mailalias</span> vps@cinay.eu
yunohost user update yack <span class="nt">--add-mailalias</span> vpn@cinay.eu
yunohost user update yack <span class="nt">--add-mailalias</span> yannick@cinay.eu
yunohost user update yack <span class="nt">--add-mailalias</span> cnx@cinay.eu
yunohost user update yack <span class="nt">--add-mailalias</span> yannick.meunier@cinay.eu
yunohost user update yack <span class="nt">--add-mailalias</span> domo@cinay.eu
yunohost user update yack <span class="nt">--add-mailalias</span> fubo@cinay.eu
yunohost user update yack <span class="nt">--add-mailalias</span> iptv@cinay.eu
yunohost user update yack <span class="nt">--add-mailalias</span> ign@cinay.eu
yunohost user update yack <span class="nt">--add-mailalias</span> map@cinay.eu
yunohost user update yack <span class="nt">--add-mailalias</span> debyan@cinay.eu
yunohost user update yack <span class="nt">--add-mailalias</span> rnmkcy@cinay.eu
yunohost user update yack <span class="nt">--add-mailalias</span> leno@cinay.eu
yunohost user update yack <span class="nt">--add-mailalias</span> buyonrakuten@cinay.eu
</code></pre></div></div>
<h2 id="domaine-yanfinet">Domaine yanfi.net</h2>
<p>Création domaine</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yunohost domain add yanfi.net
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Success! Self-signed certificate now installed for the domain 'yanfi.net'
Success! Configuration updated for 'nginx'
Success! Configuration updated for 'postfix'
Success! Configuration updated for 'dovecot'
Success! Configuration updated for 'dnsmasq'
Success! Domain created
</code></pre></div></div>
<h3 id="paramètres-dns-yanfinet">Paramètres DNS yanfi.net</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yunohost domain dns suggest yanfi.net
</code></pre></div></div>
<p>La configuration DNS du domaine</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Info: This command shows you the *recommended* configuration. It does not actually set up the DNS configuration for you. It is your responsability to configure your DNS zone in your registrar according to this recommendation.
; Basic ipv4/ipv6 records
@ 3600 IN A 37.60.230.30
@ 3600 IN AAAA 2a02:c206:2162:8819::1
; Mail
@ 3600 IN MX 10 yanfi.net.
@ 3600 IN TXT "v=spf1 a mx -all"
; Extra
* 3600 IN A 37.60.230.30
* 3600 IN AAAA 2a02:c206:2162:8819::1
@ 3600 IN CAA 0 issue "letsencrypt.org"
</code></pre></div></div>
<p><img src="/images/ovh-logo-a.png" alt="" height="20" /><br />
Se connecter à lespace client du site OVH : <strong>Web cloud → Domaines → yanfi.net → Zone DNS</strong><br />
Cliquer sur <strong>“Modifier en mode textuel”</strong>, garder les 4 premières lignes :<br />
<img src="/images/dns-ovh-zone.png" alt="" width="600" /><br />
puis effacer tout ce quil y a en-dessous, et le remplacer par la configuration donnée par votre serveur avec la commande <code class="language-plaintext highlighter-rouge">yunohost domain dns suggest yanfi.net</code></p>
<h3 id="certificats-ssl-lets-encrypt-yanfinet">Certificats SSL Lets Encrypt yanfi.net</h3>
<p><img src="/images/LetsEncrypt-a.png" alt="" height="30" /><br />
On active les certificats SSL pour le domaine yanfi.net</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yunohost domain cert install yanfi.net --no-checks
</code></pre></div></div>
<p>Résultat</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[...]
Success! Configuration updated for 'nginx'
Success! Let's Encrypt certificate now installed for the domain 'yanfi.net'
</code></pre></div></div>
<h3 id="utilisateurs-domaine-yanfinet">Utilisateurs domaine yanfi.net</h3>
<p>Créer utilisateur yanfi dans le domaine yanfi.net</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yunohost user create yanfi -F 'yanfi net' -p Mot_de_passe_ou_passphrase -d yanfi.net
</code></pre></div></div>
<p>Ajout des alias</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yunohost user update yanfi --add-mailalias foryuno@yanfi.net
yunohost user update yanfi --add-mailalias yannick@yanfi.net
yunohost user update yanfi --add-mailalias ffsync@yanfi.net
yunohost user update yanfi --add-mailalias yannick.meunier@yanfi.net
yunohost user update yanfi --add-mailalias foroli@yanfi.net
yunohost user update yanfi --add-mailalias windy@yanfi.net
yunohost user update yanfi --add-mailalias div@yanfi.net
yunohost user update yanfi --add-mailalias api@yanfi.net
yunohost user update yanfi --add-mailalias assur@yanfi.net
yunohost user update yanfi --add-mailalias randos@yanfi.net
</code></pre></div></div>
<p>Créer utilisateur claudine dans le domaine yanfi.net</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yunohost user create claudine -F 'Claudine Meunier' -p Mot_de_passe_ou_passphrase -d yanfi.net
</code></pre></div></div>
<h2 id="migration-imap">Migration imap</h2>
<p><a href="https://jesuisadmin.fr/migrer-emails-facilement-imapsync/">Migrer ses emails facilement avec imapsync</a></p>
<p><em>imapsync est comme son nom lindique un outil (développé en perl) qui permet de synchroniser des boîtes mail en utilisant le protocol imap</em></p>
<p>On se connecte sur le serveur xoyize.xyz (109.123.254.249)<br />
En mode sudo, on installe impasync</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>apt-get <span class="nb">install </span>libauthen-ntlm-perl libclass-load-perl libcrypt-ssleay-perl <span class="se">\</span>
libdata-uniqid-perl libdigest-hmac-perl libdist-checkconflicts-perl <span class="se">\</span>
libfile-copy-recursive-perl <span class="se">\</span>
libio-compress-perl libio-socket-inet6-perl <span class="se">\</span>
libio-socket-ssl-perl libio-tee-perl libmail-imapclient-perl <span class="se">\</span>
libmodule-scandeps-perl <span class="se">\</span>
libnet-ssleay-perl libpar-packer-perl libreadonly-perl libregexp-common-perl <span class="se">\</span>
libsys-meminfo-perl libterm-readkey-perl libtest-fatal-perl libtest-mock-guard-perl <span class="se">\</span>
libtest-pod-perl libtest-requires-perl libtest-simple-perl libunicode-string-perl <span class="se">\</span>
liburi-perl libtest-nowarnings-perl <span class="se">\</span>
libtest-deep-perl libtest-warn-perl <span class="se">\</span>
make <span class="se">\</span>
cpanminus
<span class="nb">sudo </span>cpanm Mail::IMAPClient
<span class="nb">sudo </span>cpanm JSON::WebToken
<span class="nb">sudo </span>cpan Encode::IMAPUTF7
<span class="nb">sudo </span>cpan File::Tail
</code></pre></div></div>
<p>Transfert imap cluadine xoyize.xyz (109.123.254.249) &gt; imap claudine xoyaz.yz</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>imapsync --host1 109.123.254.249 --port1 993 --ssl1 --user1 claudine --password1 Mot_de_passe --host2 37.60.230.30 --port2 993 --ssl2 --user2 claudine --password2 Mot_de_passe
</code></pre></div></div>
<h2 id="borgbackup">BorgBackup</h2>
<p>Sauvegardes borgbackup seront effectuées sur un “Storage Box” Hetzner (u326239.your-storagebox.de), dans le dépôt <code class="language-plaintext highlighter-rouge">ssh://u326239@u326239.your-storagebox.de:23/./backup/borg/xoyaz.xyz</code></p>
<h3 id="installation">Installation</h3>
<p>Se connecter sur la page web administration yunohost xoyaz.xyz et installer <strong>Borg Backup</strong> <br />
<img src="/images/yuno-borg01.png" alt="" /></p>
<p>Renseigner et installer lapplication<br />
<img src="/images/yuno-borg02.png" alt="" />
Les fichiers des clés <code class="language-plaintext highlighter-rouge">id_borg_ed25519.pub</code> et <code class="language-plaintext highlighter-rouge">id_borg_ed25519</code> dans le dossier `/root/.ssh/</p>
<h3 id="boite-de-stockage">Boite de stockage</h3>
<p>Ajouter la clé Publique <code class="language-plaintext highlighter-rouge">ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAGSDBEyWPgDHgNuWNtaygp6jmIlpWCs6VKnC0oGLTXO root@xoyaz.xyz</code> au fichier authorized_keys de la boîte de stockage</p>
<p><em>Cette opération va se dérouler sur poste ayant accès à la boîte de stockage avec clés SSH</em></p>
<p>On télécharge le fichier existant <code class="language-plaintext highlighter-rouge">authorized_keys</code> de la boîte de stockage <strong>bx11-yann</strong> dans un fichier local nommé <code class="language-plaintext highlighter-rouge">storagebox_authorized_keys</code></p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">echo</span> <span class="nt">-e</span> <span class="s2">"get .ssh/authorized_keys storagebox_authorized_keys"</span> | sftp <span class="nt">-P</span> 23 <span class="nt">-i</span> ~/.ssh/bx11-yann-ed25519 u326239@u326239.your-storagebox.de
</code></pre></div></div>
<p>Ajout clé publique</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">echo</span> <span class="s2">"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAGSDBEyWPgDHgNuWNtaygp6jmIlpWCs6VKnC0oGLTXO root@xoyaz.xyz"</span> <span class="o">&gt;&gt;</span> storagebox_authorized_keys
</code></pre></div></div>
<p>On renvoie le fichier modifié <code class="language-plaintext highlighter-rouge">storagebox_authorized_keys</code> dans le fichier <code class="language-plaintext highlighter-rouge">authorized_keys</code> de la boîte de stockage <strong>bx11-yann</strong></p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">echo</span> <span class="nt">-e</span> <span class="s2">"put storagebox_authorized_keys .ssh/authorized_keys"</span> | sftp <span class="nt">-P</span> 23 <span class="nt">-i</span> ~/.ssh/bx11-yann-ed25519 u326239@u326239.your-storagebox.de
</code></pre></div></div>
<p>Valider la connexion SSH avec la clé privée borg située sous /root/.ssh (en mode su)</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sftp -P 23 -i /root/.ssh/id_borg_ed25519 u326239@u326239.your-storagebox.de
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>The authenticity of host '[u326239.your-storagebox.de]:23 ([2a01:4f8:2b01:d53::2]:23)' can't be established.
ED25519 key fingerprint is SHA256:XqONwb1S0zuj5A1CDxpOSuD2hnAArV1A3wKY7Z3sdgM.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '[u326239.your-storagebox.de]:23' (ED25519) to the list of known hosts.
Connected to u326239.your-storagebox.de.
sftp&gt; quit
</code></pre></div></div>
<h3 id="rappel-concernant-la-phrase-de-passe">Rappel concernant la phrase de passe</h3>
<p class="info">La phrase de passe est le seul moyen de décrypter vos sauvegardes. Vous devez veiller à la conserver en sécurité dans un endroit « extérieur » à votre serveur afin de couvrir le scénario dans lequel votre serveur serait détruit pour une raison ou une autre.</p>
<h3 id="tester-la-sauvegarde">Tester la sauvegarde</h3>
<p class="info">À cette étape, votre sauvegarde doit sexécuter à lheure prévue. Notez que la première sauvegarde peut prendre beaucoup de temps, car de nombreuses données doivent être copiées via ssh. Les sauvegardes suivantes sont incrémentales : seules les données nouvellement générées depuis la dernière sauvegarde seront copiées.</p>
<p>Si vous souhaitez tester la configuration correcte de Borg Apps avant lheure prévue, vous pouvez lancer une sauvegarde manuellement à partir de la ligne de commande :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo systemctl start borg
</code></pre></div></div>
<p>Si tout démarre correctement, un message est envoyé:<br />
<img src="/images/yuno-borg03.png" alt="" width="300" /></p>
<p>Une fois la sauvegarde terminée, vous pouvez vérifier quune sauvegarde est listée dans le <strong>webadmin &gt; Applications &gt; Borg &gt; Last backups list</strong>.<br />
<img src="/images/yuno-borg04.png" alt="" /></p>
<h3 id="exécution-manuelle-des-commandes-borg">Exécution manuelle des commandes borg</h3>
<p>Le panneau de configuration dispose dune « Liste des dernières sauvegardes » qui permet de consulter rapidement les archives de sauvegarde récemment créées.<br />
Cependant, vous pouvez vouloir vérifier manuellement que les sauvegardes sont effectivement effectuées régulièrement et quelles contiennent le contenu attendu.<br />
Tout dabord, préparez lenvironnement avec les variables borg appropriées :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app=borg
PATH="/var/www/$app/venv/bin/:$PATH"
export BORG_PASSPHRASE="$(sudo yunohost app setting $app passphrase)"
export BORG_RSH="ssh -i /root/.ssh/id_${app}_ed25519 -oStrictHostKeyChecking=yes"
repository="$(sudo yunohost app setting $app repository)"
</code></pre></div></div>
<p>Les commandes exécutables</p>
<ul>
<li>Lister les archives:
* borg list “$repository” | less</li>
<li>Lister les fichiers dune archive spécifique:
* borg list “$repository::ARCHIVE_NAME” | less</li>
<li>Voir les informations sur larchive:
* borg info “$repository::ARCHIVE_NAME”</li>
<li>Vérifier lintégrité des données:
<ul>
<li>borg info “$repository::ARCHIVE_NAME” verify-data</li>
</ul>
</li>
</ul>
<h3 id="restauration-darchives-à-partir-de-borg">Restauration darchives à partir de Borg</h3>
<p>Une « archive » borg peut être exportée vers un fichier <code class="language-plaintext highlighter-rouge">.tar</code> qui peut ensuite être restauré en utilisant la procédure classique de sauvegarde et de restauration de Yunohost :</p>
<p><strong>NB : cette commande suppose que vous avez préparé lenvironnement comme dans la section précédente</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>borg export-tar « $repository::ARCHIVE_NAME » /home/yunohost/archives/ARCHIVE_NAME.tar
</code></pre></div></div>
<h4 id="restaurez-ensuite-en-utilisant-le-flux-de-travail-classique-">Restaurez ensuite en utilisant le flux de travail classique :</h4>
<ul>
<li>à partir de la ligne de commande :
<ul>
<li>yunohost backup restore ARCHIVE_NAME</li>
</ul>
</li>
<li>ou dans le webadmin &gt; Sauvegardes</li>
</ul>
<h4 id="restaurer-la--sourceconfig--de-lapplication-et-ses-données-séparément">Restaurer la « source+config » de lapplication, et ses données séparément</h4>
<p>Pour les applications contenant une grande quantité de données, il nest pas pratique de tout restaurer en une seule fois en raison de lespace et du temps que cela prendrait. <br />
Au lieu de cela, vous voudrez peut-être restaurer dabord le « noyau » (la source, la configuration, etc.) de lapplication, - et ensuite les données.<br />
Tout dabord, borg peut exporter une archive <code class="language-plaintext highlighter-rouge">.tar</code> mais ignorer le chemin correspondant aux données de lapplication.</p>
<p>Par exemple, pour exporter une archive tar pour Nextcloud, mais sans ses données :</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>borg export-tar <span class="nt">--exclude</span> apps/nextcloud/backup/home/yunohost.app <span class="s2">"</span><span class="nv">$repository</span><span class="s2">::ARCHIVE_NAME"</span> /home/yunohost.backup/archives/ARCHIVE_NAME.tar
yunohost backup restore ARCHIVE_NAME
</code></pre></div></div>
<p>Ensuite, extrayez les données de Nextcloud directement au bon endroit, <strong>sans passer</strong> par le processus classique de restauration de sauvegarde de YunoHost :</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd</span> /home/yunohost.app/
borg extract <span class="s2">"</span><span class="nv">$repository</span><span class="s2">::ARCHIVE_NAME"</span> apps/nextcloud/backup/home/yunohost.app/
<span class="nb">mv </span>apps/nextcloud/backup/home/yunohost.app/nextcloud ./
<span class="nb">rm</span> <span class="nt">-r</span> apps
</code></pre></div></div>
2024-11-21 17:20:33 +01:00
<h2 id="outils">Outils</h2>
<h3 id="cockpit">Cockpit</h3>
<p>Installer cockpit en tant que “client”</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt install cockpit
</code></pre></div></div>
2024-11-07 22:37:17 +01:00
</div>
<div class="d-print-none"><footer class="article__footer"><meta itemprop="dateModified" content="2024-11-06T00: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>
&emsp;</div>
-->
</footer>
2024-11-08 14:10:33 +01:00
<div class="article__section-navigator clearfix"><div class="previous"><span>PRÉCÉDENT</span><a href="/2024/10/31/NetworkManager-nmcli.html">Réseau NetworkManager - nmcli</a></div><div class="next"><span>SUIVANT</span><a href="/2024/11/08/PostgreSQL.html">PostgreSQL</a></div></div></div>
2024-11-07 22:37:17 +01:00
</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}&nbsp;{title}</a>&nbsp;(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>