2597 lines
224 KiB
HTML
2597 lines
224 KiB
HTML
|
<!DOCTYPE html><html lang="fr">
|
|||
|
<head><meta charset="utf-8">
|
|||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|||
|
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"><title>Rsync via SSH et systemd Timer - YannStatic</title>
|
|||
|
|
|||
|
<meta name="description" content="Rsync">
|
|||
|
<link rel="canonical" href="https://static.rnmkcy.eu/2019/12/13/Rsync.html"><link rel="alternate" type="application/rss+xml" title="YannStatic" href="/feed.xml">
|
|||
|
|
|||
|
<!-- - include head/favicon.html - -->
|
|||
|
<link rel="shortcut icon" type="image/png" href="/assets/favicon/favicon.png"><link rel="stylesheet" href="/assets/css/main.css"><link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.13/css/all.css" ><!-- start custom head snippets --><link rel="stylesheet" href="/assets/css/expand.css">
|
|||
|
<!-- end custom head snippets --><script>(function() {
|
|||
|
window.isArray = function(val) {
|
|||
|
return Object.prototype.toString.call(val) === '[object Array]';
|
|||
|
};
|
|||
|
window.isString = function(val) {
|
|||
|
return typeof val === 'string';
|
|||
|
};
|
|||
|
|
|||
|
window.hasEvent = function(event) {
|
|||
|
return 'on'.concat(event) in window.document;
|
|||
|
};
|
|||
|
|
|||
|
window.isOverallScroller = function(node) {
|
|||
|
return node === document.documentElement || node === document.body || node === window;
|
|||
|
};
|
|||
|
|
|||
|
window.isFormElement = function(node) {
|
|||
|
var tagName = node.tagName;
|
|||
|
return tagName === 'INPUT' || tagName === 'SELECT' || tagName === 'TEXTAREA';
|
|||
|
};
|
|||
|
|
|||
|
window.pageLoad = (function () {
|
|||
|
var loaded = false, cbs = [];
|
|||
|
window.addEventListener('load', function () {
|
|||
|
var i;
|
|||
|
loaded = true;
|
|||
|
if (cbs.length > 0) {
|
|||
|
for (i = 0; i < cbs.length; i++) {
|
|||
|
cbs[i]();
|
|||
|
}
|
|||
|
}
|
|||
|
});
|
|||
|
return {
|
|||
|
then: function(cb) {
|
|||
|
cb && (loaded ? cb() : (cbs.push(cb)));
|
|||
|
}
|
|||
|
};
|
|||
|
})();
|
|||
|
})();
|
|||
|
(function() {
|
|||
|
window.throttle = function(func, wait) {
|
|||
|
var args, result, thisArg, timeoutId, lastCalled = 0;
|
|||
|
|
|||
|
function trailingCall() {
|
|||
|
lastCalled = new Date;
|
|||
|
timeoutId = null;
|
|||
|
result = func.apply(thisArg, args);
|
|||
|
}
|
|||
|
return function() {
|
|||
|
var now = new Date,
|
|||
|
remaining = wait - (now - lastCalled);
|
|||
|
|
|||
|
args = arguments;
|
|||
|
thisArg = this;
|
|||
|
|
|||
|
if (remaining <= 0) {
|
|||
|
clearTimeout(timeoutId);
|
|||
|
timeoutId = null;
|
|||
|
lastCalled = now;
|
|||
|
result = func.apply(thisArg, args);
|
|||
|
} else if (!timeoutId) {
|
|||
|
timeoutId = setTimeout(trailingCall, remaining);
|
|||
|
}
|
|||
|
return result;
|
|||
|
};
|
|||
|
};
|
|||
|
})();
|
|||
|
(function() {
|
|||
|
var Set = (function() {
|
|||
|
var add = function(item) {
|
|||
|
var i, data = this._data;
|
|||
|
for (i = 0; i < data.length; i++) {
|
|||
|
if (data[i] === item) {
|
|||
|
return;
|
|||
|
}
|
|||
|
}
|
|||
|
this.size ++;
|
|||
|
data.push(item);
|
|||
|
return data;
|
|||
|
};
|
|||
|
|
|||
|
var Set = function(data) {
|
|||
|
this.size = 0;
|
|||
|
this._data = [];
|
|||
|
var i;
|
|||
|
if (data.length > 0) {
|
|||
|
for (i = 0; i < data.length; i++) {
|
|||
|
add.call(this, data[i]);
|
|||
|
}
|
|||
|
}
|
|||
|
};
|
|||
|
Set.prototype.add = add;
|
|||
|
Set.prototype.get = function(index) { return this._data[index]; };
|
|||
|
Set.prototype.has = function(item) {
|
|||
|
var i, data = this._data;
|
|||
|
for (i = 0; i < data.length; i++) {
|
|||
|
if (this.get(i) === item) {
|
|||
|
return true;
|
|||
|
}
|
|||
|
}
|
|||
|
return false;
|
|||
|
};
|
|||
|
Set.prototype.is = function(map) {
|
|||
|
if (map._data.length !== this._data.length) { return false; }
|
|||
|
var i, j, flag, tData = this._data, mData = map._data;
|
|||
|
for (i = 0; i < tData.length; i++) {
|
|||
|
for (flag = false, j = 0; j < mData.length; j++) {
|
|||
|
if (tData[i] === mData[j]) {
|
|||
|
flag = true;
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
if (!flag) { return false; }
|
|||
|
}
|
|||
|
return true;
|
|||
|
};
|
|||
|
Set.prototype.values = function() {
|
|||
|
return this._data;
|
|||
|
};
|
|||
|
return Set;
|
|||
|
})();
|
|||
|
|
|||
|
window.Lazyload = (function(doc) {
|
|||
|
var queue = {js: [], css: []}, sources = {js: {}, css: {}}, context = this;
|
|||
|
var createNode = function(name, attrs) {
|
|||
|
var node = doc.createElement(name), attr;
|
|||
|
for (attr in attrs) {
|
|||
|
if (attrs.hasOwnProperty(attr)) {
|
|||
|
node.setAttribute(attr, attrs[attr]);
|
|||
|
}
|
|||
|
}
|
|||
|
return node;
|
|||
|
};
|
|||
|
var end = function(type, url) {
|
|||
|
var s, q, qi, cbs, i, j, cur, val, flag;
|
|||
|
if (type === 'js' || type ==='css') {
|
|||
|
s = sources[type], q = queue[type];
|
|||
|
s[url] = true;
|
|||
|
for (i = 0; i < q.length; i++) {
|
|||
|
cur = q[i];
|
|||
|
if (cur.urls.has(url)) {
|
|||
|
qi = cur, val = qi.urls.values();
|
|||
|
qi && (cbs = qi.callbacks);
|
|||
|
for (flag = true, j = 0; j < val.length; j++) {
|
|||
|
cur = val[j];
|
|||
|
if (!s[cur]) {
|
|||
|
flag = false;
|
|||
|
}
|
|||
|
}
|
|||
|
if (flag && cbs && cbs.length > 0) {
|
|||
|
for (j = 0; j < cbs.length; j++) {
|
|||
|
cbs[j].call(context);
|
|||
|
}
|
|||
|
qi.load = true;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
};
|
|||
|
var load = function(type, urls, callback) {
|
|||
|
var s, q, qi, node, i, cur,
|
|||
|
_urls = typeof urls === 'string' ? new Set([urls]) : new Set(urls), val, url;
|
|||
|
if (type === 'js' || type ==='css') {
|
|||
|
s = sources[type], q = queue[type];
|
|||
|
for (i = 0; i < q.length; i++) {
|
|||
|
cur = q[i];
|
|||
|
if (_urls.is(cur.urls)) {
|
|||
|
qi = cur;
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
val = _urls.values();
|
|||
|
if (qi) {
|
|||
|
callback && (qi.load || qi.callbacks.push(callback));
|
|||
|
callback && (qi.load && callback());
|
|||
|
} else {
|
|||
|
q.push({
|
|||
|
urls: _urls,
|
|||
|
callbacks: callback ? [callback] : [],
|
|||
|
load: false
|
|||
|
});
|
|||
|
for (i = 0; i < val.length; i++) {
|
|||
|
node = null, url = val[i];
|
|||
|
if (s[url] === undefined) {
|
|||
|
(type === 'js' ) && (node = createNode('script', { src: url }));
|
|||
|
(type === 'css') && (node = createNode('link', { rel: 'stylesheet', href: url }));
|
|||
|
if (node) {
|
|||
|
node.onload = (function(type, url) {
|
|||
|
return function() {
|
|||
|
end(type, url);
|
|||
|
};
|
|||
|
})(type, url);
|
|||
|
(doc.head || doc.body).appendChild(node);
|
|||
|
s[url] = false;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
};
|
|||
|
return {
|
|||
|
js: function(url, callback) {
|
|||
|
load('js', url, callback);
|
|||
|
},
|
|||
|
css: function(url, callback) {
|
|||
|
load('css', url, callback);
|
|||
|
}
|
|||
|
};
|
|||
|
})(this.document);
|
|||
|
})();
|
|||
|
</script><script>
|
|||
|
(function() {
|
|||
|
var TEXT_VARIABLES = {
|
|||
|
version: '2.2.6',
|
|||
|
sources: {
|
|||
|
font_awesome: 'https://use.fontawesome.com/releases/v5.0.13/css/all.css',
|
|||
|
jquery: '/assets/js/jquery.min.js',
|
|||
|
leancloud_js_sdk: '//cdn.jsdelivr.net/npm/leancloud-storage@3.13.2/dist/av-min.js',
|
|||
|
chart: 'https://cdn.bootcss.com/Chart.js/2.7.2/Chart.bundle.min.js',
|
|||
|
gitalk: {
|
|||
|
js: 'https://cdn.bootcss.com/gitalk/1.2.2/gitalk.min.js',
|
|||
|
css: 'https://cdn.bootcss.com/gitalk/1.2.2/gitalk.min.css'
|
|||
|
},
|
|||
|
valine: 'https://unpkg.com/valine/dist/Valine.min.js'
|
|||
|
},
|
|||
|
site: {
|
|||
|
toc: {
|
|||
|
selectors: 'h1,h2,h3'
|
|||
|
}
|
|||
|
},
|
|||
|
paths: {
|
|||
|
search_js: '/assets/search.js'
|
|||
|
}
|
|||
|
};
|
|||
|
window.TEXT_VARIABLES = TEXT_VARIABLES;
|
|||
|
})();
|
|||
|
</script>
|
|||
|
</head>
|
|||
|
<body>
|
|||
|
<div class="root" data-is-touch="false">
|
|||
|
<div class="layout--page js-page-root"><!----><div class="page__main js-page-main page__viewport hide-footer has-aside has-aside cell cell--auto">
|
|||
|
|
|||
|
<div class="page__main-inner"><div class="page__header d-print-none"><header class="header"><div class="main">
|
|||
|
<div class="header__title">
|
|||
|
<div class="header__brand"><svg id="svg" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="400" height="478.9473684210526" viewBox="0, 0, 400,478.9473684210526"><g id="svgg"><path id="path0" d="M308.400 56.805 C 306.970 56.966,303.280 57.385,300.200 57.738 C 290.906 58.803,278.299 59.676,269.200 59.887 L 260.600 60.085 259.400 61.171 C 258.010 62.428,256.198 63.600,255.645 63.600 C 255.070 63.600,252.887 65.897,252.598 66.806 C 252.460 67.243,252.206 67.600,252.034 67.600 C 251.397 67.600,247.206 71.509,247.202 72.107 C 247.201 72.275,246.390 73.190,245.400 74.138 C 243.961 75.517,243.598 76.137,243.592 77.231 C 243.579 79.293,241.785 83.966,240.470 85.364 C 239.176 86.740,238.522 88.365,237.991 91.521 C 237.631 93.665,236.114 97.200,235.554 97.200 C 234.938 97.200,232.737 102.354,232.450 104.472 C 232.158 106.625,230.879 109.226,229.535 110.400 C 228.933 110.926,228.171 113.162,226.434 119.500 C 226.178 120.435,225.795 121.200,225.584 121.200 C 225.373 121.200,225.200 121.476,225.200 121.813 C 225.200 122.149,224.885 122.541,224.500 122.683 C 223.606 123.013,223.214 123.593,223.204 124.600 C 223.183 126.555,220.763 132.911,219.410 134.562 C 218.443 135.742,217.876 136.956,217.599 138.440 C 217.041 141.424,215.177 146.434,214.532 146.681 C 214.240 146.794,214.000 147.055,214.000 147.261 C 214.000 147.467,213.550 148.086,213.000 148.636 C 212.450 149.186,212.000 149.893,212.000 150.208 C 212.000 151.386,208.441 154.450,207.597 153.998 C 206.319 153.315,204.913 150.379,204.633 147.811 C 204.365 145.357,202.848 142.147,201.759 141.729 C 200.967 141.425,199.200 137.451,199.200 135.974 C 199.200 134.629,198.435 133.224,196.660 131.311 C 195.363 129.913,194.572 128.123,193.870 125.000 C 193.623 123.900,193.236 122.793,193.010 122.540 C 190.863 120.133,190.147 118.880,188.978 115.481 C 188.100 112.928,187.151 111.003,186.254 109.955 C 185.358 108.908,184.518 107.204,183.847 105.073 C 183.280 103.273,182.497 101.329,182.108 100.753 C 181.719 100.177,180.904 98.997,180.298 98.131 C 179.693 97.265,178.939 95.576,178.624 94.378 C 178.041 92.159,177.125 90.326,175.023 87.168 C 174.375 86.196,173.619 84.539,173.342 83.486 C 172.800 81.429,171.529 79.567,170.131 78.785 C 169.654 78.517,168.697 77.511,168.006 76.549 C 167.316 75.587,166.594 74.800,166.402 74.800 C 166.210 74.800,164.869 73.633,163.421 72.206 C 160.103 68.936,161.107 69.109,146.550 69.301 C 133.437 69.474,128.581 70.162,126.618 72.124 C 126.248 72.495,125.462 72.904,124.872 73.033 C 124.282 73.163,123.088 73.536,122.219 73.863 C 121.349 74.191,119.028 74.638,117.061 74.858 C 113.514 75.254,109.970 76.350,108.782 77.419 C 107.652 78.436,100.146 80.400,97.388 80.400 C 95.775 80.400,93.167 81.360,91.200 82.679 C 90.430 83.195,89.113 83.804,88.274 84.031 C 85.875 84.681,78.799 90.910,74.400 96.243 L 73.400 97.456 73.455 106.028 C 73.526 117.055,74.527 121.238,77.820 124.263 C 78.919 125.273,80.400 127.902,80.400 128.842 C 80.400 129.202,81.075 130.256,81.900 131.186 C 83.563 133.059,85.497 136.346,86.039 138.216 C 86.233 138.886,87.203 140.207,88.196 141.153 C 89.188 142.098,90.000 143.104,90.000 143.388 C 90.000 144.337,92.129 148.594,92.869 149.123 C 93.271 149.410,93.600 149.831,93.600 150.059 C 93.600 150.286,93.932 150.771,94.337 151.136 C 94.743 151.501,95.598 153.004,96.237 154.475 C 96.877 155.947,97.760 157.351,98.200 157.596 C 98.640 157.841,99.900 159.943,101.000 162.267 C 102.207 164.817,103.327 166.644,103.825 166.876 C 104.278 167.087,105.065 168.101,105.573 169.130 C 107.658 173.348,108.097 174.093,110.006 176.647 C 111.103 178.114,112.000 179.725,112.000 180.227 C 112.000 181.048,113.425 183.163,114.678 184.200 C 115.295 184.711,117.396 188.733,117.720 190.022 C 117.855 190.562,118.603 191.633,119.381 192.402 C 120.160 193.171,121.496 195.258,122.351 197.039 C 123.206 198.820,124.167 200.378,124.487 200.501 C 124.807 200.624,125.953 202.496,127.034 204.662 C 128.114 206.828,129.676 209.299,130.505 210.153 C 131.333 211.007,132.124 212.177,132.262 212.753 C 132.618 214.239,134.291 217.048,136.288 219.5
|
|||
|
" href="/">YannStatic</a></div><!--<button class="button button--secondary button--circle search-button js-search-toggle"><i class="fas fa-search"></i></button>--><!-- <li><button class="button button--secondary button--circle search-button js-search-toggle"><i class="fas fa-search"></i></button></li> -->
|
|||
|
<!-- Champ de recherche -->
|
|||
|
<div id="searchbox" class="search search--dark" style="visibility: visible">
|
|||
|
<div class="main">
|
|||
|
<div class="search__header"></div>
|
|||
|
<div class="search-bar">
|
|||
|
<div class="search-box js-search-box">
|
|||
|
<div class="search-box__icon-search"><i class="fas fa-search"></i></div>
|
|||
|
<input id="search-input" type="text" />
|
|||
|
<!-- <div class="search-box__icon-clear js-icon-clear">
|
|||
|
<a><i class="fas fa-times"></i></a>
|
|||
|
</div> -->
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
<!-- Script pointing to search-script.js -->
|
|||
|
<script>/*!
|
|||
|
* Simple-Jekyll-Search
|
|||
|
* Copyright 2015-2020, Christian Fei
|
|||
|
* Licensed under the MIT License.
|
|||
|
*/
|
|||
|
|
|||
|
(function(){
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$Templater_7 = {
|
|||
|
compile: compile,
|
|||
|
setOptions: setOptions
|
|||
|
}
|
|||
|
|
|||
|
const options = {}
|
|||
|
options.pattern = /\{(.*?)\}/g
|
|||
|
options.template = ''
|
|||
|
options.middleware = function () {}
|
|||
|
|
|||
|
function setOptions (_options) {
|
|||
|
options.pattern = _options.pattern || options.pattern
|
|||
|
options.template = _options.template || options.template
|
|||
|
if (typeof _options.middleware === 'function') {
|
|||
|
options.middleware = _options.middleware
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function compile (data) {
|
|||
|
return options.template.replace(options.pattern, function (match, prop) {
|
|||
|
const value = options.middleware(prop, data[prop], options.template)
|
|||
|
if (typeof value !== 'undefined') {
|
|||
|
return value
|
|||
|
}
|
|||
|
return data[prop] || match
|
|||
|
})
|
|||
|
}
|
|||
|
|
|||
|
'use strict';
|
|||
|
|
|||
|
function fuzzysearch (needle, haystack) {
|
|||
|
var tlen = haystack.length;
|
|||
|
var qlen = needle.length;
|
|||
|
if (qlen > tlen) {
|
|||
|
return false;
|
|||
|
}
|
|||
|
if (qlen === tlen) {
|
|||
|
return needle === haystack;
|
|||
|
}
|
|||
|
outer: for (var i = 0, j = 0; i < qlen; i++) {
|
|||
|
var nch = needle.charCodeAt(i);
|
|||
|
while (j < tlen) {
|
|||
|
if (haystack.charCodeAt(j++) === nch) {
|
|||
|
continue outer;
|
|||
|
}
|
|||
|
}
|
|||
|
return false;
|
|||
|
}
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
var _$fuzzysearch_1 = fuzzysearch;
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
/* removed: const _$fuzzysearch_1 = require('fuzzysearch') */;
|
|||
|
|
|||
|
var _$FuzzySearchStrategy_5 = new FuzzySearchStrategy()
|
|||
|
|
|||
|
function FuzzySearchStrategy () {
|
|||
|
this.matches = function (string, crit) {
|
|||
|
return _$fuzzysearch_1(crit.toLowerCase(), string.toLowerCase())
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$LiteralSearchStrategy_6 = new LiteralSearchStrategy()
|
|||
|
|
|||
|
function LiteralSearchStrategy () {
|
|||
|
this.matches = function (str, crit) {
|
|||
|
if (!str) return false
|
|||
|
|
|||
|
str = str.trim().toLowerCase()
|
|||
|
crit = crit.trim().toLowerCase()
|
|||
|
|
|||
|
return crit.split(' ').filter(function (word) {
|
|||
|
return str.indexOf(word) >= 0
|
|||
|
}).length === crit.split(' ').length
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$Repository_4 = {
|
|||
|
put: put,
|
|||
|
clear: clear,
|
|||
|
search: search,
|
|||
|
setOptions: __setOptions_4
|
|||
|
}
|
|||
|
|
|||
|
/* removed: const _$FuzzySearchStrategy_5 = require('./SearchStrategies/FuzzySearchStrategy') */;
|
|||
|
/* removed: const _$LiteralSearchStrategy_6 = require('./SearchStrategies/LiteralSearchStrategy') */;
|
|||
|
|
|||
|
function NoSort () {
|
|||
|
return 0
|
|||
|
}
|
|||
|
|
|||
|
const data = []
|
|||
|
let opt = {}
|
|||
|
|
|||
|
opt.fuzzy = false
|
|||
|
opt.limit = 10
|
|||
|
opt.searchStrategy = opt.fuzzy ? _$FuzzySearchStrategy_5 : _$LiteralSearchStrategy_6
|
|||
|
opt.sort = NoSort
|
|||
|
opt.exclude = []
|
|||
|
|
|||
|
function put (data) {
|
|||
|
if (isObject(data)) {
|
|||
|
return addObject(data)
|
|||
|
}
|
|||
|
if (isArray(data)) {
|
|||
|
return addArray(data)
|
|||
|
}
|
|||
|
return undefined
|
|||
|
}
|
|||
|
function clear () {
|
|||
|
data.length = 0
|
|||
|
return data
|
|||
|
}
|
|||
|
|
|||
|
function isObject (obj) {
|
|||
|
return Boolean(obj) && Object.prototype.toString.call(obj) === '[object Object]'
|
|||
|
}
|
|||
|
|
|||
|
function isArray (obj) {
|
|||
|
return Boolean(obj) && Object.prototype.toString.call(obj) === '[object Array]'
|
|||
|
}
|
|||
|
|
|||
|
function addObject (_data) {
|
|||
|
data.push(_data)
|
|||
|
return data
|
|||
|
}
|
|||
|
|
|||
|
function addArray (_data) {
|
|||
|
const added = []
|
|||
|
clear()
|
|||
|
for (let i = 0, len = _data.length; i < len; i++) {
|
|||
|
if (isObject(_data[i])) {
|
|||
|
added.push(addObject(_data[i]))
|
|||
|
}
|
|||
|
}
|
|||
|
return added
|
|||
|
}
|
|||
|
|
|||
|
function search (crit) {
|
|||
|
if (!crit) {
|
|||
|
return []
|
|||
|
}
|
|||
|
return findMatches(data, crit, opt.searchStrategy, opt).sort(opt.sort)
|
|||
|
}
|
|||
|
|
|||
|
function __setOptions_4 (_opt) {
|
|||
|
opt = _opt || {}
|
|||
|
|
|||
|
opt.fuzzy = _opt.fuzzy || false
|
|||
|
opt.limit = _opt.limit || 10
|
|||
|
opt.searchStrategy = _opt.fuzzy ? _$FuzzySearchStrategy_5 : _$LiteralSearchStrategy_6
|
|||
|
opt.sort = _opt.sort || NoSort
|
|||
|
opt.exclude = _opt.exclude || []
|
|||
|
}
|
|||
|
|
|||
|
function findMatches (data, crit, strategy, opt) {
|
|||
|
const matches = []
|
|||
|
for (let i = 0; i < data.length && matches.length < opt.limit; i++) {
|
|||
|
const match = findMatchesInObject(data[i], crit, strategy, opt)
|
|||
|
if (match) {
|
|||
|
matches.push(match)
|
|||
|
}
|
|||
|
}
|
|||
|
return matches
|
|||
|
}
|
|||
|
|
|||
|
function findMatchesInObject (obj, crit, strategy, opt) {
|
|||
|
for (const key in obj) {
|
|||
|
if (!isExcluded(obj[key], opt.exclude) && strategy.matches(obj[key], crit)) {
|
|||
|
return obj
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function isExcluded (term, excludedTerms) {
|
|||
|
for (let i = 0, len = excludedTerms.length; i < len; i++) {
|
|||
|
const excludedTerm = excludedTerms[i]
|
|||
|
if (new RegExp(excludedTerm).test(term)) {
|
|||
|
return true
|
|||
|
}
|
|||
|
}
|
|||
|
return false
|
|||
|
}
|
|||
|
|
|||
|
/* globals ActiveXObject:false */
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$JSONLoader_2 = {
|
|||
|
load: load
|
|||
|
}
|
|||
|
|
|||
|
function load (location, callback) {
|
|||
|
const xhr = getXHR()
|
|||
|
xhr.open('GET', location, true)
|
|||
|
xhr.onreadystatechange = createStateChangeListener(xhr, callback)
|
|||
|
xhr.send()
|
|||
|
}
|
|||
|
|
|||
|
function createStateChangeListener (xhr, callback) {
|
|||
|
return function () {
|
|||
|
if (xhr.readyState === 4 && xhr.status === 200) {
|
|||
|
try {
|
|||
|
callback(null, JSON.parse(xhr.responseText))
|
|||
|
} catch (err) {
|
|||
|
callback(err, null)
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function getXHR () {
|
|||
|
return window.XMLHttpRequest ? new window.XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP')
|
|||
|
}
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$OptionsValidator_3 = function OptionsValidator (params) {
|
|||
|
if (!validateParams(params)) {
|
|||
|
throw new Error('-- OptionsValidator: required options missing')
|
|||
|
}
|
|||
|
|
|||
|
if (!(this instanceof OptionsValidator)) {
|
|||
|
return new OptionsValidator(params)
|
|||
|
}
|
|||
|
|
|||
|
const requiredOptions = params.required
|
|||
|
|
|||
|
this.getRequiredOptions = function () {
|
|||
|
return requiredOptions
|
|||
|
}
|
|||
|
|
|||
|
this.validate = function (parameters) {
|
|||
|
const errors = []
|
|||
|
requiredOptions.forEach(function (requiredOptionName) {
|
|||
|
if (typeof parameters[requiredOptionName] === 'undefined') {
|
|||
|
errors.push(requiredOptionName)
|
|||
|
}
|
|||
|
})
|
|||
|
return errors
|
|||
|
}
|
|||
|
|
|||
|
function validateParams (params) {
|
|||
|
if (!params) {
|
|||
|
return false
|
|||
|
}
|
|||
|
return typeof params.required !== 'undefined' && params.required instanceof Array
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$utils_9 = {
|
|||
|
merge: merge,
|
|||
|
isJSON: isJSON
|
|||
|
}
|
|||
|
|
|||
|
function merge (defaultParams, mergeParams) {
|
|||
|
const mergedOptions = {}
|
|||
|
for (const option in defaultParams) {
|
|||
|
mergedOptions[option] = defaultParams[option]
|
|||
|
if (typeof mergeParams[option] !== 'undefined') {
|
|||
|
mergedOptions[option] = mergeParams[option]
|
|||
|
}
|
|||
|
}
|
|||
|
return mergedOptions
|
|||
|
}
|
|||
|
|
|||
|
function isJSON (json) {
|
|||
|
try {
|
|||
|
if (json instanceof Object && JSON.parse(JSON.stringify(json))) {
|
|||
|
return true
|
|||
|
}
|
|||
|
return false
|
|||
|
} catch (err) {
|
|||
|
return false
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
var _$src_8 = {};
|
|||
|
(function (window) {
|
|||
|
'use strict'
|
|||
|
|
|||
|
let options = {
|
|||
|
searchInput: null,
|
|||
|
resultsContainer: null,
|
|||
|
json: [],
|
|||
|
success: Function.prototype,
|
|||
|
searchResultTemplate: '<li><a href="{url}" title="{desc}">{title}</a></li>',
|
|||
|
templateMiddleware: Function.prototype,
|
|||
|
sortMiddleware: function () {
|
|||
|
return 0
|
|||
|
},
|
|||
|
noResultsText: 'No results found',
|
|||
|
limit: 10,
|
|||
|
fuzzy: false,
|
|||
|
debounceTime: null,
|
|||
|
exclude: []
|
|||
|
}
|
|||
|
|
|||
|
let debounceTimerHandle
|
|||
|
const debounce = function (func, delayMillis) {
|
|||
|
if (delayMillis) {
|
|||
|
clearTimeout(debounceTimerHandle)
|
|||
|
debounceTimerHandle = setTimeout(func, delayMillis)
|
|||
|
} else {
|
|||
|
func.call()
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
const requiredOptions = ['searchInput', 'resultsContainer', 'json']
|
|||
|
|
|||
|
/* removed: const _$Templater_7 = require('./Templater') */;
|
|||
|
/* removed: const _$Repository_4 = require('./Repository') */;
|
|||
|
/* removed: const _$JSONLoader_2 = require('./JSONLoader') */;
|
|||
|
const optionsValidator = _$OptionsValidator_3({
|
|||
|
required: requiredOptions
|
|||
|
})
|
|||
|
/* removed: const _$utils_9 = require('./utils') */;
|
|||
|
|
|||
|
window.SimpleJekyllSearch = function (_options) {
|
|||
|
const errors = optionsValidator.validate(_options)
|
|||
|
if (errors.length > 0) {
|
|||
|
throwError('You must specify the following required options: ' + requiredOptions)
|
|||
|
}
|
|||
|
|
|||
|
options = _$utils_9.merge(options, _options)
|
|||
|
|
|||
|
_$Templater_7.setOptions({
|
|||
|
template: options.searchResultTemplate,
|
|||
|
middleware: options.templateMiddleware
|
|||
|
})
|
|||
|
|
|||
|
_$Repository_4.setOptions({
|
|||
|
fuzzy: options.fuzzy,
|
|||
|
limit: options.limit,
|
|||
|
sort: options.sortMiddleware,
|
|||
|
exclude: options.exclude
|
|||
|
})
|
|||
|
|
|||
|
if (_$utils_9.isJSON(options.json)) {
|
|||
|
initWithJSON(options.json)
|
|||
|
} else {
|
|||
|
initWithURL(options.json)
|
|||
|
}
|
|||
|
|
|||
|
const rv = {
|
|||
|
search: search
|
|||
|
}
|
|||
|
|
|||
|
typeof options.success === 'function' && options.success.call(rv)
|
|||
|
return rv
|
|||
|
}
|
|||
|
|
|||
|
function initWithJSON (json) {
|
|||
|
_$Repository_4.put(json)
|
|||
|
registerInput()
|
|||
|
}
|
|||
|
|
|||
|
function initWithURL (url) {
|
|||
|
_$JSONLoader_2.load(url, function (err, json) {
|
|||
|
if (err) {
|
|||
|
throwError('failed to get JSON (' + url + ')')
|
|||
|
}
|
|||
|
initWithJSON(json)
|
|||
|
})
|
|||
|
}
|
|||
|
|
|||
|
function emptyResultsContainer () {
|
|||
|
options.resultsContainer.innerHTML = ''
|
|||
|
}
|
|||
|
|
|||
|
function appendToResultsContainer (text) {
|
|||
|
options.resultsContainer.innerHTML += text
|
|||
|
}
|
|||
|
|
|||
|
function registerInput () {
|
|||
|
options.searchInput.addEventListener('input', function (e) {
|
|||
|
if (isWhitelistedKey(e.which)) {
|
|||
|
emptyResultsContainer()
|
|||
|
debounce(function () { search(e.target.value) }, options.debounceTime)
|
|||
|
}
|
|||
|
})
|
|||
|
}
|
|||
|
|
|||
|
function search (query) {
|
|||
|
if (isValidQuery(query)) {
|
|||
|
emptyResultsContainer()
|
|||
|
render(_$Repository_4.search(query), query)
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function render (results, query) {
|
|||
|
const len = results.length
|
|||
|
if (len === 0) {
|
|||
|
return appendToResultsContainer(options.noResultsText)
|
|||
|
}
|
|||
|
for (let i = 0; i < len; i++) {
|
|||
|
results[i].query = query
|
|||
|
appendToResultsContainer(_$Templater_7.compile(results[i]))
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function isValidQuery (query) {
|
|||
|
return query && query.length > 0
|
|||
|
}
|
|||
|
|
|||
|
function isWhitelistedKey (key) {
|
|||
|
return [13, 16, 20, 37, 38, 39, 40, 91].indexOf(key) === -1
|
|||
|
}
|
|||
|
|
|||
|
function throwError (message) {
|
|||
|
throw new Error('SimpleJekyllSearch --- ' + message)
|
|||
|
}
|
|||
|
})(window)
|
|||
|
|
|||
|
}());
|
|||
|
</script>
|
|||
|
|
|||
|
<!-- Configuration -->
|
|||
|
<script>
|
|||
|
SimpleJekyllSearch({
|
|||
|
searchInput: document.getElementById('search-input'),
|
|||
|
resultsContainer: document.getElementById('results-container'),
|
|||
|
json: '/search.json',
|
|||
|
//searchResultTemplate: '<li><a href="https://static.rnmkcy.eu{url}">{date} {title}</a></li>'
|
|||
|
searchResultTemplate: '<li><a href="{url}">{date} {title}</a></li>'
|
|||
|
})
|
|||
|
</script>
|
|||
|
<!-- Fin déclaration champ de recherche --></div><nav class="navigation">
|
|||
|
<ul><li class="navigation__item"><a href="/archive.html">Etiquettes</a></li><li class="navigation__item"><a href="/htmldoc.html">Documents</a></li><li class="navigation__item"><a href="/liens_ttrss.html">Liens</a></li><li class="navigation__item"><a href="/aide-jekyll-text-theme.html">Aide</a></li></ul>
|
|||
|
</nav></div>
|
|||
|
</header>
|
|||
|
|
|||
|
</div><div class="page__content"><div class ="main"><div class="grid grid--reverse">
|
|||
|
<div class="col-main cell cell--auto"><!-- start custom main top snippet --><div id="results-container" class="search-result js-search-result"></div><!-- end custom main top snippet -->
|
|||
|
<article itemscope itemtype="http://schema.org/Article"><div class="article__header"><header><h1 style="color:Tomato;">Rsync via SSH et systemd Timer</h1></header></div><meta itemprop="headline" content="Rsync via SSH et systemd Timer"><div class="article__info clearfix"><ul class="left-col menu"><li>
|
|||
|
<a class="button button--secondary button--pill button--sm"
|
|||
|
href="/archive.html?tag=outils">outils</a>
|
|||
|
</li><li>
|
|||
|
<a class="button button--secondary button--pill button--sm"
|
|||
|
href="/archive.html?tag=ssh">ssh</a>
|
|||
|
</li></ul><ul class="right-col menu"><li>
|
|||
|
<i class="far fa-calendar-alt"></i> <span title="Création" style="color:#FF00FF">13 déc. 2019</span>
|
|||
|
|
|||
|
<span title="Modification" style="color:#00FF7F">20 nov. 2020</span></li></ul></div><meta itemprop="datePublished" content="2020-11-20T00:00:00+01:00">
|
|||
|
<meta itemprop="keywords" content="outils,ssh"><div class="js-article-content">
|
|||
|
<div class="layout--article"><!-- start custom article top snippet -->
|
|||
|
<style>
|
|||
|
#myBtn {
|
|||
|
display: none;
|
|||
|
position: fixed;
|
|||
|
bottom: 10px;
|
|||
|
right: 10px;
|
|||
|
z-index: 99;
|
|||
|
font-size: 12px;
|
|||
|
font-weight: bold;
|
|||
|
border: none;
|
|||
|
outline: none;
|
|||
|
background-color: white;
|
|||
|
color: black;
|
|||
|
cursor: pointer;
|
|||
|
padding: 5px;
|
|||
|
border-radius: 4px;
|
|||
|
}
|
|||
|
|
|||
|
#myBtn:hover {
|
|||
|
background-color: #555;
|
|||
|
}
|
|||
|
</style>
|
|||
|
|
|||
|
<button onclick="topFunction()" id="myBtn" title="Haut de page">⇧</button>
|
|||
|
|
|||
|
<script>
|
|||
|
//Get the button
|
|||
|
var mybutton = document.getElementById("myBtn");
|
|||
|
|
|||
|
// When the user scrolls down 20px from the top of the document, show the button
|
|||
|
window.onscroll = function() {scrollFunction()};
|
|||
|
|
|||
|
function scrollFunction() {
|
|||
|
if (document.body.scrollTop > 20 || document.documentElement.scrollTop > 20) {
|
|||
|
mybutton.style.display = "block";
|
|||
|
} else {
|
|||
|
mybutton.style.display = "none";
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// When the user clicks on the button, scroll to the top of the document
|
|||
|
function topFunction() {
|
|||
|
document.body.scrollTop = 0;
|
|||
|
document.documentElement.scrollTop = 0;
|
|||
|
}
|
|||
|
</script>
|
|||
|
|
|||
|
|
|||
|
<!-- end custom article top snippet -->
|
|||
|
<div class="article__content" itemprop="articleBody"><details>
|
|||
|
<summary><b>Afficher/cacher Sommaire</b></summary>
|
|||
|
<!-- affichage sommaire -->
|
|||
|
<div class="toc-aside js-toc-root"></div>
|
|||
|
</details><h1 id="rsync">Rsync</h1>
|
|||
|
|
|||
|
<p><img src="/images/rsynca.png" alt="rsync" /><br />
|
|||
|
<em>rsync (pour <strong>r</strong>emote <strong>sync</strong>hronization ou synchronisation à distance), est un logiciel de synchronisation de fichiers.<br />
|
|||
|
Il est fréquemment utilisé pour mettre en place des systèmes de sauvegarde distante.
|
|||
|
rsync travaille de manière unidirectionnelle c’est-à-dire qu’il synchronise, copie ou actualise les données d’une source (locale ou distante) vers une destination (locale ou distante) en ne transférant que les octets des fichiers qui ont été modifiés.</em></p>
|
|||
|
|
|||
|
<p>La notion d’unidirectionnalité semble souvent mal comprise : elle signifie qu’en une commande, la synchronisation ne peut se faire que dans un sens. Rien n’empêche ensuite de relancer la commande une seconde fois dans l’autre sens !{:.info}</p>
|
|||
|
|
|||
|
<h2 id="utilisation-basique">Utilisation basique</h2>
|
|||
|
|
|||
|
<p>L’appel de base :</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rsync source/ destination/
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>L’intérêt est une utilisation à travers le réseau. <strong>rsync</strong> utilise SSH par défaut</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rsync -az source/ login@serveur.org:/destination/
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>où:</p>
|
|||
|
|
|||
|
<ul>
|
|||
|
<li><code class="language-plaintext highlighter-rouge">-a</code> ou <code class="language-plaintext highlighter-rouge">--archive</code> : est un moyen rapide de dire que vous voulez la récursivité et préserver pratiquement tout. La seule exception est que si <code class="language-plaintext highlighter-rouge">--files-from</code> a été spécifiée alors <code class="language-plaintext highlighter-rouge">-r</code> n’est pas utilisée. Ceci est équivalent à <code class="language-plaintext highlighter-rouge">-rlptgoD</code>.</li>
|
|||
|
<li><code class="language-plaintext highlighter-rouge">-z</code> ou <code class="language-plaintext highlighter-rouge">--compress</code> : compresse les données lors du transfert. (Limite la bande passante mais augmente l’utilisation processeur et le temps de transfert : inutile en réseau local ou avec très bon débit)</li>
|
|||
|
</ul>
|
|||
|
|
|||
|
<p class="warning">Attention, il convient d’être vigilant dans l’utilisation ou non du slash (« / ») dans le chemin de la source. Ainsi, les deux commandes suivantes <strong>ne sont pas</strong> équivalentes :<br />
|
|||
|
<code class="language-plaintext highlighter-rouge">rsync source destination/</code><br />
|
|||
|
<code class="language-plaintext highlighter-rouge">rsync source/ destination/</code><br />
|
|||
|
En effet, la première commande va <strong>créer</strong> le dossier source dans le dossier destination en ajoutant donc un niveau dans l’arborescence. La deuxième commande copie le <strong>contenu</strong> du dossier source dans le dossier destination.<br />
|
|||
|
Autrement dit, les deux commandes suivantes sont, elles, équivalentes (*) :<br />
|
|||
|
<code class="language-plaintext highlighter-rouge">rsync source destination/</code><br />
|
|||
|
<code class="language-plaintext highlighter-rouge">rsync source/ destination/source/</code><br />
|
|||
|
Enfin, il faut noter que l’utilisation ou non d’un slash final dans le chemin de la destination n’a aucune incidence. Les deux commandes suivantes sont donc équivalentes :<br />
|
|||
|
<code class="language-plaintext highlighter-rouge">rsync source destination/</code><br />
|
|||
|
<code class="language-plaintext highlighter-rouge">rsync source destination</code><br />
|
|||
|
(*)Sauf dans le cas ou source est un lien symbolique vers un répertoire, la première commande ne copie que le lien, tandis que la seconde travaille bien à l’intérieur du répertoire</p>
|
|||
|
|
|||
|
<p>Pour une gestion du port ssh, utiliser la syntaxe suivante:</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rsync -avz source -e "ssh -p port" user@ip:"/chemin/de destination avec espaces/"
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<h3 id="créer-un-dossier-miroir">Créer un dossier miroir</h3>
|
|||
|
|
|||
|
<p>Voici un exemple d’une commande, utilisant le protocole SSH, qui copie à l’identique le dossier <code class="language-plaintext highlighter-rouge"><source></code> vers le dossier <code class="language-plaintext highlighter-rouge"><destination></code>.</p>
|
|||
|
|
|||
|
<p>Copie du dossier source vers le serveur :</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rsync -e ssh -avz --delete-after /home/source user@ip_du_serveur:/dossier/destination/
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>où :</p>
|
|||
|
|
|||
|
<ul>
|
|||
|
<li><code class="language-plaintext highlighter-rouge"><nowiki>--</nowiki>delete-after</code> : à la fin du transfert, supprime les fichiers dans le dossier de destination ne se trouvant pas dans le dossier source.</li>
|
|||
|
<li><code class="language-plaintext highlighter-rouge">-z</code> : compresse les fichiers (Limite la bande passante mais augmente l’utilisation processeur et le temps de transfert : inutile en réseau local ou avec très bon débit)</li>
|
|||
|
<li><code class="language-plaintext highlighter-rouge">-v</code> : verbeux</li>
|
|||
|
<li><code class="language-plaintext highlighter-rouge">-e ssh</code> : utilise le protocole SSH</li>
|
|||
|
</ul>
|
|||
|
|
|||
|
<p>Si les noms des chemins contiennent des espaces, on peut les écrire entre guillemet pour échapper les espaces :</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rsync -e ssh -avz --delete-after "/home/source avec espace/" user@ip_du_serveur:"/dossier/destination avec espace/"
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Avec l’option <code class="language-plaintext highlighter-rouge">-n</code> la commande liste ce qu’elle va faire sans l’exécuter :</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rsync -e ssh -avzn --delete-after /home/mondossier_source user@ip_du_serveur:/dossier/destination/
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<h3 id="exclure-des-fichiers">Exclure des fichiers</h3>
|
|||
|
|
|||
|
<p>On peut exclure des fichiers/dossiers selon beaucoup de schémas. C’est utile pour ne pas sauvegarder le cache, les fichiers temporaires, le répertoire <code class="language-plaintext highlighter-rouge">.git</code>, la corbeille, etc…</p>
|
|||
|
|
|||
|
<p>Liste dans la commande :</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rsync --exclude="nom_de_dossier" --exclude="- autre_nom_de_dossier" source/ destination/
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Un fichier de règles d’exclusion</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rsync --exclude-from=ExclusionRSync source/ destination/
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Et le fichier ExclusionRSync dans le dossier courant sera de cette forme :</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>tmp
|
|||
|
.Trash
|
|||
|
.cache
|
|||
|
.PlayOnLinux
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<h3 id="inclure-des-fichiers">Inclure des fichiers</h3>
|
|||
|
|
|||
|
<p>Dès lors qu’on exclut, il peut être nécessaire d’inclure.<br />
|
|||
|
Exemple, vous souhaitez ne synchroniser qu’un type de fichier, mettons des .csv, cela donne</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rsync --include="*.csv" --exclude="*" source/ destination/
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>il faut respecter l’ordre <code class="language-plaintext highlighter-rouge">include</code> <strong>puis</strong> <code class="language-plaintext warning highlighter-rouge">exclude</code></p>
|
|||
|
|
|||
|
<h3 id="sauvegarde-distante-du-serveur-web">Sauvegarde distante du serveur web</h3>
|
|||
|
|
|||
|
<p>Cas présenté :</p>
|
|||
|
|
|||
|
<ul>
|
|||
|
<li>un serveur distant s’exécutant sous le compte système www-data.
|
|||
|
<ul>
|
|||
|
<li>ce serveur est accessible via ssh</li>
|
|||
|
<li>on a un compte utilisateur pour se connecter sur ce serveur</li>
|
|||
|
<li>ce compte (ou un autre) a les droits d’administration de la machine</li>
|
|||
|
</ul>
|
|||
|
</li>
|
|||
|
<li>une machine sur laquelle sauvegarder les données
|
|||
|
<ul>
|
|||
|
<li>on a un compte utilisateur avec le droit sudo</li>
|
|||
|
</ul>
|
|||
|
</li>
|
|||
|
</ul>
|
|||
|
|
|||
|
<p>Pour l’exemple qui suit :</p>
|
|||
|
|
|||
|
<ol>
|
|||
|
<li>sur la machine locale, on devient <code class="language-plaintext highlighter-rouge">www-data</code> pour travailler avec les droits de ce dernier</li>
|
|||
|
<li>www-data exécute la commande rsync qui va établir une connexion via ssh au serveur distant avec le compte <code class="language-plaintext highlighter-rouge">utilisateur</code> (on peut avoir besoin de saisir le mot de passe de l’utilisateur distant si on n’a pas déposé de clef publique)</li>
|
|||
|
<li>sur le serveur distant, via ssh, <code class="language-plaintext highlighter-rouge">utilisateur</code> va lancer sudo pour devenir <code class="language-plaintext highlighter-rouge">www-data</code></li>
|
|||
|
<li><code class="language-plaintext highlighter-rouge">www-data</code> exécute la commande rsync qui échange les informations avec la machine locale</li>
|
|||
|
</ol>
|
|||
|
|
|||
|
<p>Sur le serveur distant :</p>
|
|||
|
|
|||
|
<ul>
|
|||
|
<li>Autoriser l’utilisateur à lancer la commande <code class="language-plaintext highlighter-rouge">rsync</code> sous le compte système** www-data** grace à sudo ( sans mot de passe): <br />
|
|||
|
<code class="language-plaintext highlighter-rouge">sudo visudo</code></li>
|
|||
|
</ul>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> utilisateurssh ALL=(www-data) NOPASSWD: /usr/bin/rsync
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<ul>
|
|||
|
<li>Optionnel : déposer une clef publique ssh au besoin pour l’utilisateur</li>
|
|||
|
</ul>
|
|||
|
|
|||
|
<p>Sur la machine locale :</p>
|
|||
|
|
|||
|
<ul>
|
|||
|
<li>Lancer une synchronisation en tant qu’utilisateur www-data grace à sudo<br />
|
|||
|
<code class="language-plaintext highlighter-rouge">sudo -u www-data rsync -a --progress -e ssh --rsync-path "sudo -u www-data rsync" utilisateur@serveur_distant:/var/www/ /var/www/</code></li>
|
|||
|
</ul>
|
|||
|
|
|||
|
<h3 id="man-rsync">man rsync</h3>
|
|||
|
|
|||
|
<p><a href="/files/rsync.1.html">man rsync</a></p>
|
|||
|
|
|||
|
<h2 id="rsync-via-sshclés">Rsync via SSH+Clés</h2>
|
|||
|
|
|||
|
<p><em>Comment copier des fichiers avec Rsync via SSH+Clés</em></p>
|
|||
|
|
|||
|
<h3 id="configuration-des-clés-publiques-ssh">Configuration des clés publiques SSH</h3>
|
|||
|
|
|||
|
<p>Sur notre serveur d’origine, nous allons générer des clés SSH publiques sans mot de passe</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh-keygen -f ~/.ssh/id_rsa -q -P ""
|
|||
|
cat ~/.ssh/id_rsa.pub
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>C’est notre clé publique SSH qui peut être placée sur d’autres hôtes pour nous donner accès :</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh-rsa AAAAB3NzaC1yc2EAAAADAADAQABAAABAAABAQDLVDBIpdpfePg/a6h8au1HTKPPrg8wuTrjdh0QFVPpTI4KHctf6/FGg1NOgM+++hrDlbrDVStKn/b3Mu65///tuvY5SG9sR4vrINCSQF+++ a+YRTGU6Sn4ltKpyj 3usHERvBndtFXoD root@cloudads
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Copiez cette clé dans votre presse-papiers et connectez-vous à votre serveur de destination.</p>
|
|||
|
|
|||
|
<p>Placez cette clé SSH dans votre fichier <strong>~/.ssh/authorized_keys</strong> <br />
|
|||
|
Si votre dossier SSH n’existe pas, créez-le manuellement :</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mkdir ~/.ssh
|
|||
|
chmod 0700 ~/.ssh
|
|||
|
touch ~/.ssh/authorized_key
|
|||
|
chmod 0644 ~/.ssh/authorized_key
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<h3 id="synchroniser-les-fichiers">Synchroniser les fichiers</h3>
|
|||
|
|
|||
|
<p>Rsync est un grand utilitaire, car il vous permet, entre autres, de copier des fichiers récursivement avec compression, et sur un canal crypté.</p>
|
|||
|
|
|||
|
<p>Nous copierons un fichier de notre serveur d’origine (198.211.117.101) dans /root/bigfile.txt vers notre serveur de destination (IP : 198.211.117.129) et l’enregistrerons dans /root/bigfile.txt également.</p>
|
|||
|
|
|||
|
<p>Connectez-vous au 198.211.117.101 et synchronisez le fichier avec 198.211.117.129 :</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rsync -avz -e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" --progress /root/bigfile.txt 198.211.117.129:/root/
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Si vous utilisez un autre utilisateur, par exemple “username”, vous devrez l’ajouter devant le serveur de destination. Assurez-vous d’avoir votre clé publique dans le fichier ~/.ssh/authorized_keys de cet utilisateur :</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rsync -avz -e "ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" --progress /root/bigfile.txt username@198.211.117.129:/
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Les options SSH sont utiles pour garder Rsync silencieux et ne pas vous demander une validation à chaque fois que vous vous connectez à un nouveau serveur.</p>
|
|||
|
|
|||
|
<blockquote>
|
|||
|
<p>L’option <strong>UserKnownHostsFile</strong> définit un fichier à utiliser pour la base de données des clés hôte utilisateur au lieu de <strong>~/.ssh/known_hosts</strong> par défaut. Vous pouvez le définir sur <strong>/dev/null</strong>. Le <strong>StrictHostKeyChecking</strong> doit être réglé sur <em>“no”</em>, pour que ssh ajoute automatiquement de nouvelles clés hôte aux fichiers hôtes connus de l’utilisateur.<br />
|
|||
|
Si cet indicateur est défini sur <em>“ask”</em>, de nouvelles clés hôte seront ajoutées aux fichiers hôte connus de l’utilisateur seulement après que l’utilisateur ait confirmé que c’est ce qu’il veut vraiment faire, et ssh refusera de se connecter aux hôtes dont la clé hôte a changé.<br />
|
|||
|
Les clés hôte des hôtes connus seront vérifiées automatiquement dans tous les cas. L’argument doit être <em>“yes”, “no” ou “ask”</em>. La valeur par défaut est <em>“ask”</em>.</p>
|
|||
|
</blockquote>
|
|||
|
|
|||
|
<p>Avec utilisateur, port, fichier clé, destination dans un domaine. La clé doit avoir les droits suivants : <code class="language-plaintext highlighter-rouge">chmod 0700 ~/.ssh/kvm-vps591606</code></p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rsync -avz -e "ssh -p 55031 -i ~/.ssh/kvm-vps591606 " --progress /srv/data/musique/* debadm@cinay.xyz:/srv/musique/
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<h2 id="rsync-via-sshclés-et-sudo">Rsync via SSH+Clés et sudo</h2>
|
|||
|
|
|||
|
<p><em>Utiliser rsync (+ ssh et sudo)</em></p>
|
|||
|
|
|||
|
<h3 id="exemple-rsync-">exemple rsync :</h3>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rsync -av --progress --delete --stats --human-readable -e 'ssh -p xxxx' user@serveurdistant.fr:/home/user/* /home/user/
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<ul>
|
|||
|
<li><code class="language-plaintext highlighter-rouge">-a</code> : c’est l’option de la “mort-qui-tue”. En fait ça fait tout (ou presque). C’est un moyen rapide de dire que vous voulez la récursivité et préserver pratiquement tout. C’est équivalent aux optissn combinées -rlptgoD.</li>
|
|||
|
<li><code class="language-plaintext highlighter-rouge">-v</code> : verbeux</li>
|
|||
|
<li><code class="language-plaintext highlighter-rouge">--progress</code> : vous indique la progression de la copie/transfert</li>
|
|||
|
<li><code class="language-plaintext highlighter-rouge">--stats</code> : affichage de stats sur le transfert des fichiers</li>
|
|||
|
<li>–human-readable : lecture “humaine” des chiffres. Idem à l’option ls <code class="language-plaintext highlighter-rouge">-h</code> (transforme en KO, MO, GB, …)</li>
|
|||
|
<li><code class="language-plaintext highlighter-rouge">- e</code> : spécifie un shell distant</li>
|
|||
|
<li><code class="language-plaintext highlighter-rouge">--delete</code> : cette option demande à rsync d’effacer tous les fichiers superflus côté réception (ceux qui ne sont pas du côté envoi); uniquement pour les répertoires synchronisés.</li>
|
|||
|
</ul>
|
|||
|
|
|||
|
<p>une fois rentré le mot de passe de l’utilisateur distant (en ayant précisé un éventuel port ssh au cas où le serveur ssh ne tournerait pas sur le traditionnel port 22), rsync va “copier” tous les fichiers du répertoire /home/user (/home/user/*) depuis le serveur distant VERS votre nouveau serveur dans le répertoire /home/user.</p>
|
|||
|
|
|||
|
<h3 id="rsync-et-sudo">rsync et sudo</h3>
|
|||
|
|
|||
|
<p>Il peut arriver que certains répertoires ou fichiers ne puissent être récupérés pour des questions de droits. Il va alors falloir, sur le serveur distant, configurer sudo</p>
|
|||
|
|
|||
|
<p>Sur le serveur distant, si sudo n’est pas installé</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt install sudo
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Il faut configurer sudo</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo visudo
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Nous allons rajouter dans le fichier la ligne suivante</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>user ALL= NOPASSWD:/usr/bin/rsync
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Puis on va utiliser l’option “–rsync-path” pour préciser à rsync de démarrer avec l’option sudo</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rsync -av --progress --stats --human-readable --rsync-path="sudo rsync" -e "ssh -p xxxx" useronremoteserver@remoteserver:/data/to/sync /archive/data/
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<h3 id="transfert-volumineux">Transfert volumineux</h3>
|
|||
|
|
|||
|
<p>Lors du transfert de grandes quantités de données, il est recommandé d’exécuter la commande rsync dans une session d’écran ou d’utiliser l’option -P :</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rsync -a -P remote_user@remote_host_or_ip:/opt/media/ /opt/media/
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<h2 id="rsync-serveur--locale">Rsync serveur + locale</h2>
|
|||
|
|
|||
|
<p><em>Sauvegarde des serveurs distants via ssh + rsync et locale via systemd timer</em></p>
|
|||
|
|
|||
|
<h3 id="description">Description</h3>
|
|||
|
|
|||
|
<p>Il faut disposer d’un serveur debian que nous appellerons <strong>serveur source</strong> qui se connectera (via <em>ssh</em> + clés) et exécutera, via le planificateur (cron), des sauvegardes de <strong>serveurs distants</strong> (via <em>rsync</em>) dans un dossier de sauvegarde du <strong>serveur source</strong></p>
|
|||
|
|
|||
|
<h3 id="serveur-distant">Serveur distant</h3>
|
|||
|
|
|||
|
<p><strong>Les opérations suivantes sont à faire sur tous les serveurs distants</strong></p>
|
|||
|
|
|||
|
<h4 id="vérifier-ou-installer-rsync">Vérifier ou installer rsync</h4>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt install rsync # debian
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<h4 id="utilisateur-et-dossier-backupuser">Utilisateur et dossier backupuser</h4>
|
|||
|
|
|||
|
<p>Ajout utilisateur de <strong>backupuser</strong> dans le groupe <strong>users</strong> ,qui ne peut exécuter que <strong>rsync</strong> ,et copier la clé publique du “serveur source”</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo useradd -g users --system --shell /bin/bash --home-dir /home/backupuser --create-home backupuser
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Se connecter en <em>backupuser</em> et accéder au dossier</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo su backupuser
|
|||
|
cd /home/backupuser
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Création dossier .ssh</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mkdir .ssh
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<h4 id="ajout-clé-publique-ssh-du-serveur-source">Ajout clé publique ssh du “serveur source”</h4>
|
|||
|
|
|||
|
<p>dans le fichier <strong>authorized_keys</strong> du nouvel utilisateur</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nano .ssh/authorized_keys
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>coller le contenu du “fichier local”</p>
|
|||
|
|
|||
|
<h4 id="script-rsync-wrappersh">Script rsync-wrapper.sh</h4>
|
|||
|
|
|||
|
<p>Création script bash <strong>rsync-wrapper.sh</strong></p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nano rsync-wrapper.sh
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Contenu du script</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#!/bin/sh
|
|||
|
|
|||
|
date > /home/backupuser/backuplog
|
|||
|
#echo $@ >> /home/backupuser/backuplog
|
|||
|
/usr/bin/sudo /usr/bin/rsync "$@";
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Droits en exécution</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>chmod +x rsync-wrapper.sh
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>fin session backupuser</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>exit # fin session backupuser
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<h4 id="autoriser-utilisateur-à-exécuter-rsync-uniquement">Autoriser utilisateur à exécuter rsync uniquement</h4>
|
|||
|
|
|||
|
<p>Autoriser <em>backupuser</em> à exécuter <strong>rsync</strong> en root sans mot de passe, ajouter au fichier sudoers</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo -s
|
|||
|
echo "backupuser ALL=NOPASSWD:/usr/bin/rsync" >> /etc/sudoers
|
|||
|
exit
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<h3 id="serveur-source">Serveur source</h3>
|
|||
|
|
|||
|
<h4 id="utilisateur-et-dossier-backupuser-1">Utilisateur et dossier backupuser</h4>
|
|||
|
|
|||
|
<p>Création utilisateur <strong>backupuser</strong> dans le groupe <strong>users</strong> et d’un jeu de clé <strong>ssh</strong></p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo useradd -g users --system --shell /bin/bash --home-dir /home/backupuser --create-home backupuser
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Se connecter en <em>backupuser</em> et accéder au dossier</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo su backupuser
|
|||
|
cd /home/backupuser
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Création dossier .ssh</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mkdir .ssh
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Création clé (ed25519 SHA512)</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh-keygen -t ed25519 -f .ssh/backup_key_ed25519 # Accepter les valeurs par défaut
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<h4 id="sauvegarde-complète-sauvegardesh">Sauvegarde complète (sauvegarde.sh)</h4>
|
|||
|
|
|||
|
<p><em>Ce script permet la connexion sur un serveur distant via ssh et la sauvegarde complète via rsync</em></p>
|
|||
|
|
|||
|
<p>Fichiers à exclure de la sauvegarde : <code class="language-plaintext highlighter-rouge">"dev/*","proc/*","sys/*","tmp/*","run/*","mnt/*","media/*","lost+found"</code></p>
|
|||
|
|
|||
|
<p>Script <em>sauvegarde.sh</em> exécuté sur le <strong>serveur source</strong></p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nano sauvegarde.sh
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#!/bin/bash
|
|||
|
|
|||
|
# -a Archive mode (keep file permissions etc...)
|
|||
|
# hôtes distants
|
|||
|
echo $(date) "Sauvegarde hôte distant yanfi.net" >> /srv/sauvegarde/savdistant.log
|
|||
|
/usr/bin/rsync -aev \
|
|||
|
--delete \
|
|||
|
--rsync-path=/home/backupuser/rsync-wrapper.sh \
|
|||
|
--exclude={"dev/*","proc/*","sys/*","tmp/*","run/*","mnt/*","media/*","lost+found"} \
|
|||
|
--rsh="/usr/bin/ssh -p 49022 -i /home/backupuser/.ssh/backup_key_ed25519" backupuser@yanfi.net:/ /srv/sauvegarde/yanfi &>> /srv/sauvegarde/savdistant.log
|
|||
|
echo $(date) "Fin sauvegarde hôte distant yanfi.net" >> /srv/sauvegarde/savdistant.log
|
|||
|
|
|||
|
#envoi des logs du jour par mail
|
|||
|
# grep "$(date +"%d %B %Y")" /srv/sauvegarde/savdistant.log |mail -s "Sauvegarde du $(date +"%d %B %Y")" $desti
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Les droits en exécution</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>chmod +x sauvegarde.sh
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>fin session backupuser</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>exit # fin session backupuser
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<h4 id="création-dossier-de-sauvegarde">Création dossier de sauvegarde</h4>
|
|||
|
|
|||
|
<p>En mode su</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo mkdir -p /srv/sauvegarde
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Donner les droits utilisateur backupuser au dossier de sauvegarde</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo chown backupuser:users /srv/sauvegarde
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<h4 id="autoriser-utilisateur-à-exécuter-rsync-uniquement-1">Autoriser utilisateur à exécuter rsync uniquement</h4>
|
|||
|
|
|||
|
<p>Autoriser <em>backupuser</em> à exécuter <strong>rsync</strong> en root sans mot de passe, ajouter au fichier sudoers</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo -s
|
|||
|
echo "backupuser ALL=NOPASSWD:/usr/bin/rsync" >> /etc/sudoers
|
|||
|
exit
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<h5 id="copie-clé-publique-sur-serveur-distant">Copie clé publique sur serveur distant</h5>
|
|||
|
|
|||
|
<p>Copie de la clé publique (.ssh/backup_key_ed25519.pub) dans le fichier <strong>.ssh/authorized_keys</strong> du serveur distant à sauvegarder :</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo cat /home/backupuser/.ssh/backup_key_ed25519.pub | ssh backupuser@serveur-distant 'cat >> .ssh/authorized_keys'
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<blockquote>
|
|||
|
<p><strong><em>ATTENTION !!! ceci n’est possible que si l’utilisateur backupuser existe sur le serveur distant</em></strong></p>
|
|||
|
</blockquote>
|
|||
|
|
|||
|
<p>Dans le cas contraire, il faut copier la clé publique dans un fichier local (on suppose que l’on est connecté au “serveur source” pat ssh)</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo cat /home/backupuser/.ssh/backup_key_ed25519.pub # Sélectionner tout le fichier et faire un Shift+Ctrl+v pour le copier dans le presse papier
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Ouvrir un “fichier local” , puis y copier le contenu du presse-papier</p>
|
|||
|
|
|||
|
<h4 id="rotation-du-fichier-log-facultatif">Rotation du fichier log (facultatif)</h4>
|
|||
|
|
|||
|
<p>Rotation avec compression</p>
|
|||
|
|
|||
|
<p>sudo nano /etc/logrotate.d/sauvegarde</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/srv/sauvegarde/savdistant.log {
|
|||
|
rotate 6
|
|||
|
monthly
|
|||
|
compress
|
|||
|
missingok
|
|||
|
}
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<ul>
|
|||
|
<li>surveille le fichier savdistant.log et génère une rotation une fois par mois - c’est l’ “intervalle de rotation”.</li>
|
|||
|
<li>‘rotate 6’ signifie qu’à chaque intervalle, on conserve 6 semaines de journalisation.</li>
|
|||
|
<li>Les fichiers de logs peuvent sont compressés au format gzip en spécifiant ‘compress’</li>
|
|||
|
<li>‘missingok’ permet au processus de ne pas s’arrêter à chaque erreur et de poursuivre avec le fichier de log suivant.</li>
|
|||
|
</ul>
|
|||
|
|
|||
|
<h4 id="ordonnancement-cron">Ordonnancement (cron)</h4>
|
|||
|
|
|||
|
<p>Sauvegarde une fois par jour à 3h15 du matin</p>
|
|||
|
|
|||
|
<p>sudo crontab -e</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># tous les jours à 3h15
|
|||
|
15 3 * * * /home/backupuser/sauvegarde.sh
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<h4 id="connexion-ssh-manuelle-pour-valider-le-distant">Connexion ssh manuelle pour valider le distant</h4>
|
|||
|
|
|||
|
<p>Le serveur source doit effectuer une connexion ssh manuelle pour valider le distant</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo su backupuser
|
|||
|
cd /home/backupuser
|
|||
|
#ssh -i .ssh/backup_key_ed25519 backupuser@serveur-distant # si le port du distant est différent de celui par défaut ,il faut le déclarer par -p N°Port.
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>retour sur utilisateur précédent</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>exit # retour sur utilisateur précédent
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<h3 id="sauvegarde-locale-yunohost">Sauvegarde Locale Yunohost</h3>
|
|||
|
|
|||
|
<h4 id="outil-yunohost-backup">Outil yunohost backup</h4>
|
|||
|
|
|||
|
<p>La sauvegarde <strong>Yunohost</strong> sera générée dans le répertoire <strong>/home/yunohost.backup/archives</strong> (certaines applications ne sont pas sauvegardées).
|
|||
|
L’archive est au format <strong>AAAAMMJJ-HHMMSS.tar.gz</strong> ainsi que le fichier info associé <strong>AAAAMMJJ-HHMMSS.info.json</strong><br />
|
|||
|
La sauvegarde est LOCALE , il faut copier le contenu sur une source externe (USB formatée en ext4 , via le réseau par rsync , etc…)<br />
|
|||
|
Donner les droits d’accès aux archives au groupe de l’utilisateur $USER :</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo usermod -a -G users $USER
|
|||
|
sudo chown -Rv root:users /home/yunohost.backup
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<h4 id="yunohost-bash-savyunosh">Yunohost bash savyuno.sh</h4>
|
|||
|
|
|||
|
<p>Se connecter en <em>backupuser</em> et accéder au dossier</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo su backupuser
|
|||
|
cd /home/backupuser
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Créer un bash pour la sauvegarde yunohost</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nano savyuno.sh
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#!/bin/bash
|
|||
|
|
|||
|
DOMAINE="domaine_tld"
|
|||
|
SAVDIR="/srv/sauvegarde"
|
|||
|
if [ -f "$SAVDIR/$DOMAINE.info.json" ];then
|
|||
|
rm $SAVDIR/{$DOMAINE.info.json,$DOMAINE.tar.gz}
|
|||
|
fi
|
|||
|
/usr/bin/yunohost backup create -n $DOMAINE
|
|||
|
mv /home/yunohost.backup/archives/{$DOMAINE.info.json,$DOMAINE.tar.gz} $SAVDIR/
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<blockquote>
|
|||
|
<p>Remplacer domaine_tld par votre nom de backup (!!! remplacer les “.” par des “_” dans les noms)</p>
|
|||
|
</blockquote>
|
|||
|
|
|||
|
<p>droits en exécution</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>chmod +x savyuno.sh
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>retour sur utilisateur précédent</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>exit # retour sur utilisateur précédent
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Vérifier si le répertoire de sauvegarde <strong>/srv/sauvegarde</strong> existe</p>
|
|||
|
|
|||
|
<h4 id="sauvegarde-des-bases-mariadbmysql-backupmysqlsh">sauvegarde des bases MariaDB/Mysql (backupmysql.sh)</h4>
|
|||
|
|
|||
|
<p>Se connecter en <em>backupuser</em> et accéder au dossier</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo su backupuser
|
|||
|
cd /home/backupuser
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Créer un bash pour la sauvegarde des bases</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nano backupmysql.sh
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#!/bin/bash
|
|||
|
|
|||
|
# Configuration de base: datestamp e.g. YYYYMMDD
|
|||
|
DATE=$(date +"%Y%m%d")
|
|||
|
|
|||
|
# Dossier où sauvegarder les backups (créez le d'abord!)
|
|||
|
BACKUP_DIR="/srv/sauvegarde/mysql"
|
|||
|
|
|||
|
# Identifiants MySQL
|
|||
|
MYSQL_USER="root"
|
|||
|
MYSQL_PASSWORD=$(cat /etc/yunohost/mysql)
|
|||
|
GZIP="$(which gzip)"
|
|||
|
|
|||
|
# Commandes MySQL (aucune raison de modifier ceci)
|
|||
|
MYSQL="$(which mysql)"
|
|||
|
MYSQLDUMP="$(which mysqldump)"
|
|||
|
|
|||
|
# Bases de données MySQL à ignorer
|
|||
|
SKIPDATABASES="Database|information_schema|performance_schema|mysql"
|
|||
|
|
|||
|
# Nombre de jours à garder les dossiers (seront effacés après X jours)
|
|||
|
RETENTION=7
|
|||
|
|
|||
|
# ---- NE RIEN MODIFIER SOUS CETTE LIGNE ------------------------------------------
|
|||
|
#
|
|||
|
# Create a new directory into backup directory location for this date
|
|||
|
mkdir -p $BACKUP_DIR/$DATE
|
|||
|
|
|||
|
# Retrieve a list of all databases
|
|||
|
databases=`$MYSQL -u$MYSQL_USER -p$MYSQL_PASSWORD -e "SHOW DATABASES;" | grep -Ev "($SKIPDATABASES)"`
|
|||
|
|
|||
|
# Dump the databases in seperate names and gzip the .sql file
|
|||
|
for db in $databases; do
|
|||
|
echo $db
|
|||
|
$MYSQLDUMP --force --opt --user=$MYSQL_USER -p$MYSQL_PASSWORD --skip-lock-tables --events --databases $db | $GZIP > "$BACKUP_DIR/$DATE/$db.sql.gz"
|
|||
|
done
|
|||
|
|
|||
|
# Remove files older than X days
|
|||
|
|
|||
|
find $BACKUP_DIR/* -mtime +$RETENTION -delete
|
|||
|
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>droits en exécution</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>chmod +x backupmysql.sh
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>retour sur utilisateur précédent</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>exit # retour sur utilisateur précédent
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Créer le répertoire de sauvegarde des bases</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo mkdir -p /srv/sauvegarde/mysql
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<h3 id="ordonnancement-des-tâches-cron">Ordonnancement des tâches (cron)</h3>
|
|||
|
|
|||
|
<p>Sauvegarde yunohost tous les jours à 2h15<br />
|
|||
|
Sauvegarde des bases mysql tous les jours à 2h30<br />
|
|||
|
Vérification validité des certificats SSL 1 fois par semaine à 2h30 et regénération auto si nécessaire (NE PAS AJOUTER si serveur Yunohost)</p>
|
|||
|
|
|||
|
<p>le fichier crontab</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo -s
|
|||
|
crontab -e
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># m h dom mon dow command
|
|||
|
30 1 * * * /home/backupuser/savyuno.sh
|
|||
|
30 2 * * * /home/backupuser/backupmysql.sh
|
|||
|
30 2 * * 1 /usr/local/sbin/le-renew-webroot >> /var/log/le-renewal.log
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p><strong>ATTENTION!!! La sauvegarde de yunohost (savyuno.sh) est très longue en temps</strong></p>
|
|||
|
|
|||
|
<h3 id="sauvegarde-locale-via-systemd-timer">Sauvegarde locale via systemd timer</h3>
|
|||
|
|
|||
|
<p>Le fonctionnement de <strong>systemd</strong> impose cependant d’avoir deux fichiers :<br />
|
|||
|
<strong>service</strong>, qui contient la définition du programme<br />
|
|||
|
<strong>timer</strong>, qui dit “quand” le lancer.</p>
|
|||
|
|
|||
|
<blockquote>
|
|||
|
<p>Ils doivent porter le même nom et se situer dans <strong>/etc/systemd/system/</strong>.</p>
|
|||
|
</blockquote>
|
|||
|
|
|||
|
<h4 id="création-service-et-timer">Création service et timer</h4>
|
|||
|
|
|||
|
<p>Si vous gérez déjà vos services via systemd, vous avez déjà utilisé des “unit” systemd de type “service”.<br />
|
|||
|
Ces “unit” permettent de définir un process et son mode d’éxécution.<br />
|
|||
|
Pour implémenter un “timer” sous systemd, il va nous falloir un fichier “service”.</p>
|
|||
|
|
|||
|
<p>Pour notre tâche à planifier, nous allons avoir au final 3 fichiers :</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Le script à exécuter
|
|||
|
Le fichier “service” qui va dire quel script exécuter
|
|||
|
Le fichier “timer” qui va indiquer quand il doit être exécuté.
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<blockquote>
|
|||
|
<p>A noter que par convention, les fichiers service et timer doivent avoir le même nom</p>
|
|||
|
</blockquote>
|
|||
|
|
|||
|
<p>Nous devons exécuter ,une fois par jour , un script de sauvegarde <strong>/home/yannick/scripts/savarch</strong> sur un ordinateur qui n’est pas sous tension 24/24h.</p>
|
|||
|
|
|||
|
<p>Pour le fichier service <strong>/etc/systemd/system/savarch.service</strong>, une base simple</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Unit]
|
|||
|
Description=Sauvegarde jour
|
|||
|
|
|||
|
[Service]
|
|||
|
Type=simple
|
|||
|
ExecStart=/bin/bash /home/yannick/scripts/savarch
|
|||
|
StandardError=journal
|
|||
|
Type=oneshot
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Je fournis une description à mon service, indique que c’est un process de type simple, le chemin vers mon script et je rajoute que le flux d’erreur est envoyé dans le journal.Il ne faut pas de section [Install] car le script va être piloté par le fichier timer.<br />
|
|||
|
La ligne <code class="language-plaintext highlighter-rouge">Type=oneshot</code> est importante, c’est elle qui dit à systemd de ne pas relancer le service en boucle.</p>
|
|||
|
|
|||
|
<p>Le fichier “timer” <strong>/etc/systemd/system/savarch.timer</strong></p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[Unit]
|
|||
|
Description=Sauvegarde jour
|
|||
|
|
|||
|
[Timer]
|
|||
|
# lisez le man systemd.timer(5) pour tout ce qui est disponible
|
|||
|
OnCalendar=daily
|
|||
|
# Autoriser la persistence entre les reboot
|
|||
|
Persistent=true
|
|||
|
Unit=savarch.service
|
|||
|
|
|||
|
[Install]
|
|||
|
WantedBy=timers.target
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<ul>
|
|||
|
<li><strong>OnCalendar</strong> permet d’indiquer l’occurrence et la fréquence d’exécution du script. Il y a les abréviations classiques (“minutely”, “hourly”, “daily”, “monthly”, “weekly”, “yearly”, “quarterly”, “semiannually”, etc) mais vous pouvez avoir des choses plus complexes comme “Mon,Tue <em>-</em>-01..04 12:00:00” - voir <strong>systemd.time</strong></li>
|
|||
|
<li><strong>Persistent</strong> va forcer l’exécution du script si la dernière exécution a été manquée suite à un reboot de serveur ou autre événement.</li>
|
|||
|
<li><strong>Install</strong> va créer la dépendance pour que votre “timer” soit bien exécuté et pris en compte par systemd.</li>
|
|||
|
</ul>
|
|||
|
|
|||
|
<h4 id="activation-et-démarrage-du-timer">Activation et démarrage du timer</h4>
|
|||
|
|
|||
|
<p>Il est possible de tester le service avec un simple <code class="language-plaintext highlighter-rouge">systemctl start savarch.service</code>, de regarder les logs avec <code class="language-plaintext highlighter-rouge">journalctl -u savarch.service</code>.</p>
|
|||
|
|
|||
|
<p>Ensuite, pour qu’il soit actif, il faut prévenir systemd</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo systemctl enable savarch.timer
|
|||
|
sudo systemctl start savarch.timer
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<h4 id="gestion-et-suivi-dun-timer">Gestion et suivi d’un timer</h4>
|
|||
|
|
|||
|
<p>Pour voir la liste des “timers” actifs et la date de leur dernière et prochaine exécution</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>systemctl list-timers
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>NEXT LEFT LAST PASSED UNIT ACTIVATES
|
|||
|
Fri 2018-03-02 00:00:00 CET 15h left Thu 2018-03-01 07:49:43 CET 56min ago logrotate.timer logrotate.service
|
|||
|
Fri 2018-03-02 00:00:00 CET 15h left Thu 2018-03-01 07:49:43 CET 56min ago man-db.timer man-db.service
|
|||
|
Fri 2018-03-02 00:00:00 CET 15h left Thu 2018-03-01 07:49:43 CET 56min ago savarch.timer savarch.service
|
|||
|
Fri 2018-03-02 00:00:00 CET 15h left Thu 2018-03-01 07:49:43 CET 56min ago shadow.timer shadow.service
|
|||
|
Fri 2018-03-02 00:00:00 CET 15h left Thu 2018-03-01 07:49:43 CET 56min ago updatedb.timer updatedb.service
|
|||
|
Fri 2018-03-02 08:04:45 CET 23h left Thu 2018-03-01 08:04:45 CET 41min ago systemd-tmpfiles-clean.timer systemd-tmpfiles-clean.service
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>et accéder aux logs de vos “timers” :</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>journalctl -u savarch.service
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>...
|
|||
|
mars 01 08:43:47 yannick-pc bash[1932]: 2018-03-01 08:43:15 Départ sauvegarde
|
|||
|
mars 01 08:43:47 yannick-pc bash[1932]: 2018-03-01 08:43:15 synchronisation source partagée /home/yannick/media/Musique cible locale /home/yannick/media/savlocal/Musique
|
|||
|
mars 01 08:43:47 yannick-pc bash[1932]: 2018-03-01 08:43:21 synchronisation partagée /home/yannick/media/devel cible locale /home/yannick/media/savlocal/devel
|
|||
|
mars 01 08:43:47 yannick-pc bash[1932]: 2018-03-01 08:43:33 synchronisation partagée /home/yannick/media/BiblioCalibre cible locale /home/yannick/media/savlocal/BiblioCalibre
|
|||
|
mars 01 08:43:47 yannick-pc bash[1932]: 2018-03-01 08:43:41 SAUVEGARDE /home/yannick -> /home/yannick/media/savlocal/yannick-pc/yannick
|
|||
|
mars 01 08:43:47 yannick-pc bash[1932]: 2018-03-01 08:43:42 SAUVEGARDE /home/yannick/media/dplus -> /home/yannick/media/savlocal/dplus
|
|||
|
mars 01 08:43:47 yannick-pc bash[1932]: 2018-03-01 08:43:47 SAUVEGARDE /home/yannick/media/yanspm-md -> /home/yannick/media/savlocal/yanspm-md
|
|||
|
mars 01 08:43:47 yannick-pc bash[1932]: 2018-03-01 08:43:47 FIN sauvegarde
|
|||
|
mars 01 08:43:47 yannick-pc systemd[1]: Started Sauvegarde jour.
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>En cas de modification du <strong>.timer</strong> ou du <strong>.service</strong>, ne pas oublier de faire un <strong>systemctl daemon-reload</strong> pour que la version actualisée de vos fichiers soit prise en compte par systemd.</p>
|
|||
|
|
|||
|
<h2 id="exemples-de-script-crontab">Exemples de script crontab</h2>
|
|||
|
|
|||
|
<h3 id="rsync--transfert-des-sauvegardes-borg-xoyazxyz--xoyizexyz">Rsync , transfert des sauvegardes borg xoyaz.xyz → xoyize.xyz</h3>
|
|||
|
|
|||
|
<p><em>Pour les serveurs yanspm.com yanfi.net et cinay.xyz ,les sauvegardes sont effectuées en utilisant le logiciel borg ,vers le serveur de backup xoyaz.xyz et on utilise rsync pour les transférer localement</em></p>
|
|||
|
|
|||
|
<p><strong>connexion avec clé</strong><br />
|
|||
|
<u>sur le serveur appelant srvxo (xoyize.xyz - 192.168.0.45)</u>
|
|||
|
Générer une paire de clé curve25519-sha256 (ECDH avec Curve25519 et SHA2) nommé <strong>xoyaz_ed25519</strong> pour une liaison SSH avec le serveur KVM.</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh-keygen -t ed25519 -o -a 100 -f ~/.ssh/xoyaz_ed25519
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Ajouter la clé publique au fichier <strong>~/.ssh/authorized_keys</strong> du serveur de backup xoyaz.xyz</p>
|
|||
|
|
|||
|
<p>Test connexion</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ssh -p 55036 -i /home/admbust/.ssh/xoyaz_ed25519 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null usernl@xoyaz.xyz
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Créer la commande rsync avec son fichier d’exclusion</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nano .ssh/exclude-list.txt
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.bash_logout
|
|||
|
.bashrc
|
|||
|
.profile
|
|||
|
.ssh
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Lancer la commande rsync dans une fenêtre terminal tmux en mode su</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>tmux
|
|||
|
sudo -s
|
|||
|
rsync -avz --progress --stats --human-readable --exclude-from '/home/admbust/.ssh/exclude-list.txt' --rsync-path="sudo rsync" -e "ssh -p 55036 -i /home/admbust/.ssh/xoyaz_ed25519 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" usernl@xoyaz.xyz:/srv/data/borg-backups/* /srv/data/borg-backups/
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>Paramétrer pour une exécution chaque jour à 2h30</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo crontab -e
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Synchro Backup (contient les dossiers de sauvegardes borg) avec xoyize
|
|||
|
30 02 * * * rsync -avz --progress --stats --human-readable --exclude-from '/home/admbust/.ssh/exclude-list.txt' --rsync-path="sudo rsync" -e "ssh -p 55036 -i /home/admbust/.ssh/xoyaz_ed25519 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" usernl@xoyaz.xyz:/srv/data/borg-backups/* /srv/data/borg-backups/ ; if [ $? -eq 0 ]; then echo "Rsync usernl@xoyaz.xyz:/srv/data/borg-backups/* /srv/data/borg-backups/ : Synchronisation OK" | systemd-cat -t rsyncborg -p info ; else echo "Rsync usernl@xoyaz.xyz:/srv/data/borg-backups/* /srv/data/borg-backups/ :Erreur Synchronisation" | systemd-cat -t rsyncborg -p emerg ; fi
|
|||
|
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>On redirige le résultat de la commande ($?) vers le journal avec <strong>systemd-cat</strong> <br />
|
|||
|
<code class="language-plaintext highlighter-rouge">if [ $? -eq 0 ]; then echo "Rsync usernl@xoyaz.xyz:/srv/data/borg-backups/* /srv/data/borg-backups/ : Synchronisation OK" | systemd-cat -t rsyncborg -p info ; else echo "Rsync usernl@xoyaz.xyz:/srv/data/borg-backups/* /srv/data/borg-backups/ :Erreur Synchronisation" | systemd-cat -t rsyncborg -p emerg ; fi</code></p>
|
|||
|
|
|||
|
<p>la ligne de commande décortiquée:</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>if [ $? -eq 0 ]
|
|||
|
then
|
|||
|
echo "Rsync usernl@xoyaz.xyz:/srv/data/borg-backups/* /srv/data/borg-backups/ : Synchronisation OK" | systemd-cat -t rsyncborg -p info
|
|||
|
else
|
|||
|
echo "Rsync usernl@xoyaz.xyz:/srv/data/borg-backups/* /srv/data/borg-backups/ :Erreur Synchronisation" | systemd-cat -t rsyncborg -p emerg
|
|||
|
fi
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<p>On peut lire le journal avec l’identifiant <em>rsyncborg</em></p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>journalctl -t rsyncborg
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-- Logs begin at Thu 2019-08-29 11:07:55 CEST, end at Tue 2019-09-17 17:50:32 CEST. --
|
|||
|
sept. 17 17:50:32 srvxo rsyncborg[11728]: Rsync usernl@xoyaz.xyz:/srv/data/borg-backups/* /srv/data/borg-backups/ : Synchronisation OK
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<h3 id="sauvegarde-dossiers-musique-calibre--xoyazxyz-backup">Sauvegarde dossiers Musique Calibre → xoyaz.xyz (backup)</h3>
|
|||
|
|
|||
|
<p>Ajouter au lanceur de tâches programmées</p>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo crontab -e
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Sauvegarde de dossiers /srv/data/{Musique,CalibreTechnique} sur serveur distant backup (nl) xoyaz.xyz
|
|||
|
# Synchro Musique avec Backup
|
|||
|
00 02 * * * rsync -avz --progress --stats --human-readable --exclude-from '/home/admbust/.ssh/exclude-list.txt' --rsync-path="sudo rsync" -e "ssh -p 55036 -i /home/admbust/.ssh/xoyaz_ed25519 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" /srv/data/Musique/* usernl@xoyaz.xyz:/home/usernl/backup/musique/
|
|||
|
# Synchro CalibreTechnique avec Backup
|
|||
|
10 02 * * * rsync -avz --progress --stats --human-readable --exclude-from '/home/admbust/.ssh/exclude-list.txt' --rsync-path="sudo rsync" -e "ssh -p 55036 -i /home/admbust/.ssh/xoyaz_ed25519 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null" /srv/data/CalibreTechnique/* usernl@xoyaz.xyz:/home/usernl/backup/CalibreTechnique/
|
|||
|
</code></pre></div></div>
|
|||
|
|
|||
|
|
|||
|
</div>
|
|||
|
|
|||
|
|
|||
|
|
|||
|
<div class="d-print-none"><footer class="article__footer"><meta itemprop="dateModified" content="2019-12-13T00:00:00+01:00"><!-- start custom article footer snippet -->
|
|||
|
|
|||
|
<!-- end custom article footer snippet -->
|
|||
|
<!--
|
|||
|
<div align="right"><a type="application/rss+xml" href="/feed.xml" title="S'abonner"><i class="fa fa-rss fa-2x"></i></a>
|
|||
|
|
|||
|
 </div>
|
|||
|
-->
|
|||
|
</footer>
|
|||
|
<div class="article__section-navigator clearfix"><div class="previous"><span>PRÉCÉDENT</span><a href="/2019/12/11/Trousseau-de-Cles-Gnome-Keyring.html">XFCE/GNOME Porte-clés ou trousseau (gnome-keyring)</a></div><div class="next"><span>SUIVANT</span><a href="/2019/12/14/Android-Samsung-Galaxy-A5-2016-SM-A510F.html">Samsung Galaxy A5 [2016] SM-A510F (Android)</a></div></div></div>
|
|||
|
|
|||
|
</div>
|
|||
|
|
|||
|
<script>(function() {
|
|||
|
var SOURCES = window.TEXT_VARIABLES.sources;
|
|||
|
window.Lazyload.js(SOURCES.jquery, function() {
|
|||
|
$(function() {
|
|||
|
var $this ,$scroll;
|
|||
|
var $articleContent = $('.js-article-content');
|
|||
|
var hasSidebar = $('.js-page-root').hasClass('layout--page--sidebar');
|
|||
|
var scroll = hasSidebar ? '.js-page-main' : 'html, body';
|
|||
|
$scroll = $(scroll);
|
|||
|
|
|||
|
$articleContent.find('.highlight').each(function() {
|
|||
|
$this = $(this);
|
|||
|
$this.attr('data-lang', $this.find('code').attr('data-lang'));
|
|||
|
});
|
|||
|
$articleContent.find('h1[id], h2[id], h3[id], h4[id], h5[id], h6[id]').each(function() {
|
|||
|
$this = $(this);
|
|||
|
$this.append($('<a class="anchor d-print-none" aria-hidden="true"></a>').html('<i class="fas fa-anchor"></i>'));
|
|||
|
});
|
|||
|
$articleContent.on('click', '.anchor', function() {
|
|||
|
$scroll.scrollToAnchor('#' + $(this).parent().attr('id'), 400);
|
|||
|
});
|
|||
|
});
|
|||
|
});
|
|||
|
})();
|
|||
|
</script>
|
|||
|
|
|||
|
</div><section class="page__comments d-print-none"></section></article><!-- start custom main bottom snippet -->
|
|||
|
<!-- end custom main bottom snippet -->
|
|||
|
</div>
|
|||
|
</div></div></div></div>
|
|||
|
</div><script>(function() {
|
|||
|
var SOURCES = window.TEXT_VARIABLES.sources;
|
|||
|
window.Lazyload.js(SOURCES.jquery, function() {
|
|||
|
var $body = $('body'), $window = $(window);
|
|||
|
var $pageRoot = $('.js-page-root'), $pageMain = $('.js-page-main');
|
|||
|
var activeCount = 0;
|
|||
|
function modal(options) {
|
|||
|
var $root = this, visible, onChange, hideWhenWindowScroll = false;
|
|||
|
var scrollTop;
|
|||
|
function setOptions(options) {
|
|||
|
var _options = options || {};
|
|||
|
visible = _options.initialVisible === undefined ? false : show;
|
|||
|
onChange = _options.onChange;
|
|||
|
hideWhenWindowScroll = _options.hideWhenWindowScroll;
|
|||
|
}
|
|||
|
function init() {
|
|||
|
setState(visible);
|
|||
|
}
|
|||
|
function setState(isShow) {
|
|||
|
if (isShow === visible) {
|
|||
|
return;
|
|||
|
}
|
|||
|
visible = isShow;
|
|||
|
if (visible) {
|
|||
|
activeCount++;
|
|||
|
scrollTop = $(window).scrollTop() || $pageMain.scrollTop();
|
|||
|
$root.addClass('modal--show');
|
|||
|
$pageMain.scrollTop(scrollTop);
|
|||
|
activeCount === 1 && ($pageRoot.addClass('show-modal'), $body.addClass('of-hidden'));
|
|||
|
hideWhenWindowScroll && window.hasEvent('touchstart') && $window.on('scroll', hide);
|
|||
|
$window.on('keyup', handleKeyup);
|
|||
|
} else {
|
|||
|
activeCount > 0 && activeCount--;
|
|||
|
$root.removeClass('modal--show');
|
|||
|
$window.scrollTop(scrollTop);
|
|||
|
activeCount === 0 && ($pageRoot.removeClass('show-modal'), $body.removeClass('of-hidden'));
|
|||
|
hideWhenWindowScroll && window.hasEvent('touchstart') && $window.off('scroll', hide);
|
|||
|
$window.off('keyup', handleKeyup);
|
|||
|
}
|
|||
|
onChange && onChange(visible);
|
|||
|
}
|
|||
|
function show() {
|
|||
|
setState(true);
|
|||
|
}
|
|||
|
function hide() {
|
|||
|
setState(false);
|
|||
|
}
|
|||
|
function handleKeyup(e) {
|
|||
|
// Char Code: 27 ESC
|
|||
|
if (e.which === 27) {
|
|||
|
hide();
|
|||
|
}
|
|||
|
}
|
|||
|
setOptions(options);
|
|||
|
init();
|
|||
|
return {
|
|||
|
show: show,
|
|||
|
hide: hide,
|
|||
|
$el: $root
|
|||
|
};
|
|||
|
}
|
|||
|
$.fn.modal = modal;
|
|||
|
});
|
|||
|
})();
|
|||
|
</script><div class="modal modal--overflow page__search-modal d-print-none js-page-search-modal"><script>
|
|||
|
(function () {
|
|||
|
var SOURCES = window.TEXT_VARIABLES.sources;
|
|||
|
window.Lazyload.js(SOURCES.jquery, function() {
|
|||
|
// search panel
|
|||
|
var search = (window.search || (window.search = {}));
|
|||
|
var useDefaultSearchBox = window.useDefaultSearchBox === undefined ?
|
|||
|
true : window.useDefaultSearchBox ;
|
|||
|
|
|||
|
var $searchModal = $('.js-page-search-modal');
|
|||
|
var $searchToggle = $('.js-search-toggle');
|
|||
|
var searchModal = $searchModal.modal({ onChange: handleModalChange, hideWhenWindowScroll: true });
|
|||
|
var modalVisible = false;
|
|||
|
search.searchModal = searchModal;
|
|||
|
|
|||
|
var $searchBox = null;
|
|||
|
var $searchInput = null;
|
|||
|
var $searchClear = null;
|
|||
|
|
|||
|
function getModalVisible() {
|
|||
|
return modalVisible;
|
|||
|
}
|
|||
|
search.getModalVisible = getModalVisible;
|
|||
|
|
|||
|
function handleModalChange(visible) {
|
|||
|
modalVisible = visible;
|
|||
|
if (visible) {
|
|||
|
search.onShow && search.onShow();
|
|||
|
useDefaultSearchBox && $searchInput[0] && $searchInput[0].focus();
|
|||
|
} else {
|
|||
|
search.onShow && search.onHide();
|
|||
|
useDefaultSearchBox && $searchInput[0] && $searchInput[0].blur();
|
|||
|
setTimeout(function() {
|
|||
|
useDefaultSearchBox && ($searchInput.val(''), $searchBox.removeClass('not-empty'));
|
|||
|
search.clear && search.clear();
|
|||
|
window.pageAsideAffix && window.pageAsideAffix.refresh();
|
|||
|
}, 400);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
$searchToggle.on('click', function() {
|
|||
|
modalVisible ? searchModal.hide() : searchModal.show();
|
|||
|
});
|
|||
|
// Char Code: 83 S, 191 /
|
|||
|
$(window).on('keyup', function(e) {
|
|||
|
if (!modalVisible && !window.isFormElement(e.target || e.srcElement) && (e.which === 83 || e.which === 191)) {
|
|||
|
modalVisible || searchModal.show();
|
|||
|
}
|
|||
|
});
|
|||
|
|
|||
|
if (useDefaultSearchBox) {
|
|||
|
$searchBox = $('.js-search-box');
|
|||
|
$searchInput = $searchBox.children('input');
|
|||
|
$searchClear = $searchBox.children('.js-icon-clear');
|
|||
|
search.getSearchInput = function() {
|
|||
|
return $searchInput.get(0);
|
|||
|
};
|
|||
|
search.getVal = function() {
|
|||
|
return $searchInput.val();
|
|||
|
};
|
|||
|
search.setVal = function(val) {
|
|||
|
$searchInput.val(val);
|
|||
|
};
|
|||
|
|
|||
|
$searchInput.on('focus', function() {
|
|||
|
$(this).addClass('focus');
|
|||
|
});
|
|||
|
$searchInput.on('blur', function() {
|
|||
|
$(this).removeClass('focus');
|
|||
|
});
|
|||
|
$searchInput.on('input', window.throttle(function() {
|
|||
|
var val = $(this).val();
|
|||
|
if (val === '' || typeof val !== 'string') {
|
|||
|
search.clear && search.clear();
|
|||
|
} else {
|
|||
|
$searchBox.addClass('not-empty');
|
|||
|
search.onInputNotEmpty && search.onInputNotEmpty(val);
|
|||
|
}
|
|||
|
}, 400));
|
|||
|
$searchClear.on('click', function() {
|
|||
|
$searchInput.val(''); $searchBox.removeClass('not-empty');
|
|||
|
search.clear && search.clear();
|
|||
|
});
|
|||
|
}
|
|||
|
});
|
|||
|
})();
|
|||
|
</script><div class="search search--dark">
|
|||
|
<div class="main">
|
|||
|
<div class="search__header">Recherche</div>
|
|||
|
<div class="search-bar">
|
|||
|
<div class="search-box js-search-box">
|
|||
|
<div class="search-box__icon-search"><i class="fas fa-search"></i></div>
|
|||
|
<input id="search-input" type="text" />
|
|||
|
<div class="search-box__icon-clear js-icon-clear">
|
|||
|
<a><i class="fas fa-times"></i></a>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
<button class="button button--theme-dark button--pill search__cancel js-search-toggle">
|
|||
|
Annuler</button>
|
|||
|
</div>
|
|||
|
<div id="results-container" class="search-result js-search-result"></div>
|
|||
|
</div>
|
|||
|
</div>
|
|||
|
<!-- Script pointing to search-script.js -->
|
|||
|
<script>/*!
|
|||
|
* Simple-Jekyll-Search
|
|||
|
* Copyright 2015-2020, Christian Fei
|
|||
|
* Licensed under the MIT License.
|
|||
|
*/
|
|||
|
|
|||
|
(function(){
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$Templater_7 = {
|
|||
|
compile: compile,
|
|||
|
setOptions: setOptions
|
|||
|
}
|
|||
|
|
|||
|
const options = {}
|
|||
|
options.pattern = /\{(.*?)\}/g
|
|||
|
options.template = ''
|
|||
|
options.middleware = function () {}
|
|||
|
|
|||
|
function setOptions (_options) {
|
|||
|
options.pattern = _options.pattern || options.pattern
|
|||
|
options.template = _options.template || options.template
|
|||
|
if (typeof _options.middleware === 'function') {
|
|||
|
options.middleware = _options.middleware
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function compile (data) {
|
|||
|
return options.template.replace(options.pattern, function (match, prop) {
|
|||
|
const value = options.middleware(prop, data[prop], options.template)
|
|||
|
if (typeof value !== 'undefined') {
|
|||
|
return value
|
|||
|
}
|
|||
|
return data[prop] || match
|
|||
|
})
|
|||
|
}
|
|||
|
|
|||
|
'use strict';
|
|||
|
|
|||
|
function fuzzysearch (needle, haystack) {
|
|||
|
var tlen = haystack.length;
|
|||
|
var qlen = needle.length;
|
|||
|
if (qlen > tlen) {
|
|||
|
return false;
|
|||
|
}
|
|||
|
if (qlen === tlen) {
|
|||
|
return needle === haystack;
|
|||
|
}
|
|||
|
outer: for (var i = 0, j = 0; i < qlen; i++) {
|
|||
|
var nch = needle.charCodeAt(i);
|
|||
|
while (j < tlen) {
|
|||
|
if (haystack.charCodeAt(j++) === nch) {
|
|||
|
continue outer;
|
|||
|
}
|
|||
|
}
|
|||
|
return false;
|
|||
|
}
|
|||
|
return true;
|
|||
|
}
|
|||
|
|
|||
|
var _$fuzzysearch_1 = fuzzysearch;
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
/* removed: const _$fuzzysearch_1 = require('fuzzysearch') */;
|
|||
|
|
|||
|
var _$FuzzySearchStrategy_5 = new FuzzySearchStrategy()
|
|||
|
|
|||
|
function FuzzySearchStrategy () {
|
|||
|
this.matches = function (string, crit) {
|
|||
|
return _$fuzzysearch_1(crit.toLowerCase(), string.toLowerCase())
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$LiteralSearchStrategy_6 = new LiteralSearchStrategy()
|
|||
|
|
|||
|
function LiteralSearchStrategy () {
|
|||
|
this.matches = function (str, crit) {
|
|||
|
if (!str) return false
|
|||
|
|
|||
|
str = str.trim().toLowerCase()
|
|||
|
crit = crit.trim().toLowerCase()
|
|||
|
|
|||
|
return crit.split(' ').filter(function (word) {
|
|||
|
return str.indexOf(word) >= 0
|
|||
|
}).length === crit.split(' ').length
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$Repository_4 = {
|
|||
|
put: put,
|
|||
|
clear: clear,
|
|||
|
search: search,
|
|||
|
setOptions: __setOptions_4
|
|||
|
}
|
|||
|
|
|||
|
/* removed: const _$FuzzySearchStrategy_5 = require('./SearchStrategies/FuzzySearchStrategy') */;
|
|||
|
/* removed: const _$LiteralSearchStrategy_6 = require('./SearchStrategies/LiteralSearchStrategy') */;
|
|||
|
|
|||
|
function NoSort () {
|
|||
|
return 0
|
|||
|
}
|
|||
|
|
|||
|
const data = []
|
|||
|
let opt = {}
|
|||
|
|
|||
|
opt.fuzzy = false
|
|||
|
opt.limit = 10
|
|||
|
opt.searchStrategy = opt.fuzzy ? _$FuzzySearchStrategy_5 : _$LiteralSearchStrategy_6
|
|||
|
opt.sort = NoSort
|
|||
|
opt.exclude = []
|
|||
|
|
|||
|
function put (data) {
|
|||
|
if (isObject(data)) {
|
|||
|
return addObject(data)
|
|||
|
}
|
|||
|
if (isArray(data)) {
|
|||
|
return addArray(data)
|
|||
|
}
|
|||
|
return undefined
|
|||
|
}
|
|||
|
function clear () {
|
|||
|
data.length = 0
|
|||
|
return data
|
|||
|
}
|
|||
|
|
|||
|
function isObject (obj) {
|
|||
|
return Boolean(obj) && Object.prototype.toString.call(obj) === '[object Object]'
|
|||
|
}
|
|||
|
|
|||
|
function isArray (obj) {
|
|||
|
return Boolean(obj) && Object.prototype.toString.call(obj) === '[object Array]'
|
|||
|
}
|
|||
|
|
|||
|
function addObject (_data) {
|
|||
|
data.push(_data)
|
|||
|
return data
|
|||
|
}
|
|||
|
|
|||
|
function addArray (_data) {
|
|||
|
const added = []
|
|||
|
clear()
|
|||
|
for (let i = 0, len = _data.length; i < len; i++) {
|
|||
|
if (isObject(_data[i])) {
|
|||
|
added.push(addObject(_data[i]))
|
|||
|
}
|
|||
|
}
|
|||
|
return added
|
|||
|
}
|
|||
|
|
|||
|
function search (crit) {
|
|||
|
if (!crit) {
|
|||
|
return []
|
|||
|
}
|
|||
|
return findMatches(data, crit, opt.searchStrategy, opt).sort(opt.sort)
|
|||
|
}
|
|||
|
|
|||
|
function __setOptions_4 (_opt) {
|
|||
|
opt = _opt || {}
|
|||
|
|
|||
|
opt.fuzzy = _opt.fuzzy || false
|
|||
|
opt.limit = _opt.limit || 10
|
|||
|
opt.searchStrategy = _opt.fuzzy ? _$FuzzySearchStrategy_5 : _$LiteralSearchStrategy_6
|
|||
|
opt.sort = _opt.sort || NoSort
|
|||
|
opt.exclude = _opt.exclude || []
|
|||
|
}
|
|||
|
|
|||
|
function findMatches (data, crit, strategy, opt) {
|
|||
|
const matches = []
|
|||
|
for (let i = 0; i < data.length && matches.length < opt.limit; i++) {
|
|||
|
const match = findMatchesInObject(data[i], crit, strategy, opt)
|
|||
|
if (match) {
|
|||
|
matches.push(match)
|
|||
|
}
|
|||
|
}
|
|||
|
return matches
|
|||
|
}
|
|||
|
|
|||
|
function findMatchesInObject (obj, crit, strategy, opt) {
|
|||
|
for (const key in obj) {
|
|||
|
if (!isExcluded(obj[key], opt.exclude) && strategy.matches(obj[key], crit)) {
|
|||
|
return obj
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function isExcluded (term, excludedTerms) {
|
|||
|
for (let i = 0, len = excludedTerms.length; i < len; i++) {
|
|||
|
const excludedTerm = excludedTerms[i]
|
|||
|
if (new RegExp(excludedTerm).test(term)) {
|
|||
|
return true
|
|||
|
}
|
|||
|
}
|
|||
|
return false
|
|||
|
}
|
|||
|
|
|||
|
/* globals ActiveXObject:false */
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$JSONLoader_2 = {
|
|||
|
load: load
|
|||
|
}
|
|||
|
|
|||
|
function load (location, callback) {
|
|||
|
const xhr = getXHR()
|
|||
|
xhr.open('GET', location, true)
|
|||
|
xhr.onreadystatechange = createStateChangeListener(xhr, callback)
|
|||
|
xhr.send()
|
|||
|
}
|
|||
|
|
|||
|
function createStateChangeListener (xhr, callback) {
|
|||
|
return function () {
|
|||
|
if (xhr.readyState === 4 && xhr.status === 200) {
|
|||
|
try {
|
|||
|
callback(null, JSON.parse(xhr.responseText))
|
|||
|
} catch (err) {
|
|||
|
callback(err, null)
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function getXHR () {
|
|||
|
return window.XMLHttpRequest ? new window.XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP')
|
|||
|
}
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$OptionsValidator_3 = function OptionsValidator (params) {
|
|||
|
if (!validateParams(params)) {
|
|||
|
throw new Error('-- OptionsValidator: required options missing')
|
|||
|
}
|
|||
|
|
|||
|
if (!(this instanceof OptionsValidator)) {
|
|||
|
return new OptionsValidator(params)
|
|||
|
}
|
|||
|
|
|||
|
const requiredOptions = params.required
|
|||
|
|
|||
|
this.getRequiredOptions = function () {
|
|||
|
return requiredOptions
|
|||
|
}
|
|||
|
|
|||
|
this.validate = function (parameters) {
|
|||
|
const errors = []
|
|||
|
requiredOptions.forEach(function (requiredOptionName) {
|
|||
|
if (typeof parameters[requiredOptionName] === 'undefined') {
|
|||
|
errors.push(requiredOptionName)
|
|||
|
}
|
|||
|
})
|
|||
|
return errors
|
|||
|
}
|
|||
|
|
|||
|
function validateParams (params) {
|
|||
|
if (!params) {
|
|||
|
return false
|
|||
|
}
|
|||
|
return typeof params.required !== 'undefined' && params.required instanceof Array
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
var _$utils_9 = {
|
|||
|
merge: merge,
|
|||
|
isJSON: isJSON
|
|||
|
}
|
|||
|
|
|||
|
function merge (defaultParams, mergeParams) {
|
|||
|
const mergedOptions = {}
|
|||
|
for (const option in defaultParams) {
|
|||
|
mergedOptions[option] = defaultParams[option]
|
|||
|
if (typeof mergeParams[option] !== 'undefined') {
|
|||
|
mergedOptions[option] = mergeParams[option]
|
|||
|
}
|
|||
|
}
|
|||
|
return mergedOptions
|
|||
|
}
|
|||
|
|
|||
|
function isJSON (json) {
|
|||
|
try {
|
|||
|
if (json instanceof Object && JSON.parse(JSON.stringify(json))) {
|
|||
|
return true
|
|||
|
}
|
|||
|
return false
|
|||
|
} catch (err) {
|
|||
|
return false
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
var _$src_8 = {};
|
|||
|
(function (window) {
|
|||
|
'use strict'
|
|||
|
|
|||
|
let options = {
|
|||
|
searchInput: null,
|
|||
|
resultsContainer: null,
|
|||
|
json: [],
|
|||
|
success: Function.prototype,
|
|||
|
searchResultTemplate: '<li><a href="{url}" title="{desc}">{title}</a></li>',
|
|||
|
templateMiddleware: Function.prototype,
|
|||
|
sortMiddleware: function () {
|
|||
|
return 0
|
|||
|
},
|
|||
|
noResultsText: 'No results found',
|
|||
|
limit: 10,
|
|||
|
fuzzy: false,
|
|||
|
debounceTime: null,
|
|||
|
exclude: []
|
|||
|
}
|
|||
|
|
|||
|
let debounceTimerHandle
|
|||
|
const debounce = function (func, delayMillis) {
|
|||
|
if (delayMillis) {
|
|||
|
clearTimeout(debounceTimerHandle)
|
|||
|
debounceTimerHandle = setTimeout(func, delayMillis)
|
|||
|
} else {
|
|||
|
func.call()
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
const requiredOptions = ['searchInput', 'resultsContainer', 'json']
|
|||
|
|
|||
|
/* removed: const _$Templater_7 = require('./Templater') */;
|
|||
|
/* removed: const _$Repository_4 = require('./Repository') */;
|
|||
|
/* removed: const _$JSONLoader_2 = require('./JSONLoader') */;
|
|||
|
const optionsValidator = _$OptionsValidator_3({
|
|||
|
required: requiredOptions
|
|||
|
})
|
|||
|
/* removed: const _$utils_9 = require('./utils') */;
|
|||
|
|
|||
|
window.SimpleJekyllSearch = function (_options) {
|
|||
|
const errors = optionsValidator.validate(_options)
|
|||
|
if (errors.length > 0) {
|
|||
|
throwError('You must specify the following required options: ' + requiredOptions)
|
|||
|
}
|
|||
|
|
|||
|
options = _$utils_9.merge(options, _options)
|
|||
|
|
|||
|
_$Templater_7.setOptions({
|
|||
|
template: options.searchResultTemplate,
|
|||
|
middleware: options.templateMiddleware
|
|||
|
})
|
|||
|
|
|||
|
_$Repository_4.setOptions({
|
|||
|
fuzzy: options.fuzzy,
|
|||
|
limit: options.limit,
|
|||
|
sort: options.sortMiddleware,
|
|||
|
exclude: options.exclude
|
|||
|
})
|
|||
|
|
|||
|
if (_$utils_9.isJSON(options.json)) {
|
|||
|
initWithJSON(options.json)
|
|||
|
} else {
|
|||
|
initWithURL(options.json)
|
|||
|
}
|
|||
|
|
|||
|
const rv = {
|
|||
|
search: search
|
|||
|
}
|
|||
|
|
|||
|
typeof options.success === 'function' && options.success.call(rv)
|
|||
|
return rv
|
|||
|
}
|
|||
|
|
|||
|
function initWithJSON (json) {
|
|||
|
_$Repository_4.put(json)
|
|||
|
registerInput()
|
|||
|
}
|
|||
|
|
|||
|
function initWithURL (url) {
|
|||
|
_$JSONLoader_2.load(url, function (err, json) {
|
|||
|
if (err) {
|
|||
|
throwError('failed to get JSON (' + url + ')')
|
|||
|
}
|
|||
|
initWithJSON(json)
|
|||
|
})
|
|||
|
}
|
|||
|
|
|||
|
function emptyResultsContainer () {
|
|||
|
options.resultsContainer.innerHTML = ''
|
|||
|
}
|
|||
|
|
|||
|
function appendToResultsContainer (text) {
|
|||
|
options.resultsContainer.innerHTML += text
|
|||
|
}
|
|||
|
|
|||
|
function registerInput () {
|
|||
|
options.searchInput.addEventListener('input', function (e) {
|
|||
|
if (isWhitelistedKey(e.which)) {
|
|||
|
emptyResultsContainer()
|
|||
|
debounce(function () { search(e.target.value) }, options.debounceTime)
|
|||
|
}
|
|||
|
})
|
|||
|
}
|
|||
|
|
|||
|
function search (query) {
|
|||
|
if (isValidQuery(query)) {
|
|||
|
emptyResultsContainer()
|
|||
|
render(_$Repository_4.search(query), query)
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function render (results, query) {
|
|||
|
const len = results.length
|
|||
|
if (len === 0) {
|
|||
|
return appendToResultsContainer(options.noResultsText)
|
|||
|
}
|
|||
|
for (let i = 0; i < len; i++) {
|
|||
|
results[i].query = query
|
|||
|
appendToResultsContainer(_$Templater_7.compile(results[i]))
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function isValidQuery (query) {
|
|||
|
return query && query.length > 0
|
|||
|
}
|
|||
|
|
|||
|
function isWhitelistedKey (key) {
|
|||
|
return [13, 16, 20, 37, 38, 39, 40, 91].indexOf(key) === -1
|
|||
|
}
|
|||
|
|
|||
|
function throwError (message) {
|
|||
|
throw new Error('SimpleJekyllSearch --- ' + message)
|
|||
|
}
|
|||
|
})(window)
|
|||
|
|
|||
|
}());
|
|||
|
</script>
|
|||
|
|
|||
|
<!-- Configuration -->
|
|||
|
<script>
|
|||
|
SimpleJekyllSearch({
|
|||
|
searchInput: document.getElementById('search-input'),
|
|||
|
resultsContainer: document.getElementById('results-container'),
|
|||
|
noResultsText: '<p>Aucun résultat!</p>',
|
|||
|
json: '/search.json',
|
|||
|
searchResultTemplate: '<li><a href="{url}">{date} {title}</a> (Création {create})</li>'
|
|||
|
})
|
|||
|
</script>
|
|||
|
|
|||
|
</div></div>
|
|||
|
|
|||
|
|
|||
|
<script>(function() {
|
|||
|
var SOURCES = window.TEXT_VARIABLES.sources;
|
|||
|
window.Lazyload.js(SOURCES.jquery, function() {
|
|||
|
function scrollToAnchor(anchor, duration, callback) {
|
|||
|
var $root = this;
|
|||
|
$root.animate({ scrollTop: $(anchor).position().top }, duration, function() {
|
|||
|
window.history.replaceState(null, '', window.location.href.split('#')[0] + anchor);
|
|||
|
callback && callback();
|
|||
|
});
|
|||
|
}
|
|||
|
$.fn.scrollToAnchor = scrollToAnchor;
|
|||
|
});
|
|||
|
})();
|
|||
|
(function() {
|
|||
|
var SOURCES = window.TEXT_VARIABLES.sources;
|
|||
|
window.Lazyload.js(SOURCES.jquery, function() {
|
|||
|
function affix(options) {
|
|||
|
var $root = this, $window = $(window), $scrollTarget, $scroll,
|
|||
|
offsetBottom = 0, scrollTarget = window, scroll = window.document, disabled = false, isOverallScroller = true,
|
|||
|
rootTop, rootLeft, rootHeight, scrollBottom, rootBottomTop,
|
|||
|
hasInit = false, curState;
|
|||
|
|
|||
|
function setOptions(options) {
|
|||
|
var _options = options || {};
|
|||
|
_options.offsetBottom && (offsetBottom = _options.offsetBottom);
|
|||
|
_options.scrollTarget && (scrollTarget = _options.scrollTarget);
|
|||
|
_options.scroll && (scroll = _options.scroll);
|
|||
|
_options.disabled !== undefined && (disabled = _options.disabled);
|
|||
|
$scrollTarget = $(scrollTarget);
|
|||
|
isOverallScroller = window.isOverallScroller($scrollTarget[0]);
|
|||
|
$scroll = $(scroll);
|
|||
|
}
|
|||
|
function preCalc() {
|
|||
|
top();
|
|||
|
rootHeight = $root.outerHeight();
|
|||
|
rootTop = $root.offset().top + (isOverallScroller ? 0 : $scrollTarget.scrollTop());
|
|||
|
rootLeft = $root.offset().left;
|
|||
|
}
|
|||
|
function calc(needPreCalc) {
|
|||
|
needPreCalc && preCalc();
|
|||
|
scrollBottom = $scroll.outerHeight() - offsetBottom - rootHeight;
|
|||
|
rootBottomTop = scrollBottom - rootTop;
|
|||
|
}
|
|||
|
function top() {
|
|||
|
if (curState !== 'top') {
|
|||
|
$root.removeClass('fixed').css({
|
|||
|
left: 0,
|
|||
|
top: 0
|
|||
|
});
|
|||
|
curState = 'top';
|
|||
|
}
|
|||
|
}
|
|||
|
function fixed() {
|
|||
|
if (curState !== 'fixed') {
|
|||
|
$root.addClass('fixed').css({
|
|||
|
left: rootLeft + 'px',
|
|||
|
top: 0
|
|||
|
});
|
|||
|
curState = 'fixed';
|
|||
|
}
|
|||
|
}
|
|||
|
function bottom() {
|
|||
|
if (curState !== 'bottom') {
|
|||
|
$root.removeClass('fixed').css({
|
|||
|
left: 0,
|
|||
|
top: rootBottomTop + 'px'
|
|||
|
});
|
|||
|
curState = 'bottom';
|
|||
|
}
|
|||
|
}
|
|||
|
function setState() {
|
|||
|
var scrollTop = $scrollTarget.scrollTop();
|
|||
|
if (scrollTop >= rootTop && scrollTop <= scrollBottom) {
|
|||
|
fixed();
|
|||
|
} else if (scrollTop < rootTop) {
|
|||
|
top();
|
|||
|
} else {
|
|||
|
bottom();
|
|||
|
}
|
|||
|
}
|
|||
|
function init() {
|
|||
|
if(!hasInit) {
|
|||
|
var interval, timeout;
|
|||
|
calc(true); setState();
|
|||
|
// run calc every 100 millisecond
|
|||
|
interval = setInterval(function() {
|
|||
|
calc();
|
|||
|
}, 100);
|
|||
|
timeout = setTimeout(function() {
|
|||
|
clearInterval(interval);
|
|||
|
}, 45000);
|
|||
|
window.pageLoad.then(function() {
|
|||
|
setTimeout(function() {
|
|||
|
clearInterval(interval);
|
|||
|
clearTimeout(timeout);
|
|||
|
}, 3000);
|
|||
|
});
|
|||
|
$scrollTarget.on('scroll', function() {
|
|||
|
disabled || setState();
|
|||
|
});
|
|||
|
$window.on('resize', function() {
|
|||
|
disabled || (calc(true), setState());
|
|||
|
});
|
|||
|
hasInit = true;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
setOptions(options);
|
|||
|
if (!disabled) {
|
|||
|
init();
|
|||
|
}
|
|||
|
$window.on('resize', window.throttle(function() {
|
|||
|
init();
|
|||
|
}, 200));
|
|||
|
return {
|
|||
|
setOptions: setOptions,
|
|||
|
refresh: function() {
|
|||
|
calc(true, { animation: false }); setState();
|
|||
|
}
|
|||
|
};
|
|||
|
}
|
|||
|
$.fn.affix = affix;
|
|||
|
});
|
|||
|
})();
|
|||
|
(function() {
|
|||
|
var SOURCES = window.TEXT_VARIABLES.sources;
|
|||
|
window.Lazyload.js(SOURCES.jquery, function() {
|
|||
|
function toc(options) {
|
|||
|
var $root = this, $window = $(window), $scrollTarget, $scroller, $tocUl = $('<ul class="toc toc--ellipsis"></ul>'), $tocLi, $headings, $activeLast, $activeCur,
|
|||
|
selectors = 'h1,h2,h3', container = 'body', scrollTarget = window, scroller = 'html, body', disabled = false,
|
|||
|
headingsPos, scrolling = false, hasRendered = false, hasInit = false;
|
|||
|
|
|||
|
function setOptions(options) {
|
|||
|
var _options = options || {};
|
|||
|
_options.selectors && (selectors = _options.selectors);
|
|||
|
_options.container && (container = _options.container);
|
|||
|
_options.scrollTarget && (scrollTarget = _options.scrollTarget);
|
|||
|
_options.scroller && (scroller = _options.scroller);
|
|||
|
_options.disabled !== undefined && (disabled = _options.disabled);
|
|||
|
$headings = $(container).find(selectors).filter('[id]');
|
|||
|
$scrollTarget = $(scrollTarget);
|
|||
|
$scroller = $(scroller);
|
|||
|
}
|
|||
|
function calc() {
|
|||
|
headingsPos = [];
|
|||
|
$headings.each(function() {
|
|||
|
headingsPos.push(Math.floor($(this).position().top));
|
|||
|
});
|
|||
|
}
|
|||
|
function setState(element, disabled) {
|
|||
|
var scrollTop = $scrollTarget.scrollTop(), i;
|
|||
|
if (disabled || !headingsPos || headingsPos.length < 1) { return; }
|
|||
|
if (element) {
|
|||
|
$activeCur = element;
|
|||
|
} else {
|
|||
|
for (i = 0; i < headingsPos.length; i++) {
|
|||
|
if (scrollTop >= headingsPos[i]) {
|
|||
|
$activeCur = $tocLi.eq(i);
|
|||
|
} else {
|
|||
|
$activeCur || ($activeCur = $tocLi.eq(i));
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
$activeLast && $activeLast.removeClass('active');
|
|||
|
($activeLast = $activeCur).addClass('active');
|
|||
|
}
|
|||
|
function render() {
|
|||
|
if(!hasRendered) {
|
|||
|
$root.append($tocUl);
|
|||
|
$headings.each(function() {
|
|||
|
var $this = $(this);
|
|||
|
$tocUl.append($('<li></li>').addClass('toc-' + $this.prop('tagName').toLowerCase())
|
|||
|
.append($('<a></a>').text($this.text()).attr('href', '#' + $this.prop('id'))));
|
|||
|
});
|
|||
|
$tocLi = $tocUl.children('li');
|
|||
|
$tocUl.on('click', 'a', function(e) {
|
|||
|
e.preventDefault();
|
|||
|
var $this = $(this);
|
|||
|
scrolling = true;
|
|||
|
setState($this.parent());
|
|||
|
$scroller.scrollToAnchor($this.attr('href'), 400, function() {
|
|||
|
scrolling = false;
|
|||
|
});
|
|||
|
});
|
|||
|
}
|
|||
|
hasRendered = true;
|
|||
|
}
|
|||
|
function init() {
|
|||
|
var interval, timeout;
|
|||
|
if(!hasInit) {
|
|||
|
render(); calc(); setState(null, scrolling);
|
|||
|
// run calc every 100 millisecond
|
|||
|
interval = setInterval(function() {
|
|||
|
calc();
|
|||
|
}, 100);
|
|||
|
timeout = setTimeout(function() {
|
|||
|
clearInterval(interval);
|
|||
|
}, 45000);
|
|||
|
window.pageLoad.then(function() {
|
|||
|
setTimeout(function() {
|
|||
|
clearInterval(interval);
|
|||
|
clearTimeout(timeout);
|
|||
|
}, 3000);
|
|||
|
});
|
|||
|
$scrollTarget.on('scroll', function() {
|
|||
|
disabled || setState(null, scrolling);
|
|||
|
});
|
|||
|
$window.on('resize', window.throttle(function() {
|
|||
|
if (!disabled) {
|
|||
|
render(); calc(); setState(null, scrolling);
|
|||
|
}
|
|||
|
}, 100));
|
|||
|
}
|
|||
|
hasInit = true;
|
|||
|
}
|
|||
|
|
|||
|
setOptions(options);
|
|||
|
if (!disabled) {
|
|||
|
init();
|
|||
|
}
|
|||
|
$window.on('resize', window.throttle(function() {
|
|||
|
init();
|
|||
|
}, 200));
|
|||
|
return {
|
|||
|
setOptions: setOptions
|
|||
|
};
|
|||
|
}
|
|||
|
$.fn.toc = toc;
|
|||
|
});
|
|||
|
})();
|
|||
|
/*(function () {
|
|||
|
|
|||
|
})();*/
|
|||
|
</script><script>
|
|||
|
/* toc must before affix, since affix need to konw toc' height. */(function() {
|
|||
|
var SOURCES = window.TEXT_VARIABLES.sources;
|
|||
|
var TOC_SELECTOR = window.TEXT_VARIABLES.site.toc.selectors;
|
|||
|
window.Lazyload.js(SOURCES.jquery, function() {
|
|||
|
var $window = $(window);
|
|||
|
var $articleContent = $('.js-article-content');
|
|||
|
var $tocRoot = $('.js-toc-root'), $col2 = $('.js-col-aside');
|
|||
|
var toc;
|
|||
|
var tocDisabled = false;
|
|||
|
var hasSidebar = $('.js-page-root').hasClass('layout--page--sidebar');
|
|||
|
var hasToc = $articleContent.find(TOC_SELECTOR).length > 0;
|
|||
|
|
|||
|
function disabled() {
|
|||
|
return $col2.css('display') === 'none' || !hasToc;
|
|||
|
}
|
|||
|
|
|||
|
tocDisabled = disabled();
|
|||
|
|
|||
|
toc = $tocRoot.toc({
|
|||
|
selectors: TOC_SELECTOR,
|
|||
|
container: $articleContent,
|
|||
|
scrollTarget: hasSidebar ? '.js-page-main' : null,
|
|||
|
scroller: hasSidebar ? '.js-page-main' : null,
|
|||
|
disabled: tocDisabled
|
|||
|
});
|
|||
|
|
|||
|
$window.on('resize', window.throttle(function() {
|
|||
|
tocDisabled = disabled();
|
|||
|
toc && toc.setOptions({
|
|||
|
disabled: tocDisabled
|
|||
|
});
|
|||
|
}, 100));
|
|||
|
|
|||
|
});
|
|||
|
})();
|
|||
|
(function() {
|
|||
|
var SOURCES = window.TEXT_VARIABLES.sources;
|
|||
|
window.Lazyload.js(SOURCES.jquery, function() {
|
|||
|
var $window = $(window), $pageFooter = $('.js-page-footer');
|
|||
|
var $pageAside = $('.js-page-aside');
|
|||
|
var affix;
|
|||
|
var tocDisabled = false;
|
|||
|
var hasSidebar = $('.js-page-root').hasClass('layout--page--sidebar');
|
|||
|
|
|||
|
affix = $pageAside.affix({
|
|||
|
offsetBottom: $pageFooter.outerHeight(),
|
|||
|
scrollTarget: hasSidebar ? '.js-page-main' : null,
|
|||
|
scroller: hasSidebar ? '.js-page-main' : null,
|
|||
|
scroll: hasSidebar ? $('.js-page-main').children() : null,
|
|||
|
disabled: tocDisabled
|
|||
|
});
|
|||
|
|
|||
|
$window.on('resize', window.throttle(function() {
|
|||
|
affix && affix.setOptions({
|
|||
|
disabled: tocDisabled
|
|||
|
});
|
|||
|
}, 100));
|
|||
|
|
|||
|
window.pageAsideAffix = affix;
|
|||
|
});
|
|||
|
})();
|
|||
|
</script><!---->
|
|||
|
|
|||
|
</div>
|
|||
|
<script>(function () {
|
|||
|
var $root = document.getElementsByClassName('root')[0];
|
|||
|
if (window.hasEvent('touchstart')) {
|
|||
|
$root.dataset.isTouch = true;
|
|||
|
document.addEventListener('touchstart', function(){}, false);
|
|||
|
}
|
|||
|
})();
|
|||
|
</script>
|
|||
|
</body>
|
|||
|
</html>
|
|||
|
|