yannstatic/static/2018/11/23/shuttle.html

3356 lines
242 KiB
HTML
Raw Permalink Normal View History

2024-10-31 20:18:37 +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>Yunohost Shuttle - YannStatic</title>
<meta name="description" content="Shuttle">
<link rel="canonical" href="https://static.rnmkcy.eu/2018/11/23/shuttle.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;">Yunohost Shuttle</h1></header></div><meta itemprop="headline" content="Yunohost Shuttle"><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=yunohost">yunohost</a>
2024-10-31 20:18:37 +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=git">git</a>
2024-10-31 20:18:37 +01:00
</li></ul><ul class="right-col menu"><li>
<i class="far fa-calendar-alt"></i>&nbsp;<span title="Création" style="color:#FF00FF">23&nbsp;nov.&nbsp;&nbsp;2018</span>
<span title="Modification" style="color:#00FF7F">22&nbsp;sept.&nbsp;2017</span></li></ul></div><meta itemprop="datePublished" content="2017-09-22T00:00:00+02:00">
<meta itemprop="keywords" content="yunohost,git"><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><h1 id="shuttle">Shuttle</h1>
<p><a href="https://yanspm.com">https://yanspm.com</a></p>
<h2 id="installation-debian-8">Installation Debian 8</h2>
<h3 id="boot-sur-parted-magic">Boot sur “Parted Magic”</h3>
<ul>
<li>Relever adresse ip : 192.168.0.45</li>
<li>Changer mot de passe root</li>
<li>se connecter ssh : <code class="language-plaintext highlighter-rouge">ssh root@192.168.0.45</code></li>
</ul>
<h3 id="partitionnement">Partitionnement</h3>
<p>Partionnement dun disque SSD de 120G</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>root@partedmagic:~# gdisk /dev/sda
GPT fdisk (gdisk) version 0.8.6
Partition table scan:
MBR: not present
BSD: not present
APM: not present
GPT: not present
Creating new GPT entries.
Command (? for help): o
This option deletes all partitions and creates a new protective MBR.
Proceed? (Y/N): y
Command (? for help): n
Partition number (1-128, default 1):
First sector (34-234441614, default = 2048) or {+-}size{KMGTP}:
Last sector (2048-234441614, default = 234441614) or {+-}size{KMGTP}: +32M
Current type is 'Linux filesystem'
Hex code or GUID (L to show codes, Enter = 8300): ef02
Changed type of partition to 'BIOS boot partition'
Command (? for help): n
Partition number (2-128, default 2):
First sector (34-234441614, default = 67584) or {+-}size{KMGTP}:
Last sector (67584-234441614, default = 234441614) or {+-}size{KMGTP}: +256M
Current type is 'Linux filesystem'
Hex code or GUID (L to show codes, Enter = 8300):
Changed type of partition to 'Linux filesystem'
Command (? for help): n
Partition number (3-128, default 3):
First sector (34-234441614, default = 591872) or {+-}size{KMGTP}:
Last sector (591872-234441614, default = 234441614) or {+-}size{KMGTP}:
Current type is 'Linux filesystem'
Hex code or GUID (L to show codes, Enter = 8300): 8e00
Changed type of partition to 'Linux LVM'
Command (? for help): p
Disk /dev/sda: 234441648 sectors, 111.8 GiB
Logical sector size: 512 bytes
Disk identifier (GUID): D83D84F4-9955-4C12-8281-55F4D695F1CA
Partition table holds up to 128 entries
First usable sector is 34, last usable sector is 234441614
Partitions will be aligned on 2048-sector boundaries
Total free space is 2014 sectors (1007.0 KiB)
Number Start (sector) End (sector) Size Code Name
1 2048 67583 32.0 MiB EF02 BIOS boot partition
2 67584 591871 256.0 MiB 8300 Linux filesystem
3 591872 234441614 111.5 GiB 8E00 Linux LVM
Command (? for help):
Final checks complete. About to write GPT data. THIS WILL OVERWRITE EXISTING
PARTITIONS!!
Do you want to proceed? (Y/N): Y
OK; writing new GUID partition table (GPT) to /dev/sda.
The operation has completed successfully.
</code></pre></div></div>
<h3 id="lvm">LVM</h3>
<p>Les partitions</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Disk /dev/sda: 234441648 sectors, 111.8 GiB
Number Start (sector) End (sector) Size Code Name
1 2048 67583 32.0 MiB EF02 BIOS boot partition
2 67584 591871 256.0 MiB 8300 Linux filesystem
3 591872 234441614 111.5 GiB 8E00 Linux LVM
</code></pre></div></div>
<p>On utilise LVM pour la gestion du disque SSD<br />
Création volume physique</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pvcreate /dev/sda3
</code></pre></div></div>
<p>Création groupe</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vgcreate vg-ssd-one /dev/sda3
</code></pre></div></div>
<p>Création des volumes logiques</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>lvcreate -L 20G -n root vg-ssd-one
lvcreate -L 60G -n home vg-ssd-one
lvcreate -L 4G -n swap vg-ssd-one
</code></pre></div></div>
<h3 id="boot-sur-debianusb-pour-installation-de-base">Boot sur Debian/USB pour installation de base</h3>
<p>Installation debian sur Disque SSD “Sandisk” 120GB partionné par gdisk</p>
<p>On valide <strong>ssh</strong> et <strong>système</strong><br />
machine : shuttle<br />
Utiliser le partitionnement manuel</p>
<h1 id="yunohostshuttle-octobre-2016">YunohostShuttle Octobre 2016</h1>
<h2 id="installation-yunohost">Installation yunohost</h2>
<p>Connexion sur “shuttle” en su (par console ou SSH)</p>
<p>Installer git</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt-get install git
</code></pre></div></div>
<p>Cloner le dépôt du script dinstallation de YunoHost</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/YunoHost/install_script /tmp/install_script
</code></pre></div></div>
<p>Lancer le script dinstallation</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd /tmp/install_script
./install_yunohost
</code></pre></div></div>
<p>Poursuivre la post-installation :</p>
<p>Domaine : yanspm.com<br />
Mot de passe administration : xxxxxx</p>
<h2 id="sudo">sudo</h2>
<p>Donner les droits à lutilisateur <strong>yann</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#passer en mode su
su
#donner les droits à "yann"
echo "yann ALL=(ALL) NOPASSWD: ALL" &gt;&gt; /etc/sudoers
</code></pre></div></div>
<h2 id="fstab">fstab</h2>
<p>Disque Sata 4To sur /dev/sdb</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> PV VG Fmt Attr PSize PFree
/dev/sda3 vg-ssd-one lvm2 a-- 111,50g 27,50g
/dev/sdb3 vg-nas-one lvm2 a-- 3,64t 2,02t
</code></pre></div></div>
<p>Toutes les opérations se font en mode <strong>su</strong></p>
<p>On va ajouter les montages sur <strong>video</strong> et <strong>yanplus</strong></p>
<p>Création des points de montage</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mkdir /media/{video,yanplus}
</code></pre></div></div>
<p>Ajout des points de montage au fichier <strong>/etc/fstab</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/dev/mapper/vg--nas--one-yanplus /media/yanplus ext4 defaults 0 2
/dev/mapper/vg--nas--one-video /media/video ext4 defaults 0 2
</code></pre></div></div>
<p>Remonter le tout</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mount -a
</code></pre></div></div>
<p>Vérifier</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ls /media/yanplus
ls /media/video
</code></pre></div></div>
<h2 id="yunohost-connexion-admin">Yunohost Connexion admin</h2>
<p>Connexion en admin <a href="https://yanspm.com">https://yanspm.com</a></p>
<h3 id="domaines">Domaines</h3>
<h4 id="domaine-yanspmcom">domaine yanspm.com</h4>
<p>Afficher Domaine yanspm.com Configuration DNS</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@ 3600 IN A 78.230.171.39
* 3600 IN A 78.230.171.39
@ 3600 IN AAAA 2a01:e34:ee6a:b270:beae:c5ff:fe57:150c
* 3600 IN AAAA 2a01:e34:ee6a:b270:beae:c5ff:fe57:150c
_xmpp-client._tcp 3600 IN SRV 0 5 5222 yanspm.com.
_xmpp-server._tcp 3600 IN SRV 0 5 5269 yanspm.com.
muc 3600 IN CNAME @
pubsub 3600 IN CNAME @
vjud 3600 IN CNAME @
@ 3600 IN MX 10 yanspm.com.
@ 3600 IN TXT "v=spf1 a mx ip4:78.230.171.39 ip6:2a01:e34:ee6a:b270:beae:c5ff:fe57:150c -all"
mail._domainkey.yanspm.com. 3600 IN TXT "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDBZYXmqh/+d0zuGj/dfWsI8BPy+AUja87IysK84T8cyBSZrAzup2tiYChYRozMjDOUwHlnL8t4KkBG9o+wY0kzlN4nSTyfn4gQAV7EoymqHLXZrOIrlJGBkCKwKhevDV/vGzB6kS2SdcAoaDj4F49Bf2gAxPazgZClvm9a8rz4MQIDAQAB"
_dmarc 3600 IN TXT "v=DMARC1; p=none"
</code></pre></div></div>
<p>Ces paramètres seront utilisés dans la mise à jour du gestionnaire de domaine</p>
<h4 id="domaine-ouestlinenet">domaine ouestline.net</h4>
<p>Ajout du domaine par ladministrateur</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$TTL 3600
@ IN SOA dns111.ovh.net. tech.ovh.net. (2016100500 86400 3600 3600000 300)
IN NS dns111.ovh.net.
IN NS ns111.ovh.net.
IN MX 10 ouestline.net.
IN A 78.230.171.39
IN AAAA 2a01:e34:ee6a:b270:beae:c5ff:fe57:150c
600 IN TXT "v=spf1 a mx ip4:78.230.171.39 ip6:2a01:e34:ee6a:b270:beae:c5ff:fe57:150c -all"
* IN CNAME ouestline.net.
_dmarc IN TXT "v=DMARC1; p=none"
mail._domainkey IN TXT "v=DKIM1; k=rsa; t=s; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDHySCU3uS+lAnqe0I18wkB1SReJDxhZdYK3dDSDpxlDOfKRXNZdd/plJsmas+ZqAdxjhW/sdDdjj0YEZMcm3VqLZhwxpr3yJJm5/Lc32EfVVMXo9qhlzYByckc1azgGF0+hZTdDfsBwUh0IUdlOIgDnsfuf6p24X7P6ngaGqpghQIDAQAB"
</code></pre></div></div>
<p>Ces paramètres seront utilisés dans la mise à jour du gestionnaire de domaine</p>
<h3 id="diffie-hellman">Diffie Hellman</h3>
<p>Générer une clé Diffie-Hellman</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo openssl dhparam -out /etc/ssl/private/dh4096.pem -outform PEM -2 4096
</code></pre></div></div>
<p>Clé sous <strong>/etc/ssl/private</strong> ,propriétaire et droits</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo chown root.ssl-cert /etc/ssl/private/dh4096.pem
sudo chmod 640 /etc/ssl/private/dh4096.pem
</code></pre></div></div>
<p>Dans les fichiers de configuration nginx de chaque domaine concerné :
<strong>/etc/nginx/conf.d/ouestline.net.conf</strong> et <strong>/etc/nginx/conf.d/yanspm.com.conf</strong><br />
Ajouter la ligne suivante :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssl_dhparam /etc/ssl/private/dh4096.pem;
</code></pre></div></div>
<p>Recharger nginx</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo service nginx restart
</code></pre></div></div>
<h3 id="utilisateurs">Utilisateurs</h3>
<p>Création <strong>yanspm</strong><br />
Adresse de messagerie : <a href="mailto:yanspm@yanspm.com">yanspm@yanspm.com</a></p>
<h2 id="dns-ovh">DNS OVH</h2>
<p>Connexion <strong>OVH Manager</strong><br />
Modifier Zone DNS Domaine <strong>yanspm.com</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$TTL 86400
@ IN SOA dns101.ovh.net. tech.ovh.net. (2016100600 86400 3600 3600000 300)
IN NS ns101.ovh.net.
IN NS dns101.ovh.net.
IN MX 10 yanspm.com.
IN A 78.230.171.39
IN AAAA 2a01:e34:ee6a:b270:beae:c5ff:fe57:150c
600 IN TXT "v=spf1 a mx ip4:78.230.171.39 ip6:2a01:e34:ee6a:b270:beae:c5ff:fe57:150c -all"
* IN CNAME yanspm.com.
_dmarc IN TXT "v=DMARC1; p=none"
_xmpp-client._tcp 14400 IN SRV 0 5 5222 yanspm.com.
_xmpp-server._tcp 14400 IN SRV 0 5 5269 yanspm.com.
mail._domainkey IN TXT "v=DKIM1; k=rsa; t=s; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDBZYXmqh/+d0zuGj/dfWsI8BPy+AUja87IysK84T8cyBSZrAzup2tiYChYRozMjDOUwHlnL8t4KkBG9o+wY0kzlN4nSTyfn4gQAV7EoymqHLXZrOIrlJGBkCKwKhevDV/vGzB6kS2SdcAoaDj4F49Bf2gAxPazgZClvm9a8rz4MQIDAQAB"
</code></pre></div></div>
<h2 id="nfs-serveur">NFS serveur</h2>
<p>Installer</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt install nfs-kernel-server
</code></pre></div></div>
<p>Le fichier <strong>/etc/exports</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># /etc/exports: the access control list for filesystems which may be exported
# to NFS clients. See exports(5).
#
# Example for NFSv2 and NFSv3:
# /srv/homes hostname1(rw,sync,no_subtree_check) hostname2(ro,sync,no_subtree_check)
#
# Example for NFSv4:
# /srv/nfs4 gss/krb5i(rw,sync,fsid=0,crossmnt,no_subtree_check)
# /srv/nfs4/homes gss/krb5i(rw,sync,no_subtree_check)
#
# ' exportfs -a' : Met à jour la liste des systèmes de fichiers exportés
#
/media/yanplus/devel 192.168.0.0/24(rw,no_subtree_check,no_root_squash)
/media/yanplus/Musique 192.168.0.0/24(rw,no_subtree_check,no_root_squash)
/media/yanplus/BiblioCalibre 192.168.0.0/24(rw,no_subtree_check,no_root_squash)
/media/yanplus/dplus 192.168.0.0/24(rw,no_subtree_check,no_root_squash)
/media/video 192.168.0.0/24(rw,no_subtree_check,no_root_squash)
</code></pre></div></div>
<h3 id="fixer-les-ports-utilisés-par-nfs-pour-le-serveur-debian">Fixer les ports utilisés par NFS pour le serveur debian</h3>
<p>statd : <strong>/etc/default/nfs-common</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>STATDOPTS="--port 42001 --outgoing-port 42002"
</code></pre></div></div>
<p>mountd : <strong>/etc/default/nfs-kernel-server</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>RPCMOUNTDOPTS="--port 42003"
</code></pre></div></div>
<p>quotad : <strong>/etc/default/quota</strong> si la gestion des quotas est utilisée</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>RPCRQUOTADOPTS="-p 42005"
</code></pre></div></div>
<p>lockd : <strong>/etc/modprobe.d/local.conf</strong> pour les accès concurrents ,créer le fichier et ajouter</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> options lockd nlm_udpport=42004 nlm_tcpport=42004
options nfs callback_tcpport=42000
</code></pre></div></div>
<h3 id="règle-de-sécurité-du-partage">règle de sécurité du partage</h3>
<p>Serveur règle de sécurité du partage<br />
Tout interdire dans le fichier <strong>/etc/hosts.deny</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Tout interdire sur portmap, nfsd et mountd
portmap:ALL
nfsd:ALL
mountd:ALL
</code></pre></div></div>
<p>Ensuite autoriser les échanges voulus dans le fichier <strong>/etc/hosts.allow</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#Autoriser ce client à se connecter aux services (essentiellement : portmap, nfsd et mountd) :
portmap: 192.168.0.
nfsd: 192.168.0.
mountd: 192.168.0.
rquotad: 192.168.0.
statd: 192.168.0.
lockd: 192.168.0.
</code></pre></div></div>
<p>Il est possible dautoriser plusieurs clients,</p>
<ul>
<li>En définissant une plage, par exemple : 192.168.0.0/192.168.0.20</li>
<li>En listant les IP locales autorisées (séparées par des espaces), comme par exemple : 192.168.0.1 192.168.0.3 192.168.0.8</li>
<li>Ou encore en utilisant la syntaxe 192.168.0. qui autorise toutes les machines dont lIP locale commence par 192.168.0. à se connecter.</li>
</ul>
<h3 id="pare-feu-yunohost">pare-feu yunohost</h3>
<p>ouvrir en <strong>tcp et udp</strong> les ports suivants: 111, 2049 ,42000 à 42005</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yunohost firewall allow Both 111
yunohost firewall allow Both 2049
yunohost firewall allow Both 42000:42005
</code></pre></div></div>
<p><strong>REDEMARRER</strong> le serveur</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl reboot
</code></pre></div></div>
<h3 id="vérification-nfs">Vérification NFS</h3>
<p>Voir les ports</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rpcinfo -p
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> program vers proto port service
100000 4 tcp 111 portmapper
100000 3 tcp 111 portmapper
100000 2 tcp 111 portmapper
100000 4 udp 111 portmapper
100000 3 udp 111 portmapper
100000 2 udp 111 portmapper
100024 1 udp 42001 status
100024 1 tcp 42001 status
100003 2 tcp 2049 nfs
100003 3 tcp 2049 nfs
100003 4 tcp 2049 nfs
100227 2 tcp 2049
100227 3 tcp 2049
100003 2 udp 2049 nfs
100003 3 udp 2049 nfs
100003 4 udp 2049 nfs
100227 2 udp 2049
100227 3 udp 2049
100021 1 udp 42004 nlockmgr
100021 3 udp 42004 nlockmgr
100021 4 udp 42004 nlockmgr
100021 1 tcp 42004 nlockmgr
100021 3 tcp 42004 nlockmgr
100021 4 tcp 42004 nlockmgr
100005 1 udp 42003 mountd
100005 1 tcp 42003 mountd
100005 2 udp 42003 mountd
100005 2 tcp 42003 mountd
100005 3 udp 42003 mountd
100005 3 tcp 42003 mountd
</code></pre></div></div>
<p>Pour vérifier que lexport a bien eu lieu, taper sur le serveur NFS la commande :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>showmount -e
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Export list for shuttle:
/media/yanplus/devel 192.168.0.0/24
</code></pre></div></div>
<h2 id="nfs-client-archlinuxmanjaro">NFS Client Archlinux/Manjaro</h2>
<p>Installer le paquet <strong>nfs-utils</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo pacman -S nfs-utils
</code></pre></div></div>
<p>Coté client le service <strong>rpcbind</strong> est utilisé</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl start rpcbind
systemctl status rpcbind
systemctl enable rpcbind
</code></pre></div></div>
<p>Création des dossiers sur le client</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mkdir -p /mnt/devel
</code></pre></div></div>
<p>Démarrage auto par ajout dans <strong>/etc/fstab</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>192.168.0.45:/media/yanplus/devel /mnt/devel nfs4 noauto,x-systemd.automount 0 0
</code></pre></div></div>
<h2 id="ssh">SSH</h2>
<p>Ajout clé publique <strong>shuttle.pub</strong> depuis <strong>poste appelant</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>scp ~/.ssh/shuttle.pub yann@192.168.0.45:/home/yann
</code></pre></div></div>
<p>Sur le <strong>poste distant</strong> création en mode utilisateur “yann” du dossier <strong>.ssh</strong> et des droits</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mkdir ~/.ssh
chmod 700 ~/.ssh
</code></pre></div></div>
<p>Ajout de la clé publique</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cat shuttle.pub &gt;&gt; ~/.ssh/authorized_keys
#modifier les droits
chmod 600 ~/.ssh/authorized_keys
#Effacer le fichier de la clé
rm shuttle.pub
</code></pre></div></div>
<p>Modification fichier configuration <strong>ssh</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo nano /etc/ssh/sshd_config
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Port 55022
PermitRootLogin no
PasswordAuthentication no
#si délai lors d'une connexion ssh sous Linux
UseDNS no
</code></pre></div></div>
<p>Relancer</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo systemctl restart ssh
</code></pre></div></div>
<p>Activer le port 55022 et désactiver le port 22 sur le parefeu yunohost</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo yunohost firewall allow TCP 55022
sudo yunohost firewall disallow TCP 22
</code></pre></div></div>
<h2 id="onduleur">Onduleur</h2>
<h3 id="matériel">Matériel</h3>
<p><a href="http://ovanhoof.developpez.com/upsusb/">Installation et gestion dun UPS USB en réseau sous linux</a><br />
UPS = Uninterruptable Power System<br />
Le périphérique UPS <strong>Eaton Protection Station 800 USB</strong> à gérer est de type “HID”<br />
Connecter londuleur liaison USB sur un port disponible du serveur</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#passer en mode su
sudo -s
#dmesg montre que votre UPS est détecté :
dmesg
[...]
[ 2470.480042] usb 1-1: USB disconnect, device number 3
[ 4300.332009] usb 1-2: new low-speed USB device number 4 using uhci_hcd
[ 4302.429077] usb 1-2: New USB device found, idVendor=0463, idProduct=ffff
[ 4302.429081] usb 1-2: New USB device strings: Mfr=1, Product=2, SerialNumber=4
[ 4302.429084] usb 1-2: Product: Protection Station
[ 4302.429086] usb 1-2: Manufacturer: EATON
[ 4302.429088] usb 1-2: SerialNumber: AN2E49008
[ 4304.624720] hid-generic 0003:0463:FFFF.0005: hiddev0,hidraw0: USB HID v10.10 Device [EATON Protection Station] on usb-0000:00:1d.0-2/input0
</code></pre></div></div>
<p>Ok, lUPS est détecté correctement, mais maintenant il faut encore vérifier son pseudo-fichier dans /dev</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ls -l /dev/usb/
total 0
crw------- 1 root root 180, 0 oct. 7 09:30 hiddev0
</code></pre></div></div>
<p>périphérique en mode caractère <strong>/dev/usb/hiddev0</strong> (les données échangées entre lups et lordinateur sont des codes alphanumériques )</p>
<h3 id="installation-nut">Installation nut</h3>
<p>Installer les paquets NUT sous Debian Jessie :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt install nut
</code></pre></div></div>
<p>on choisit le mode <strong>STANDALONE</strong> dans fichier <strong>/etc/nut/nut.conf</strong>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> MODE=standalone
</code></pre></div></div>
<p>Il faut ensuite spécifier comment communiquer avec lUPS, donc choisir le driver et le port , ce qui fait dans le fichier <strong>/etc/nut/ups.conf</strong></p>
<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[eaton]</span>
<span class="py">driver</span> <span class="p">=</span> <span class="s">usbhid-ups </span>
<span class="py">port</span> <span class="p">=</span> <span class="s">auto</span>
<span class="py">desc</span> <span class="p">=</span> <span class="s">"Eaton Protection Station 800"</span>
</code></pre></div></div>
<p>Configurer le démon réseau au niveau des accès via le fichier <strong>/etc/nut/upsd.conf</strong><br />
On écoute en local sur le port 3493</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>LISTEN 127.0.0.1 3493
LISTEN ::1 3493
MAXCONN 32 #Nombre maximal de connections simultanées
</code></pre></div></div>
<h4 id="utilisateurs-1">Utilisateurs</h4>
<p>Créer les utilisateurs (administrateur et superviseur) fichier <strong>/etc/nut/upsd.users</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># 1 seul utilisateur "ond" avec tous les droits !
[ond]
password = dfYRtY38
upsmon master
</code></pre></div></div>
<h4 id="moniteur-de-supervision">moniteur de supervision</h4>
<p>Le moniteur de supervision va surveiller londuleur et lancer différentes actions en fonction des évènements constatés fichier <strong>/etc/nut/upsmon.conf</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Notre serveur n'a pas d'alimentation redondante = 1
MINSUPPLIES 1
# Commande d'arrêt du serveur en cas de fin d'autonomie
SHUTDOWNCMD "/sbin/shutdown -h +0"
# Commande lancée quand quelque chose se passe
NOTIFYCMD /sbin/upssched
# Interval entre deux interrogations de upsd
POLLFREQ 5
# Intervalle entre deux interrogations de upsd en mode "batterie"
POLLFREQALERT 5
# Temps d'attente pour la déconnexion des upsmon esclaves
HOSTSYNC 15
# Temps pendant lequel on tolère la non-réponse d'un onduleur, multiple de POLLFREQ
DEADTIME 15
# Fichier d'état
POWERDOWNFLAG /etc/killpower
# Intervalle de 1/2 jour, pour répéter le message de "remplacement de batteries - NOTIFY_REPLBATT"
RBWARNTIME 43200
# Interval de 5 minutes, pour répéter le message de "onduleur injoignable - NOTIFY_NOCOMM"
NOCOMMWARNTIME 300
# Intervalle entre la "notification d'arrêt - NOTIFY_SHUTDOWN" et le lancement de SHUTDOWNCMD
FINALDELAY 5
# On surveille l'onduleur qui est directement relié (master)
MONITOR eaton@localhost 1 ond dfYRtY38 master
# Actions spécifiques autres que par défaut (SYSLOG et WALL) réalisées en fonction de l'état retourné par l'onduleur
NOTIFYFLAG COMMBAD EXEC
NOTIFYFLAG ONLINE SYSLOG+EXEC
NOTIFYFLAG ONBATT SYSLOG+EXEC
NOTIFYFLAG LOWBATT EXEC
NOTIFYFLAG REPLBATT SYSLOG+EXEC
NOTIFYFLAG SHUTDOWN EXEC
NOTIFYFLAG COMMOK IGNORE
</code></pre></div></div>
<p>On remarque que, à part larrêt du système sur fin des batteries qui est géré par <strong>upsmon</strong>, toutes les notifications le sont par <strong>upssched</strong>.</p>
<h4 id="distributeur-dévènements">distributeur dévènements</h4>
<p>Lutilitaire <strong>upssched</strong> permet de « temporiser » les actions liées aux évènements générés par <strong>upsmon</strong>.<br />
Cest surtout intéressant lorsque des évènements furtifs se produisent (microcoupures) fichier <strong>/etc/nut/upssched.conf</strong>.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Script lancé par upssched pour gérer les évènements et les timers associés
CMDSCRIPT /etc/nut/upssched-cmd
# Fichier pour noter les états internes de upssched
PIPEFN /var/run/nut/upssched.pipe
# Fichier de lock pour éviter un conflit en cas de notification de deux évènements simultanés
LOCKFN /var/run/nut/upssched.lock
# En cas de perte de communication avec l'onduleur
AT COMMBAD * EXECUTE perte-liaison
# En cas de retour secteur, on stope la minuterie et on notifie
AT ONLINE * CANCEL-TIMER attente-retour-secteur
AT ONLINE * EXECUTE charge-sur-secteur
# En cas de perte de secteur, on se laisse au maximum 20 minutes avant d'agir et on notifie
AT ONBATT * START-TIMER attente-retour-secteur 1200
AT ONBATT * EXECUTE charge-sur-batterie
# En cas de niveau de batteries trop bas
AT LOWBATT * EXECUTE batteries-vides
# En cas de fin de vie des batteries on sera prévenu
AT REPLBATT * EXECUTE batteries-hs
# En cas d'arrêt (fin des 20 minutes ou fin d'autonomie), on notifie.
AT SHUTDOWN * EXECUTE arret-en-cours
</code></pre></div></div>
<h4 id="prise-en-charge-des-évènements-script">prise en charge des évènements (script)</h4>
<p>Fichier <strong>/etc/nut/upssched-cmd</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#!/usr/bin/perl -w
use Encode;
use utf8;
use strict;
use Switch;
# encodage des données transmises dans lURL
use URI::Escape;
sub SendEmail
{
my ( $from, $to, $subject, $msg ) = @_;
$from = Encode::encode('MIME-Q', $from);
$to = Encode::encode('MIME-Q', $to);
$subject = Encode::encode('MIME-Q', $subject);
open (SENDMAIL, "| /usr/sbin/sendmail -t") or die("Failed to open pipe to sendmail: $!");
binmode(SENDMAIL, ":utf8");
print SENDMAIL &lt;&lt;"EOF";
Content-Transfer-Encoding: 8bit
Content-type: text/plain; charset=UTF-8
Subject: $subject
From: $from
To: $to
$msg
EOF
close (SENDMAIL);
}
my $orig= 'Onduleur@yanspm.com';
my $dest='yanspm@yanspm.com';
my $sujet="Onduleur - Arrêt en cours";
my $message="Le système est en cours d'arrêt.";
my $comment="";
#
# Début du programme
#
if ($#ARGV == -1) {
print "Saisir argument : "; $comment=&lt;STDIN&gt;; chomp($comment);
}
else {
$comment=$ARGV[0];
}
#
# Analyse argument
#
switch ($comment) {
case "charge-sur-batterie" {
$sujet="Onduleur - Charge sur batteries";
$message="L'onduleur est passé sur batteries ...\nL'arrêt système sera demandé si le secteur ne revient pas." ;
}
case "attente-retour-secteur" {
$sujet="Onduleur - Fin d'attente de retour secteur";
$message="Cela fait trop longtemps que le secteur est absent.\nUn arrêt forcé est en cours !";
# Demande d'arrêt forcé (force shut down)
system "/sbin/upsmon -c fsd";
}
case "charge-sur-secteur" {
$sujet="Onduleur - Charge sur secteur";
$message="L'onduleur est revenu sur secteur.";
}
case "batteries-vide" {
$sujet="Onduleur - Batteries vides";
$message="Les batteries sont vides, l'arrêt est imminent.";
}
case "arret-en-cours" {
$sujet="Onduleur - Arrêt en cours";
$message="Le système est en cours d'arrêt.";
}
case "perte-liaison" {
$sujet="Onduleur - Perte de liaison avec l'onduleur";
$message="La communication avec l'onduleur est interrompue.";
}
case "batteries-hs" {
$sujet="Onduleur - URGENT - batteries HS";
$message="Les batteries sont à remplacer d'urgence.";
}
else {
$sujet="Onduleur - Commande inconnue ...";
$message="Une commande inconnue a été envoyée par l'onduleur.\nLa commande est : $comment";
}
}
# Envoi du message
SendEmail($orig,$dest,$sujet,$message);
</code></pre></div></div>
<p>Rendre exécutable le script</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>chmod +x /etc/nut/upssched-cmd
</code></pre></div></div>
<p><strong>Redémarrer la machine</strong></p>
<p>vérifier si les daemons sont lancés</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ps auxf |grep ups
root 1479 0.0 0.0 12748 2232 pts/0 S+ 10:42 0:00 \_ grep ups
nut 1373 0.0 0.0 19140 1668 ? Ss 10:35 0:00 /lib/nut/usbhid-ups -a eaton
nut 1375 0.0 0.0 31864 2112 ? Ss 10:35 0:00 /lib/nut/upsd
root 1377 0.0 0.0 29492 1496 ? Ss 10:35 0:00 /lib/nut/upsmon
nut 1378 0.0 0.0 37668 4892 ? S 10:35 0:00 \_ /lib/nut/upsmon
</code></pre></div></div>
<p>Onduleur connecté ?</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>upsc eaton@localhost
[...]
ups.mfr: EATON
ups.model: Protection Station 800
ups.power.nominal: 800
ups.productid: ffff
ups.serial: AN2E49008
ups.status: OL CHRG
ups.timer.shutdown: 0
ups.timer.start: 0
ups.vendorid: 0463
</code></pre></div></div>
<h2 id="yunohost-applications">yunohost applications</h2>
<h3 id="comment-changer-le-nom-dune-application-installée">Comment changer le nom dune application installée?</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># passer en mode su
sudo -s
# lister les applications (installed=true)
yunohost app list
[...]
description: App vide sans accès FTP
id: multi_webapp
installed: True
label: Shaarli
license: free
name: Multi custom webapp
[...]
# on change le label
yunohost app setting multi_webapp label -v "Shaarli"
# actualisation
yunohost app ssowatconf &amp;&amp; nginx -s reload
</code></pre></div></div>
<h3 id="mise-à-jour-dune-application-installée-par-lien-github">Mise à jour dune application installée par lien github</h3>
<p>Nextcloud</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo yunohost app upgrade -u https://github.com/YunoHost-Apps/nextcloud_ynh
</code></pre></div></div>
<p>Coops</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo yunohost app upgrade -u https://github.com/YunoHost-Apps/cops_ynh
</code></pre></div></div>
<p>bicbucstriim</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo yunohost app upgrade -u https://https://github.com/YunoHost-Apps/BicBucStriim_ynh
</code></pre></div></div>
<p>multiwebapp</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo yunohost app upgrade -u https://github.com/YunoHost-Apps/multi_webapp_ynh
</code></pre></div></div>
<h2 id="serveur-de-sauvegarde-shuttle">serveur de sauvegarde (shuttle)</h2>
<p>Création utilisateur <strong>backupuser</strong> et jeu de clé ssh</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo adduser backupuser` #mot de passe à saisir
</code></pre></div></div>
<p>Cette commande va vous demander plusieurs informations et notamment un mot de passe à noter impérativement.<br />
Nhésitez pas à définir un mot de passe compliqué, denviron 16 caractères avec des chiffres, des lettres majuscule/minuscule et quelques caractères spéciaux car vous naurez à le taper réellement quune fois.</p>
<p>Création dossier <strong>.ssh</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo mkdir /home/backupuser/.ssh/
</code></pre></div></div>
<p>Modifier les droits</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo chown backupuser. -R /home/backupuser/.ssh/
sudo chmod 644 -R /home/backupuser/.ssh/
</code></pre></div></div>
<p>On se connecte en backupuser pour générer les clés RSA</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo su backupuser
ssh-keygen -t rsa
chmo 400 id_rsa* #lecture seule pour utilisateur backupuser
exit # retour sur utilisateur précédent
</code></pre></div></div>
<p>Copier la clé publique sur lhôte distant</p>
<h2 id="ssl-certificats">SSL Certificats</h2>
<ul>
<li><a href="post_url 2017-08-31-Acme-Certficats-Serveurs %}">Yunohost , installer et renouveler les certificats Lets encrypt</a></li>
</ul>
<p>La liste des certificats</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># cat /etc/yunohost/certs/yanspm.com/crt.pem |openssl x509 -text |grep DNS
DNS:yanspm.com
# cat /etc/yunohost/certs/ouestline.net/crt.pem |openssl x509 -text |grep DNS
DNS:blog.ouestline.net, DNS:cartes.ouestline.net, DNS:devel.ouestline.net, DNS:lua.ouestline.net, DNS:map.ouestline.net, DNS:media.ouestline.net, DNS:music.ouestline.net, DNS:ouestline.net, DNS:photos.ouestline.net, DNS:static.ouestline.net, DNS:yannick.ouestline.net
root@shuttle:/home/yann#
</code></pre></div></div>
<p>Pour info le batch de création du certificat <strong>lets encrypt</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./letsencrypt-auto certonly --config /etc/letsencrypt/conf.ini -d ouestline.net -d blog.ouestline.net -d cartes.ouestline.net -d devel.ouestline.net -d lua.ouestline.net -d map.ouestline.net -d media.ouestline.net -d music.ouestline.net -d photos.ouestline.net -d static.ouestline.net -d yannick.ouestline.net -d notes.ouestline.net
</code></pre></div></div>
<h2 id="cops">Cops</h2>
<p>Configuration ** /var/www/cops/config_local.php**</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$config['calibre_directory'] = '/media/yanplus/BiblioCalibre/';
</code></pre></div></div>
<p><strong>/etc/nginx/conf.d/yanspm.com.d/cops.conf</strong> pour ajouer <code class="language-plaintext highlighter-rouge">fastcgi_param SCRIPT_FILENAME $request_filename;</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>location /cops {
alias /var/www/cops/;
auth_pam "closed site";
auth_pam_service_name "nginx";
index index.php;
try_files $uri $uri/ index.php;
location ~ [^/]\.php(/|$) {
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
fastcgi_pass unix:/var/run/php5-fpm.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;
}
}
</code></pre></div></div>
<p>Relancer le service <code class="language-plaintext highlighter-rouge">service nginx restart</code><br />
Accès <a href="https://yanspm.com/cops">https://yanspm.com/cops</a></p>
<h2 id="les-sous-domaines-ouestlinenet">Les sous-domaines ouestline.net</h2>
<p>Applications gérées par le fichier de configuration nginx <strong>/etc/nginx/conf.d/subdomain.ouestline.net.conf</strong> et les paramètres SSL du domaine <strong>ouestline.net</strong> par le fichier <strong>/etc/nginx/ssl_params</strong></p>
<p>Les sous-domaines <strong>ouestline.net</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ouestline.net
blog.ouestline.net
cartes.ouestline.net
devel.ouestline.net
map.ouestline.net
media.ouestline.net
music.ouestline.net
static.ouestline.net
notes.ouestline.net
yannick.ouestline.net
</code></pre></div></div>
<h3 id="paramètres-ssl">Paramètres SSL</h3>
<p><strong>/etc/nginx/ssl_params</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> ssl_certificate /etc/yunohost/certs/ouestline.net/crt.pem;
ssl_certificate_key /etc/yunohost/certs/ouestline.net/key.pem;
ssl_session_timeout 5m;
ssl_session_cache shared:SSL:50m;
ssl_prefer_server_ciphers on;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers ALL:!aNULL:!eNULL:!LOW:!EXP:!RC4:!3DES:+HIGH:+MEDIUM;
add_header Strict-Transport-Security "max-age=31536000;";
</code></pre></div></div>
<h3 id="fichier-de-configuration-nginx">Fichier de configuration nginx</h3>
<p><strong>/etc/nginx/conf.d/subdomain.ouestline.net.conf</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># serveur développement
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name devel.ouestline.net;
root /media/yanplus/devel/ouestline;
include ssl_params;
ssl_dhparam /etc/ssl/private/dh4096.pem;
index index.php index.html index.htm;
try_files $uri $uri/ index.php;
location ~ [^/]\.php(/|$) {
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
fastcgi_pass unix:/var/run/php5-fpm.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;
}
fancyindex on; # Enable fancy indexes.
fancyindex_exact_size off; # Output human-readable file sizes.
#logs
access_log /var/log/nginx/devel.ouestline.net-access.log;
error_log /var/log/nginx/devel.ouestline.net-error.log;
}
# serveur turtl pour notes (framasoft)
upstream turtl {
server 127.0.0.1:8181;
}
client_max_body_size 2m;
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name notes.ouestline.net;
include ssl_params;
ssl_dhparam /etc/ssl/private/dh4096.pem;
index index.html;
location / {
proxy_set_header Host $host;
proxy_pass http://turtl;
}
#logs
access_log /var/log/nginx/notes.ouestline.net-access.log;
error_log /var/log/nginx/notes.ouestline.net-error.log;
}
# cartes
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name map.ouestline.net;
root /media/yanplus/devel/osm;
include ssl_params;
ssl_dhparam /etc/ssl/private/dh4096.pem;
index index.php index.html index.htm;
try_files $uri $uri/ index.php;
location ~ [^/]\.php(/|$) {
fastcgi_split_path_info ^(.+?\.php)(/.*)$;
fastcgi_pass unix:/var/run/php5-fpm.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;
}
#autoindex on;
fancyindex on; # Enable fancy indexes.
fancyindex_exact_size off; # Output human-readable file sizes.
#logs
access_log /var/log/nginx/map.ouestline.net-access.log;
error_log /var/log/nginx/map.ouestline.net-error.log;
}
# média
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name media.ouestline.net;
include ssl_params;
ssl_dhparam /etc/ssl/private/dh4096.pem;
# raspberry pi - domoticz
# log pi mp raspberry
location / {
proxy_pass http://192.168.0.35:8080;
}
#logs
access_log /var/log/nginx/media.ouestline.net-access.log;
error_log /var/log/nginx/media.ouestline.net-error.log;
}
# peoxy serveur statique type jekyll
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name static.ouestline.net;
include ssl_params;
ssl_dhparam /etc/ssl/private/dh4096.pem;
location / {
proxy_pass http://127.0.0.1:4000;
}
#logs
access_log /var/log/nginx/static.ouestline.net-access.log;
error_log /var/log/nginx/static.ouestline.net-error.log;
}
# proxy subsonic
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name music.ouestline.net;
include ssl_params;
ssl_dhparam /etc/ssl/private/dh4096.pem;
location / {
proxy_pass http://127.0.0.1:8080;
}
#logs
access_log /var/log/nginx/music.ouestline.net-access.log;
error_log /var/log/nginx/music.ouestline.net-error.log;
}
</code></pre></div></div>
<h3 id="subsoniclibresonic">Subsonic/Libresonic</h3>
<h4 id="subsonic-debian">Subsonic (Debian)</h4>
<p><a href="https://music.ouestline.net">https://music.ouestline.net</a></p>
<p>Installer jdk</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt-get install openjdk-7-jre
</code></pre></div></div>
<p>Télécharger le .deb</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wget http://subsonic.org/download/subsonic-6.0.deb
</code></pre></div></div>
<p>Installer le .deb</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo dpkg -i subsonic-6.0.deb
</code></pre></div></div>
<p>Fichier de configuration <strong>/etc/default/subsonic</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#
# This is the configuration file for the Subsonic service
# (/etc/init.d/subsonic)
#
# To change the startup parameters of Subsonic, modify
# the SUBSONIC_ARGS variable below.
#
# Type "/usr/share/subsonic/subsonic.sh --help" on the command line to read an
# explanation of the different options.
#
# For example, to specify that Subsonic should use port 80 (for http)
# and 443 (for https), and use a Java memory heap size of 200 MB, use
# the following:
#
# SUBSONIC_ARGS="--port=80 --https-port=443 --max-memory=200"
SUBSONIC_ARGS="--port=8090 --max-memory=200"
# The user which should run the Subsonic process. Default "root".
# Note that non-root users are by default not allowed to use ports
# below 1024. Also make sure to grant the user write permissions in
# the music directories, otherwise changing album art and tags will fail.
SUBSONIC_USER=yann
</code></pre></div></div>
<p>NE PAS OUBLIER : <strong>admin</strong> Autoriser laccès à ces dossiers de médias <strong>Music</strong></p>
<p>Configuration serveur nginx pour accès <a href="https://music.ouestline.net">https://music.ouestline.net</a> <br />
Ajouter ce qui suit au fichier <strong>/etc/nginx/conf.d/subdomain.ouestline.net.conf</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># proxy subsonic
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name music.ouestline.net;
include ssl_params;
ssl_dhparam /etc/ssl/private/dh4096.pem;
location / {
proxy_pass http://127.0.0.1:8090;
}
#logs
access_log /var/log/nginx/music.ouestline.net-access.log;
error_log /var/log/nginx/music.ouestline.net-error.log;
}
</code></pre></div></div>
<p>Relancer le serveur</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo systemctl restart nginx
</code></pre></div></div>
<h4 id="java-version">Java version</h4>
<p>Si plusieurs versions installées</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo update-alternatives --config java
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Il existe 2 choix pour l'alternative java (qui fournit /usr/bin/java).
Sélection Chemin Priorité État
------------------------------------------------------------
0 /usr/lib/jvm/java-7-openjdk-amd64/jre/bin/java 1071 mode automatique
1 /usr/lib/jvm/java-7-openjdk-amd64/jre/bin/java 1071 mode manuel
* 2 /usr/lib/jvm/java-8-openjdk-amd64/jre/bin/java 1069 mode manuel
Appuyez sur &lt;Entrée&gt; pour conserver la valeur par défaut[*] ou choisissez le numéro sélectionné :
</code></pre></div></div>
<h4 id="subsonic-docker-desinstalle">Subsonic (docker) DESINSTALLE</h4>
<p>On va installer un <strong>serveur subsonic</strong> sous forme de <strong>container docker</strong></p>
<ol>
<li>Installer <strong>docker</strong></li>
<li>Créer un container <strong>yann_subso</strong> à partir dun fichier <a href="https://github.com/cyrilix/docker-subsonic">Dockfile</a></li>
<li>Créer un fichier de configuration <strong>music.ouestline.conf</strong> nginx avec proxy</li>
<li>Créer un fichier systemd <strong>dockersubso.service</strong> pour la gestion du container</li>
</ol>
<p><a href="/doku.php?id=docker#docker_sur_shuttle">docker : Instructions détaillées</a></p>
<p>Création fichier de configuration nginx <br />
<code class="language-plaintext highlighter-rouge">sudo nano /etc/nginx/conf.d/music.ouestline.net.conf</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>server {
listen 443 ssl;
listen [::]:443 ssl;
server_name music.ouestline.net;
ssl_certificate /etc/yunohost/certs/ouestline.net/crt.pem;
ssl_certificate_key /etc/yunohost/certs/ouestline.net/key.pem;
ssl_session_timeout 5m;
ssl_session_cache shared:SSL:50m;
ssl_prefer_server_ciphers on;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers ALL:!aNULL:!eNULL:!LOW:!EXP:!RC4:!3DES:+HIGH:+MEDIUM;
add_header Strict-Transport-Security "max-age=31536000;";
ssl_dhparam /etc/ssl/private/dh4096.pem;
location / {
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header Host $http_host;
proxy_max_temp_file_size 0;
proxy_pass http://127.0.0.1:8080;
proxy_redirect http:// https://;
}
#logs
access_log /var/log/nginx/music.ouestline.net-access.log;
error_log /var/log/nginx/music.ouestline.net-error.log;
}
</code></pre></div></div>
<p>Accès <a href="https://music.ouestline.net">https://music.ouestline.net</a></p>
<h4 id="libresonic--desinstalle">Libresonic DESINSTALLE</h4>
<p><em>Libresonic est un streamer multimédia gratuit basé sur le Web, offrant un accès permanent à votre musique.
Libresonic est conçu pour gérer de très grandes collections de musique (des centaines de gigaoctets). Bien que optimisé pour le streaming MP3, il fonctionne pour nimporte quel format audio ou vidéo pouvant être transmis via HTTP, par exemple AAC et OGG. En utilisant des plug-ins de transcode, Libresonic prend en charge la conversion à la volée et le streaming de pratiquement nimporte quel format audio, y compris WMA, FLAC, APE, Musepack, WavPack et Shorten.</em></p>
<blockquote>
<p>ATTENTION! Si <strong>subsonic</strong> est installé , il faut désactiver le service <code class="language-plaintext highlighter-rouge">sudo systemctl stop subsonic.service &amp;&amp; sudo systemctl disable subsonic.service</code></p>
</blockquote>
<p><a href="https://github.com/YunoHost-Apps/libresonic_ynh">https://github.com/YunoHost-Apps/libresonic_ynh</a><br />
<a href="https://libresonic.github.io/docs/">Libresonic Documentation</a></p>
<p>Installation</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo yunohost app install https://github.com/YunoHost-Apps/libresonic_ynh
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Choisissez un nom de domaine pour Libresonic (default: yanspm.com) : ouestline.net
Choisissez un chemin pour Libresonic (default: /libresonic) :
Choisissez ladministrateur : yanspm
Succès ! La configuration de SSOwat a été générée
Succès ! Installation terminée
</code></pre></div></div>
<p>Laccès se fait via le portail <a href="https://ouestline.net/libresonic">https://ouestline.net/libresonic</a> , administration ldap via yanspm</p>
<p>Désinstallation</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo rm /etc/nginx/conf.d/ouestline.net.d/libresonic.conf
sudo rm /etc/default/libresonic
sudo service tomcat8 stop
sudo rm /var/lib/tomcat8/webapps/libresonic.war
sudo rm -r /var/libresonic/
sudo systemctl disable tomcat8
sudo service nginx reload
sudo yunohost app ssowatconf
</code></pre></div></div>
<h3 id="serveur-turtl-notesouestlinenet-désactivé">Serveur Turtl notes.ouestline.net (désactivé)</h3>
<p><a href="https://notes.ouestline.net">https://notes.ouestline.net</a></p>
<p>Turtl est un logiciel libre distribué sous licence AGPLv3 qui a pour objectif de fournir un système de notes synchronisables…<br />
<a href="http://framacloud.org/cultiver-son-jardin/installation-de-turtl/">Framacloud rubrique « Cultiver son jardin »:Installation de Turtl</a></p>
<p>Suivre la documentation dinstallation du serveur turtl</p>
<p>Les fichiers spécifiques :<br />
<strong>/var/www/turtl/api/config/config.lisp</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(in-package :turtl)
(defparameter *root* (asdf:system-relative-pathname :turtl #P"")
"Defines the root directory turtl is loading from (basically the ASDF path).")
(defparameter *pid-file* nil
"File to write the current proc's PID to.")
(defvar *server-bind* nil
"The address to bind Turtl to (nil is the same as 0.0.0.0).")
(defvar *server-port* 8181
"The port we want to start the Turtl service on.")
(defvar *db-name* "turtl"
"The name of the database we'll be using LOL")
(defvar *db-host* "127.0.0.1"
"The database hostname/ip.")
(defvar *db-port* 28015
"The database port number.")
(defvar *production-error-handling* nil
"If t, will attempt to catch all errors that make it to top-level and not let
the parent process enter the debugger.")
(defvar *enable-hsts-header* nil
"If NIL, Turtl won't pass back an HSTS security header. If this is set, it
should be set to a integer value, which will be passed to the max-age value
of the header.")
(defvar *enabled-cors-resources* "resource://turtl-at-lyonbros-dot-com"
"When set, will enable CORS for resource:// origins if they match the given
string. Entries should be comma separated (this string is passed verbatim in
the Access-Control-Allow-Origin header).")
(defvar *site-url* "https://notes.ouestline.net"
"The main URL the site will load from.")
(defvar *api-path* ""
"The path the API lives under. Can be blank.")
(defvar *admin-email* "contact@ouestline.net"
"The email used for admin communications. This is reported to users on a
server error, and possibly other instances.")
(defvar *email-from* "noreply@ouestline.net"
"The email address all turtl emails come from.")
(defvar *email-user* ""
"The username used for sending email. Needs to be set on load.")
(defvar *email-pass* ""
"The password used for sending email. Needs to be set on load.")
(defvar *display-errors* t
"Whether or not to show errors in HTTP responses. Useful for debugging, bad
for production.")
(defparameter *public-actions*
`((:post . ,(concatenate 'string *api-path* "/users"))
(:post . ,(concatenate 'string *api-path* "/log/error"))
(:post . "/cla/sign")
(:get . ,(cl-ppcre:create-scanner (concatenate 'string *api-path* "/invites/codes/([0-9a-f-]+)"))))
"A list of public resources/actions that do not require authentication.")
(defparameter *default-storage-limit* 100
"The max amount of data a profile can hold (in megabytes). Set to nil to allow
infinite size profiles.")
(defparameter *storage-invite-credit* 25
"The amount of storage (in mb) to credit a user when they refer someone.")
;; setup the logger
(vom:config :turtl :info)
(defvar *analytics* '(:enabled t
:db "analytics")
"Holds analytics config")
;; -----------------------------------------------------------------------------
;; File storage section.
;; -----------------------------------------------------------------------------
;; Choose either local uploads or S3.
(defvar *local-upload* nil
"NIL disables local files storage (files are uploaded to S3). Set to a local
path to save files locally instead of remotely. No trailing slash!")
(defvar *local-upload-url* nil
"Define the URL that local files will be loaded from. Generally, this will be
the same URL the API is accessed from and only needs to be defined if the
*local-upload* variable has a value. This should *not* include the /files
path, and there should be no trailing slash. Example:
https://notes.ouestline.net")
(defvar *amazon-s3* '(:token ""
:secret ""
:bucket ""
:endpoint "https://s3.amazonaws.com")
"Holds Amazon S3 config.")
</code></pre></div></div>
<p><strong>/etc/systemd/system/turtl.service</strong></p>
<div class="language-ini highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nn">[Unit]</span>
<span class="py">Description</span><span class="p">=</span><span class="s">Note taking service</span>
<span class="py">Documentation</span><span class="p">=</span><span class="s">http://turtl.it</span>
<span class="py">Requires</span><span class="p">=</span><span class="s">network.target</span>
<span class="py">Requires</span><span class="p">=</span><span class="s">rethinkdb.service</span>
<span class="py">After</span><span class="p">=</span><span class="s">network.target</span>
<span class="py">After</span><span class="p">=</span><span class="s">rethinkdb.service</span>
<span class="nn">[Service]</span>
<span class="py">Type</span><span class="p">=</span><span class="s">simple</span>
<span class="py">User</span><span class="p">=</span><span class="s">www-data</span>
<span class="py">WorkingDirectory</span><span class="p">=</span><span class="s">/var/www/turtl/api/</span>
<span class="py">ExecStart</span><span class="p">=</span><span class="s">/usr/bin/ccl -Q -b --load start.lisp</span>
<span class="nn">[Install]</span>
<span class="py">WantedBy</span><span class="p">=</span><span class="s">multi-user.target</span>
</code></pre></div></div>
<p>Rendons maintenant notre Turtl accessible depuis lextérieur grâce à Nginx.<br />
<strong>/etc/nginx/conf.d/subdomain.ouestline.net.conf</strong></p>
<h3 id="cartographie-mapouestlinenet">Cartographie map.ouestline.net</h3>
<p><a href="http://map.ouestline.net">http://map.ouestline.net</a></p>
<p>Le dossier web <strong>/media/yanplus/devel/osm</strong></p>
<h3 id="serveur-jekyll-staticouestlinenet">Serveur jekyll static.ouestline.net</h3>
<p><a href="https://static.ouestline.net">https://static.ouestline.net</a></p>
<p>Voir création <a href="post_url 2016-10-01-Jekyll-serveur-statique %}">Serveur statique Jekyll sur debian Jessie</a></p>
<p>Pour créer les “posts” de static.ouestline.net sur le PC1 desktop , on va créer des liens symboliques sur <strong>/media/yanplus/devel/jekyll</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Création des dossiers
sudo mkdir -p /media/yanplus/devel/ouestline-jekyll{_posts,images}
# droits
sudo chown $USER.users -R /media/yanplus/devel/ouestline-jekyll{_posts,images}
# les liens , on supprime les dossiers existants sur dbyll/
sudo rm -r /srv/jekyll/dbyll/{_posts,images}
# création des liens
sudo ln -s /media/yanplus/devel/ouestline-jekyll_posts /srv/jekyll/dbyll/_posts
sudo ln -s /media/yanplus/devel/ouestline-jekyllimages /srv/jekyll/dbyll/images
</code></pre></div></div>
<p>Structure du serveur <strong>static.ouestline.net</strong><br />
<code class="language-plaintext highlighter-rouge">tree -L 3 /srv</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/srv
├── jekyll
│ └── dbyll
│ ├── 404.html
│ ├── assets
│ ├── categories.html
│ ├── _config.yml
│ ├── favicon.png
│ ├── feed.xml
│ ├── Gemfile
│ ├── Gemfile.lock
│ ├── images -&gt; /media/yanplus/devel/ouestline-jekyllimages
│ ├── _includes
│ ├── index.html
│ ├── js
│ ├── _layouts
│ ├── LICENSE
│ ├── _posts -&gt; /media/yanplus/devel/ouestline-jekyll_posts
│ ├── README.md
│ ├── search.html
│ ├── _site
│ └── tags.html
└── start_jekyll.sh
</code></pre></div></div>
<h3 id="serveur-développement-develouestlinenet">Serveur développement devel.ouestline.net</h3>
<p><a href="https://devel.ouestline.net">https://devel.ouestline.net</a></p>
<p>Le dossier web <strong>/media/yanplus/devel/ouestline</strong><br />
Fichier de configuration <strong>/etc/nginx/conf.d/devel.ouestline.net.conf</strong></p>
<h2 id="nextcloud-yunohost">NextCloud Yunohost</h2>
<p>Installation de lapplication <strong>NextCloud</strong> (fork de OwnCloud)<br />
Ouvrir le Portail administration yanspm.com -&gt; Applications puis clic sur <strong>+Installer</strong><br />
Descendre en bas de la page , <strong>Installer une application personnalisée</strong> et saisir dans le champ <strong>URL</strong> :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>https://github.com/YunoHost-Apps/nextcloud_ynh
</code></pre></div></div>
<p>Puis cliquer sur <strong>Installer</strong></p>
<p>Libellé pour Nextcloud : <strong>NextCloud</strong><br />
Choisissez un domaine pour Nextcloud : <strong>yanspm.com</strong><br />
Choisissez un chemin pour Nextcloud : <strong>/nextcloud</strong><br />
Choisissez ladministrateur de Nextcloud : <strong>yanspm@yanspm.com</strong><br />
Accéder au dossier personnel des utilisateurs depuis Nextcloud ? : <strong>Valider</strong> loption</p>
<p>Puis cliquer sur <strong>Installer</strong><br />
Activer les applications <strong>Calendar</strong> <strong>Contacts</strong> et <strong>Notes</strong></p>
<blockquote>
<p>ATTENTION !!! Il faut réactiver les applications <strong>Calendar</strong> <strong>Contacts</strong> et <strong>Notes</strong> après une mise à jour de Nextcloud</p>
</blockquote>
<h3 id="droits-sur-le-dossier-homeyanspmscripts">Droits sur le dossier /home/yanspm/scripts</h3>
<p>Donner droits accès à lutilisateur debian <strong>$USER</strong> au dossier personnel <strong>nextcloud</strong> de lutilisateur <strong>yanspm</strong><br />
Ex: Nous avons créer sous nextcloud <strong>Home scripts</strong> dans le dossier personnel de <strong>yanspm</strong><br />
Dossier <strong>/home/yanspm/scripts</strong> avec les droits pour <strong>nextcloud</strong> :
<code class="language-plaintext highlighter-rouge">drwxr-xr-x 2 nextcloud nextcloud 4096 sept. 22 08:28 scripts</code><br />
Ajout de lutilisateur <strong>$USER</strong> au groupe <strong>nextcloud</strong><br />
<code class="language-plaintext highlighter-rouge">sudo usermod -aG nextcloud $USER</code><br />
Donner les droits en écriture au groupe dans le dossier <strong>/home/yanspm/scripts</strong><br />
<code class="language-plaintext highlighter-rouge">sudo chmod g+w /home/yanspm/scripts</code><br />
Se déconnecter puis se connecter pour la prise en compte</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
## Nginx compilé , http/2
Compilé nginx pour une version qui accepte le **http2**
Il faut interdire la mise à jour par dpkg ou apt des paquets nginx
Liste des paquets concernés
dpkg -l |grep nginx
</code></pre></div></div>
<p>root@xeuyakzas:/home/yak# sudo dpkg -l |grep nginx
ii nginx-common 1.6.2-5+deb8u4 all small, powerful, scalable web/proxy server - common files
ii nginx-extras 1.6.2-5+deb8u4 amd64 nginx web/proxy server (extended version)</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
Les paquets **nginx-common** et **nginx-extras** ne doivent pas faire l'objet d'une mise à jour
</code></pre></div></div>
<p>sudo -s
echo “nginx-common hold” | dpkg set-selections
echo “nginx-extras hold” | dpkg set-selections</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
Pour avoir la liste des paquets bloqués
dpkg --get-selections | grep hold
</code></pre></div></div>
<p>nginx-common hold
nginx-extras hold</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
[Compilation Nginx](post_url 2017-10-03-Serveur-web-nginx-PHP7 %})
Mise en place du nouveau binaire nginx
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo -s
# Arrêt et sauvegarde binaire nginx 1.6.2
systemctl stop nginx
mv /usr/sbin/nginx .
# copie du nouveau binaire nginx
cp /usr/share/nginx/sbin/nginx /usr/sbin/
# relance
systemctl start nginx
# Vérification version
nginx -V
nginx version: nginx/1.11.3 ```
</code></pre></div></div>
<h3 id="utiliser-le-protocole-http2">Utiliser le protocole HTTP/2</h3>
<p>HTTP/2 (supporté depuis la version nginx 1.9.5) va vous permettre de gagner du temps au niveau des états dattente car plusieurs ressources pourront être directement déchargées dans le même flux de réponse HTTP.<br />
Afin de vous assurer que HTTP/2 est bien activé sur votre serveur, je vous invite à taper <strong>nginx -V</strong> afin de vous assurer que vous disposiez bien de loption de compilation <strong>with-http_v2_module</strong> ainsi quà vérifier que votre version dOpenSSL utilisée par nginx est récente.</p>
<p>Il vous suffit simplement de spécifier dans lattribut listen que vous souhaitez utiliser : « http2 »</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
...
</code></pre></div></div>
<p>Les fichiers à modifier , <strong>/etc/nginx/conf.d/xeuyakzas.xyz.conf</strong> et <strong>/etc/nginx/conf.d/static.xeuyakzas.xyz.conf</strong><br />
Vérification et relance serveur nginx (en mode su)</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nginx -t
systemctl restart nginx
</code></pre></div></div>
<h3 id="désinstaller-gitlab">Désinstaller gitlab</h3>
<p>Pour supprimer une installation gitlab</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo systemctl stop gitlab-runsvdir.service
sudo systemctl disable gitlab-runsvdir.service
sudo gitlab-ctl uninstall
# Your config files have been backed up to /root/gitlab-cleanse-2017-06-07T21:55
sudo dpkg -r gitlab-ce
sudo rm -r /root/gitlab-cleanse-2017-06-07T21\:55/
sudo rm -r /opt/gitlab/
</code></pre></div></div>
<p>Redémarrer le serveur</p>
</div>
<div class="d-print-none"><footer class="article__footer"><meta itemprop="dateModified" content="2018-11-23T00: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>
<div class="article__section-navigator clearfix"><div class="previous"><span>PRÉCÉDENT</span><a href="/2018/11/23/serveur-de-messagerie-complet-sur-debian-avec-iRedMail.html">KVM Debian Stretch serveur de messagerie complet avec iRedMail</a></div><div class="next"><span>SUIVANT</span><a href="/2018/11/23/xeuyakzas.xyz.html">xeuyakzas.xyz (VPS austria)</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}&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>