commit
3028a18a37
13
.github/ISSUE_TEMPLATE/pr_to_nighty_template.yml
vendored
Normal file
13
.github/ISSUE_TEMPLATE/pr_to_nighty_template.yml
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
## :memo: Brief description
|
||||||
|
<!-- Diff summary - START -->
|
||||||
|
<!-- Diff summary - END -->
|
||||||
|
|
||||||
|
|
||||||
|
## :computer: Commits
|
||||||
|
<!-- Diff commits - START -->
|
||||||
|
<!-- Diff commits - END -->
|
||||||
|
|
||||||
|
|
||||||
|
## :file_folder: Modified files
|
||||||
|
<!-- Diff files - START -->
|
||||||
|
<!-- Diff files - END -->
|
@ -14,7 +14,7 @@ jobs:
|
|||||||
pull-requests: write
|
pull-requests: write
|
||||||
steps:
|
steps:
|
||||||
- name: Mark/Close Stale Issues and Pull Requests 🗑️
|
- name: Mark/Close Stale Issues and Pull Requests 🗑️
|
||||||
uses: actions/stale@v6.0.0
|
uses: actions/stale@v6.0.1
|
||||||
with:
|
with:
|
||||||
repo-token: ${{ secrets.STALE_ACTION_PAT }}
|
repo-token: ${{ secrets.STALE_ACTION_PAT }}
|
||||||
days-before-stale: 60
|
days-before-stale: 60
|
||||||
|
25
.github/workflows/pr_to_nightly.yml
vendored
Normal file
25
.github/workflows/pr_to_nightly.yml
vendored
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
name: Create PR to merge to nightly from staging
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- staging
|
||||||
|
jobs:
|
||||||
|
action-pull-request:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Run the Action
|
||||||
|
uses: devops-infra/action-pull-request@v0.5.1
|
||||||
|
with:
|
||||||
|
github_token: ${{ secrets.PRTONIGHTLY_ACTION_PAT }}
|
||||||
|
title: Automatic PR to nightly from ${{ github.event.repository.updated_at}}
|
||||||
|
assignee: DerLinkman
|
||||||
|
source_branch: staging
|
||||||
|
target_branch: nightly
|
||||||
|
reviewer: DerLinkman
|
||||||
|
label: upstream
|
||||||
|
template: .github/ISSUE_TEMPLATE/pr_to_nighty_template.yml
|
||||||
|
get_diff: true
|
3
data/Dockerfiles/backup/Dockerfile
Normal file
3
data/Dockerfiles/backup/Dockerfile
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
FROM debian:bullseye-slim
|
||||||
|
|
||||||
|
RUN apt update && apt install pigz
|
@ -252,7 +252,7 @@ def permBan(net, unban=False):
|
|||||||
if rule not in chain.rules and not unban:
|
if rule not in chain.rules and not unban:
|
||||||
logCrit('Add host/network %s to blacklist' % net)
|
logCrit('Add host/network %s to blacklist' % net)
|
||||||
chain.insert_rule(rule)
|
chain.insert_rule(rule)
|
||||||
r.hset('F2B_PERM_BANS', '%s' % net, int(round(time.time())))
|
r.hset('F2B_PERM_BANS', '%s' % net, int(round(time.time())))
|
||||||
elif rule in chain.rules and unban:
|
elif rule in chain.rules and unban:
|
||||||
logCrit('Remove host/network %s from blacklist' % net)
|
logCrit('Remove host/network %s from blacklist' % net)
|
||||||
chain.delete_rule(rule)
|
chain.delete_rule(rule)
|
||||||
@ -267,7 +267,7 @@ def permBan(net, unban=False):
|
|||||||
if rule not in chain.rules and not unban:
|
if rule not in chain.rules and not unban:
|
||||||
logCrit('Add host/network %s to blacklist' % net)
|
logCrit('Add host/network %s to blacklist' % net)
|
||||||
chain.insert_rule(rule)
|
chain.insert_rule(rule)
|
||||||
r.hset('F2B_PERM_BANS', '%s' % net, int(round(time.time())))
|
r.hset('F2B_PERM_BANS', '%s' % net, int(round(time.time())))
|
||||||
elif rule in chain.rules and unban:
|
elif rule in chain.rules and unban:
|
||||||
logCrit('Remove host/network %s from blacklist' % net)
|
logCrit('Remove host/network %s from blacklist' % net)
|
||||||
chain.delete_rule(rule)
|
chain.delete_rule(rule)
|
||||||
@ -346,6 +346,8 @@ def snat4(snat_target):
|
|||||||
rule.dst = '!' + rule.src
|
rule.dst = '!' + rule.src
|
||||||
target = rule.create_target("SNAT")
|
target = rule.create_target("SNAT")
|
||||||
target.to_source = snat_target
|
target.to_source = snat_target
|
||||||
|
match = rule.create_match("comment")
|
||||||
|
match.comment = f'{int(round(time.time()))}'
|
||||||
return rule
|
return rule
|
||||||
|
|
||||||
while not quit_now:
|
while not quit_now:
|
||||||
@ -356,19 +358,26 @@ def snat4(snat_target):
|
|||||||
table.refresh()
|
table.refresh()
|
||||||
chain = iptc.Chain(table, 'POSTROUTING')
|
chain = iptc.Chain(table, 'POSTROUTING')
|
||||||
table.autocommit = False
|
table.autocommit = False
|
||||||
if get_snat4_rule() not in chain.rules:
|
new_rule = get_snat4_rule()
|
||||||
logCrit('Added POSTROUTING rule for source network %s to SNAT target %s' % (get_snat4_rule().src, snat_target))
|
for position, rule in enumerate(chain.rules):
|
||||||
chain.insert_rule(get_snat4_rule())
|
match = all((
|
||||||
table.commit()
|
new_rule.get_src() == rule.get_src(),
|
||||||
else:
|
new_rule.get_dst() == rule.get_dst(),
|
||||||
for position, item in enumerate(chain.rules):
|
new_rule.target.parameters == rule.target.parameters,
|
||||||
if item == get_snat4_rule():
|
new_rule.target.name == rule.target.name
|
||||||
if position != 0:
|
))
|
||||||
chain.delete_rule(get_snat4_rule())
|
if position == 0:
|
||||||
table.commit()
|
if not match:
|
||||||
|
logInfo(f'Added POSTROUTING rule for source network {new_rule.src} to SNAT target {snat_target}')
|
||||||
|
chain.insert_rule(new_rule)
|
||||||
|
else:
|
||||||
|
if match:
|
||||||
|
logInfo(f'Remove rule for source network {new_rule.src} to SNAT target {snat_target} from POSTROUTING chain at position {position}')
|
||||||
|
chain.delete_rule(rule)
|
||||||
|
table.commit()
|
||||||
table.autocommit = True
|
table.autocommit = True
|
||||||
except:
|
except:
|
||||||
print('Error running SNAT4, retrying...')
|
print('Error running SNAT4, retrying...')
|
||||||
|
|
||||||
def snat6(snat_target):
|
def snat6(snat_target):
|
||||||
global lock
|
global lock
|
||||||
@ -402,7 +411,7 @@ def snat6(snat_target):
|
|||||||
table.commit()
|
table.commit()
|
||||||
table.autocommit = True
|
table.autocommit = True
|
||||||
except:
|
except:
|
||||||
print('Error running SNAT6, retrying...')
|
print('Error running SNAT6, retrying...')
|
||||||
|
|
||||||
def autopurge():
|
def autopurge():
|
||||||
while not quit_now:
|
while not quit_now:
|
||||||
@ -468,7 +477,7 @@ def whitelistUpdate():
|
|||||||
if Counter(new_whitelist) != Counter(WHITELIST):
|
if Counter(new_whitelist) != Counter(WHITELIST):
|
||||||
WHITELIST = new_whitelist
|
WHITELIST = new_whitelist
|
||||||
logInfo('Whitelist was changed, it has %s entries' % len(WHITELIST))
|
logInfo('Whitelist was changed, it has %s entries' % len(WHITELIST))
|
||||||
time.sleep(60.0 - ((time.time() - start_time) % 60.0))
|
time.sleep(60.0 - ((time.time() - start_time) % 60.0))
|
||||||
|
|
||||||
def blacklistUpdate():
|
def blacklistUpdate():
|
||||||
global quit_now
|
global quit_now
|
||||||
@ -479,7 +488,7 @@ def blacklistUpdate():
|
|||||||
new_blacklist = []
|
new_blacklist = []
|
||||||
if list:
|
if list:
|
||||||
new_blacklist = genNetworkList(list)
|
new_blacklist = genNetworkList(list)
|
||||||
if Counter(new_blacklist) != Counter(BLACKLIST):
|
if Counter(new_blacklist) != Counter(BLACKLIST):
|
||||||
addban = set(new_blacklist).difference(BLACKLIST)
|
addban = set(new_blacklist).difference(BLACKLIST)
|
||||||
delban = set(BLACKLIST).difference(new_blacklist)
|
delban = set(BLACKLIST).difference(new_blacklist)
|
||||||
BLACKLIST = new_blacklist
|
BLACKLIST = new_blacklist
|
||||||
@ -490,7 +499,7 @@ def blacklistUpdate():
|
|||||||
if delban:
|
if delban:
|
||||||
for net in delban:
|
for net in delban:
|
||||||
permBan(net=net, unban=True)
|
permBan(net=net, unban=True)
|
||||||
time.sleep(60.0 - ((time.time() - start_time) % 60.0))
|
time.sleep(60.0 - ((time.time() - start_time) % 60.0))
|
||||||
|
|
||||||
def initChain():
|
def initChain():
|
||||||
# Is called before threads start, no locking
|
# Is called before threads start, no locking
|
||||||
|
2
data/web/css/build/007-languages.min.css
vendored
2
data/web/css/build/007-languages.min.css
vendored
File diff suppressed because one or more lines are too long
@ -195,9 +195,65 @@ require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/sessions.inc.php';
|
|||||||
// Set language
|
// Set language
|
||||||
if (!isset($_SESSION['mailcow_locale']) && !isset($_COOKIE['mailcow_locale'])) {
|
if (!isset($_SESSION['mailcow_locale']) && !isset($_COOKIE['mailcow_locale'])) {
|
||||||
if ($DETECT_LANGUAGE && isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
|
if ($DETECT_LANGUAGE && isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
|
||||||
$header_lang = strtolower(substr($_SERVER['HTTP_ACCEPT_LANGUAGE'], 0, 2));
|
// regex inspired from @GabrielAnderson on http://stackoverflow.com/questions/6038236/http-accept-language
|
||||||
if (array_key_exists($header_lang, $AVAILABLE_LANGUAGES)) {
|
preg_match_all('/([a-z]{1,8}(-[a-z]{1,8})*)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $lang_parse);
|
||||||
$_SESSION['mailcow_locale'] = $header_lang;
|
|
||||||
|
$langs = $lang_parse[1];
|
||||||
|
$ranks = $lang_parse[4];
|
||||||
|
|
||||||
|
// (create an associative array 'language' => 'preference')
|
||||||
|
$lang2pref = array();
|
||||||
|
for ($i=0; $i<count($langs); $i++) {
|
||||||
|
$lang2pref[strtolower($langs[$i])] = (float) (!empty($ranks[$i]) ? $ranks[$i] : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// (comparison function for uksort)
|
||||||
|
$cmpLangs = function ($a, $b) use ($lang2pref) {
|
||||||
|
if ($lang2pref[$a] > $lang2pref[$b])
|
||||||
|
return -1;
|
||||||
|
elseif ($lang2pref[$a] < $lang2pref[$b])
|
||||||
|
return 1;
|
||||||
|
elseif (strlen($a) > strlen($b))
|
||||||
|
return -1;
|
||||||
|
elseif (strlen($a) < strlen($b))
|
||||||
|
return 1;
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// sort the languages by prefered language and by the most specific region
|
||||||
|
uksort($lang2pref, $cmpLangs);
|
||||||
|
|
||||||
|
// generate language array without the region part
|
||||||
|
$AVAILABLE_BASE_LANGUAGES=array();
|
||||||
|
foreach ($AVAILABLE_LANGUAGES as $code => $lang) {
|
||||||
|
$base_code = substr($code, 0, 2);
|
||||||
|
if (!array_key_exists($base_code, $AVAILABLE_BASE_LANGUAGES)) {
|
||||||
|
$AVAILABLE_BASE_LANGUAGES[$base_code] = $code;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find a perfect match or partial match
|
||||||
|
// Match en-gb or en
|
||||||
|
foreach ($lang2pref as $lang => $q) {
|
||||||
|
if (array_key_exists($lang, $AVAILABLE_LANGUAGES)) {
|
||||||
|
$_SESSION['mailcow_locale'] = $lang;
|
||||||
|
break;
|
||||||
|
} elseif (array_key_exists($lang, $AVAILABLE_BASE_LANGUAGES)) {
|
||||||
|
$_SESSION['mailcow_locale'] = $AVAILABLE_BASE_LANGUAGES[$lang];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try suggest match
|
||||||
|
// e.g. suggest en-gb when only en-us is provided
|
||||||
|
if (!isset($_COOKIE['mailcow_locale'])) {
|
||||||
|
foreach ($lang2pref as $lang => $q) {
|
||||||
|
if (array_key_exists(substr($lang, 0, 2), $AVAILABLE_BASE_LANGUAGES)) {
|
||||||
|
$_SESSION['mailcow_locale'] = $AVAILABLE_BASE_LANGUAGES[substr($lang, 0, 2)];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -215,7 +271,7 @@ if (isset($_GET['lang']) && array_key_exists($_GET['lang'], $AVAILABLE_LANGUAGES
|
|||||||
/*
|
/*
|
||||||
* load language
|
* load language
|
||||||
*/
|
*/
|
||||||
$lang = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . '/lang/lang.en.json'), true);
|
$lang = json_decode(file_get_contents($_SERVER['DOCUMENT_ROOT'] . '/lang/lang.en-gb.json'), true);
|
||||||
|
|
||||||
$langFile = $_SERVER['DOCUMENT_ROOT'] . '/lang/lang.'.$_SESSION['mailcow_locale'].'.json';
|
$langFile = $_SERVER['DOCUMENT_ROOT'] . '/lang/lang.'.$_SESSION['mailcow_locale'].'.json';
|
||||||
if(file_exists($langFile)) {
|
if(file_exists($langFile)) {
|
||||||
|
@ -76,33 +76,35 @@ $autodiscover_config = array(
|
|||||||
$DETECT_LANGUAGE = true;
|
$DETECT_LANGUAGE = true;
|
||||||
|
|
||||||
// Change default language
|
// Change default language
|
||||||
$DEFAULT_LANG = 'en';
|
$DEFAULT_LANG = 'en-gb';
|
||||||
|
|
||||||
// Available languages
|
// Available languages
|
||||||
// https://www.iso.org/obp/ui/#search
|
// https://www.iso.org/obp/ui/#search
|
||||||
// https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
|
// https://en.wikipedia.org/wiki/IETF_language_tag
|
||||||
$AVAILABLE_LANGUAGES = array(
|
$AVAILABLE_LANGUAGES = array(
|
||||||
'cs' => 'Čeština (Czech)',
|
// 'ca-es' => 'Català (Catalan)',
|
||||||
'da' => 'Danish (Dansk)',
|
'cs-cz' => 'Čeština (Czech)',
|
||||||
'de' => 'Deutsch (German)',
|
'da-dk' => 'Danish (Dansk)',
|
||||||
'en' => 'English',
|
'de-de' => 'Deutsch (German)',
|
||||||
'es' => 'Español (Spanish)',
|
'en-gb' => 'English',
|
||||||
'fi' => 'Suomi (Finish)',
|
'es-es' => 'Español (Spanish)',
|
||||||
'fr' => 'Français (French)',
|
'fi-fi' => 'Suomi (Finish)',
|
||||||
'hu' => 'Magyar (Hungarian)',
|
'fr-fr' => 'Français (French)',
|
||||||
'it' => 'Italiano (Italian)',
|
'hu-hu' => 'Magyar (Hungarian)',
|
||||||
'ko' => '한국어 (Korean)',
|
'it-it' => 'Italiano (Italian)',
|
||||||
'lv' => 'latviešu (Latvian)',
|
'ko-kr' => '한국어 (Korean)',
|
||||||
'nl' => 'Nederlands (Dutch)',
|
'lv-lv' => 'latviešu (Latvian)',
|
||||||
'pl' => 'Język Polski (Polish)',
|
'nl-nl' => 'Nederlands (Dutch)',
|
||||||
'pt' => 'Português (Portuguese)',
|
'pl-pl' => 'Język Polski (Polish)',
|
||||||
'ro' => 'Română (Romanian)',
|
'pt-pt' => 'Português (Portuguese)',
|
||||||
'ru' => 'Pусский (Russian)',
|
'ro-ro' => 'Română (Romanian)',
|
||||||
'sk' => 'Slovenčina (Slovak)',
|
'ru-ru' => 'Pусский (Russian)',
|
||||||
'sv' => 'Svenska (Swedish)',
|
'sk-sk' => 'Slovenčina (Slovak)',
|
||||||
'tr' => 'Türkçe (Turkish)',
|
'sv-se' => 'Svenska (Swedish)',
|
||||||
'uk' => 'Українська (Ukrainian)',
|
'tr-tr' => 'Türkçe (Turkish)',
|
||||||
'zh' => '中文 (Chinese)'
|
'uk-ua' => 'Українська (Ukrainian)',
|
||||||
|
'zh-cn' => '简体中文 (Simplified Chinese)',
|
||||||
|
'zh-tw' => '繁體中文 (Traditional Chinese)',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Change theme (default: lumen)
|
// Change theme (default: lumen)
|
||||||
|
1187
data/web/lang/lang.zh-tw.json
Normal file
1187
data/web/lang/lang.zh-tw.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -32,12 +32,12 @@
|
|||||||
<ul class="nav navbar-nav navbar-right">
|
<ul class="nav navbar-nav navbar-right">
|
||||||
{% if mailcow_locale %}
|
{% if mailcow_locale %}
|
||||||
<li class="dropdown{% if available_languages|length == 1 %}lang-link-disabled{% endif %}">
|
<li class="dropdown{% if available_languages|length == 1 %}lang-link-disabled{% endif %}">
|
||||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><span class="flag-icon flag-icon-{{ mailcow_locale }}"></span><span class="caret"></span></a>
|
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><span class="flag-icon flag-icon-{{ mailcow_locale[-2:] }}"></span><span class="caret"></span></a>
|
||||||
<ul class="dropdown-menu" role="menu">
|
<ul class="dropdown-menu" role="menu">
|
||||||
{% for key, val in available_languages %}
|
{% for key, val in available_languages %}
|
||||||
<li{% if mailcow_locale == key %} class="active"{% endif %}>
|
<li{% if mailcow_locale == key %} class="active"{% endif %}>
|
||||||
<a href="?{{ query_string({'lang': key}) }}">
|
<a href="?{{ query_string({'lang': key}) }}">
|
||||||
<span class="flag-icon flag-icon-{{ key }}"></span>{{ val }}
|
<span class="flag-icon flag-icon-{{ key[-2:] }}"></span>{{ val }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -45,13 +45,13 @@
|
|||||||
</div>
|
</div>
|
||||||
{% if not oauth2_request %}
|
{% if not oauth2_request %}
|
||||||
<button type="button" {% if available_languages|length == 1 %}disabled="true"{% endif %} class="btn btn-xs-lg btn-default pull-right dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
<button type="button" {% if available_languages|length == 1 %}disabled="true"{% endif %} class="btn btn-xs-lg btn-default pull-right dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||||
<span class="flag-icon flag-icon-{{ mailcow_locale }}"></span> <span class="caret"></span>
|
<span class="flag-icon flag-icon-{{ mailcow_locale[-2:] }}"></span> <span class="caret"></span>
|
||||||
</button>
|
</button>
|
||||||
<ul class="dropdown-menu pull-right login">
|
<ul class="dropdown-menu pull-right login">
|
||||||
{% for key, val in available_languages %}
|
{% for key, val in available_languages %}
|
||||||
<li{% if mailcow_locale == key %} class="active"{% endif %}>
|
<li{% if mailcow_locale == key %} class="active"{% endif %}>
|
||||||
<a href="?{{ query_string({'lang': key}) }}">
|
<a href="?{{ query_string({'lang': key}) }}">
|
||||||
<span class="flag-icon flag-icon-{{ key }}"></span>{{ val }}
|
<span class="flag-icon flag-icon-{{ key[-2:] }}"></span>{{ val }}
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
@ -41,7 +41,7 @@ services:
|
|||||||
- mysql
|
- mysql
|
||||||
|
|
||||||
redis-mailcow:
|
redis-mailcow:
|
||||||
image: redis:6-alpine
|
image: redis:7-alpine
|
||||||
volumes:
|
volumes:
|
||||||
- redis-vol-1:/data/
|
- redis-vol-1:/data/
|
||||||
restart: always
|
restart: always
|
||||||
@ -76,7 +76,7 @@ services:
|
|||||||
- clamd
|
- clamd
|
||||||
|
|
||||||
rspamd-mailcow:
|
rspamd-mailcow:
|
||||||
image: mailcow/rspamd:1.90
|
image: mailcow/rspamd:1.91
|
||||||
stop_grace_period: 30s
|
stop_grace_period: 30s
|
||||||
depends_on:
|
depends_on:
|
||||||
- dovecot-mailcow
|
- dovecot-mailcow
|
||||||
|
@ -26,7 +26,7 @@ if(empty($targetLang)) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// load master lang
|
// load master lang
|
||||||
$masterLang = file_get_contents(__DIR__.'/../data/web/lang/lang.en.json');
|
$masterLang = file_get_contents(__DIR__.'/../data/web/lang/lang.en-gb.json');
|
||||||
$masterLang = json_decode($masterLang, true);
|
$masterLang = json_decode($masterLang, true);
|
||||||
|
|
||||||
// load target lang
|
// load target lang
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
DEBIAN_DOCKER_IMAGE="debian:bullseye-slim"
|
DEBIAN_DOCKER_IMAGE="mailcow/backup:1.0"
|
||||||
|
|
||||||
if [[ ! -z ${MAILCOW_BACKUP_LOCATION} ]]; then
|
if [[ ! -z ${MAILCOW_BACKUP_LOCATION} ]]; then
|
||||||
BACKUP_LOCATION="${MAILCOW_BACKUP_LOCATION}"
|
BACKUP_LOCATION="${MAILCOW_BACKUP_LOCATION}"
|
||||||
@ -52,6 +52,15 @@ BACKUP_LOCATION=$(echo ${BACKUP_LOCATION} | sed 's#/$##')
|
|||||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
COMPOSE_FILE=${SCRIPT_DIR}/../docker-compose.yml
|
COMPOSE_FILE=${SCRIPT_DIR}/../docker-compose.yml
|
||||||
ENV_FILE=${SCRIPT_DIR}/../.env
|
ENV_FILE=${SCRIPT_DIR}/../.env
|
||||||
|
THREADS=$(echo ${THREADS:-1})
|
||||||
|
|
||||||
|
if ! [[ "${THREADS}" =~ ^[1-9]+$ ]] ; then
|
||||||
|
echo "Thread input is not a number!"
|
||||||
|
exit 1
|
||||||
|
elif [[ "${THREADS}" =~ ^[1-9]+$ ]] ; then
|
||||||
|
echo "Using ${THREADS} Thread(s) for this run."
|
||||||
|
echo "Notice: You can set the Thread count with the THREADS Variable before you run this script."
|
||||||
|
fi
|
||||||
|
|
||||||
if [ ! -f ${COMPOSE_FILE} ]; then
|
if [ ! -f ${COMPOSE_FILE} ]; then
|
||||||
echo "Compose file not found"
|
echo "Compose file not found"
|
||||||
@ -99,32 +108,32 @@ function backup() {
|
|||||||
docker run --name mailcow-backup --rm \
|
docker run --name mailcow-backup --rm \
|
||||||
-v ${BACKUP_LOCATION}/mailcow-${DATE}:/backup:z \
|
-v ${BACKUP_LOCATION}/mailcow-${DATE}:/backup:z \
|
||||||
-v $(docker volume ls -qf name=^${CMPS_PRJ}_vmail-vol-1$):/vmail:ro,z \
|
-v $(docker volume ls -qf name=^${CMPS_PRJ}_vmail-vol-1$):/vmail:ro,z \
|
||||||
${DEBIAN_DOCKER_IMAGE} /bin/tar --warning='no-file-ignored' --use-compress-program="gzip --rsyncable" -Pcvpf /backup/backup_vmail.tar.gz /vmail
|
${DEBIAN_DOCKER_IMAGE} /bin/tar --warning='no-file-ignored' --use-compress-program="pigz --rsyncable -p ${THREADS}" -Pcvpf /backup/backup_vmail.tar.gz /vmail
|
||||||
;;&
|
;;&
|
||||||
crypt|all)
|
crypt|all)
|
||||||
docker run --name mailcow-backup --rm \
|
docker run --name mailcow-backup --rm \
|
||||||
-v ${BACKUP_LOCATION}/mailcow-${DATE}:/backup:z \
|
-v ${BACKUP_LOCATION}/mailcow-${DATE}:/backup:z \
|
||||||
-v $(docker volume ls -qf name=^${CMPS_PRJ}_crypt-vol-1$):/crypt:ro,z \
|
-v $(docker volume ls -qf name=^${CMPS_PRJ}_crypt-vol-1$):/crypt:ro,z \
|
||||||
${DEBIAN_DOCKER_IMAGE} /bin/tar --warning='no-file-ignored' --use-compress-program="gzip --rsyncable" -Pcvpf /backup/backup_crypt.tar.gz /crypt
|
${DEBIAN_DOCKER_IMAGE} /bin/tar --warning='no-file-ignored' --use-compress-program="pigz --rsyncable -p ${THREADS}" -Pcvpf /backup/backup_crypt.tar.gz /crypt
|
||||||
;;&
|
;;&
|
||||||
redis|all)
|
redis|all)
|
||||||
docker exec $(docker ps -qf name=redis-mailcow) redis-cli save
|
docker exec $(docker ps -qf name=redis-mailcow) redis-cli save
|
||||||
docker run --name mailcow-backup --rm \
|
docker run --name mailcow-backup --rm \
|
||||||
-v ${BACKUP_LOCATION}/mailcow-${DATE}:/backup:z \
|
-v ${BACKUP_LOCATION}/mailcow-${DATE}:/backup:z \
|
||||||
-v $(docker volume ls -qf name=^${CMPS_PRJ}_redis-vol-1$):/redis:ro,z \
|
-v $(docker volume ls -qf name=^${CMPS_PRJ}_redis-vol-1$):/redis:ro,z \
|
||||||
${DEBIAN_DOCKER_IMAGE} /bin/tar --warning='no-file-ignored' --use-compress-program="gzip --rsyncable" -Pcvpf /backup/backup_redis.tar.gz /redis
|
${DEBIAN_DOCKER_IMAGE} /bin/tar --warning='no-file-ignored' --use-compress-program="pigz --rsyncable -p ${THREADS}" -Pcvpf /backup/backup_redis.tar.gz /redis
|
||||||
;;&
|
;;&
|
||||||
rspamd|all)
|
rspamd|all)
|
||||||
docker run --name mailcow-backup --rm \
|
docker run --name mailcow-backup --rm \
|
||||||
-v ${BACKUP_LOCATION}/mailcow-${DATE}:/backup:z \
|
-v ${BACKUP_LOCATION}/mailcow-${DATE}:/backup:z \
|
||||||
-v $(docker volume ls -qf name=^${CMPS_PRJ}_rspamd-vol-1$):/rspamd:ro,z \
|
-v $(docker volume ls -qf name=^${CMPS_PRJ}_rspamd-vol-1$):/rspamd:ro,z \
|
||||||
${DEBIAN_DOCKER_IMAGE} /bin/tar --warning='no-file-ignored' --use-compress-program="gzip --rsyncable" -Pcvpf /backup/backup_rspamd.tar.gz /rspamd
|
${DEBIAN_DOCKER_IMAGE} /bin/tar --warning='no-file-ignored' --use-compress-program="pigz --rsyncable -p ${THREADS}" -Pcvpf /backup/backup_rspamd.tar.gz /rspamd
|
||||||
;;&
|
;;&
|
||||||
postfix|all)
|
postfix|all)
|
||||||
docker run --name mailcow-backup --rm \
|
docker run --name mailcow-backup --rm \
|
||||||
-v ${BACKUP_LOCATION}/mailcow-${DATE}:/backup:z \
|
-v ${BACKUP_LOCATION}/mailcow-${DATE}:/backup:z \
|
||||||
-v $(docker volume ls -qf name=^${CMPS_PRJ}_postfix-vol-1$):/postfix:ro,z \
|
-v $(docker volume ls -qf name=^${CMPS_PRJ}_postfix-vol-1$):/postfix:ro,z \
|
||||||
${DEBIAN_DOCKER_IMAGE} /bin/tar --warning='no-file-ignored' --use-compress-program="gzip --rsyncable" -Pcvpf /backup/backup_postfix.tar.gz /postfix
|
${DEBIAN_DOCKER_IMAGE} /bin/tar --warning='no-file-ignored' --use-compress-program="pigz --rsyncable -p ${THREADS}" -Pcvpf /backup/backup_postfix.tar.gz /postfix
|
||||||
;;&
|
;;&
|
||||||
mysql|all)
|
mysql|all)
|
||||||
SQLIMAGE=$(grep -iEo '(mysql|mariadb)\:.+' ${COMPOSE_FILE})
|
SQLIMAGE=$(grep -iEo '(mysql|mariadb)\:.+' ${COMPOSE_FILE})
|
||||||
@ -191,7 +200,7 @@ function restore() {
|
|||||||
docker run -it --name mailcow-backup --rm \
|
docker run -it --name mailcow-backup --rm \
|
||||||
-v ${RESTORE_LOCATION}:/backup:z \
|
-v ${RESTORE_LOCATION}:/backup:z \
|
||||||
-v $(docker volume ls -qf name=^${CMPS_PRJ}_vmail-vol-1$):/vmail:z \
|
-v $(docker volume ls -qf name=^${CMPS_PRJ}_vmail-vol-1$):/vmail:z \
|
||||||
${DEBIAN_DOCKER_IMAGE} /bin/tar -Pxvzf /backup/backup_vmail.tar.gz
|
${DEBIAN_DOCKER_IMAGE} /bin/tar --use-compress-program="pigz -d -p ${THREADS}" -Pxvf /backup/backup_vmail.tar.gz
|
||||||
docker start $(docker ps -aqf name=dovecot-mailcow)
|
docker start $(docker ps -aqf name=dovecot-mailcow)
|
||||||
echo
|
echo
|
||||||
echo "In most cases it is not required to run a full resync, you can run the command printed below at any time after testing wether the restore process broke a mailbox:"
|
echo "In most cases it is not required to run a full resync, you can run the command printed below at any time after testing wether the restore process broke a mailbox:"
|
||||||
@ -210,7 +219,7 @@ function restore() {
|
|||||||
docker run -it --name mailcow-backup --rm \
|
docker run -it --name mailcow-backup --rm \
|
||||||
-v ${RESTORE_LOCATION}:/backup:z \
|
-v ${RESTORE_LOCATION}:/backup:z \
|
||||||
-v $(docker volume ls -qf name=^${CMPS_PRJ}_redis-vol-1$):/redis:z \
|
-v $(docker volume ls -qf name=^${CMPS_PRJ}_redis-vol-1$):/redis:z \
|
||||||
${DEBIAN_DOCKER_IMAGE} /bin/tar -Pxvzf /backup/backup_redis.tar.gz
|
${DEBIAN_DOCKER_IMAGE} /bin/tar --use-compress-program="pigz -d -p ${THREADS}" -Pxvf /backup/backup_redis.tar.gz
|
||||||
docker start $(docker ps -aqf name=redis-mailcow)
|
docker start $(docker ps -aqf name=redis-mailcow)
|
||||||
;;
|
;;
|
||||||
crypt)
|
crypt)
|
||||||
@ -218,7 +227,7 @@ function restore() {
|
|||||||
docker run -it --name mailcow-backup --rm \
|
docker run -it --name mailcow-backup --rm \
|
||||||
-v ${RESTORE_LOCATION}:/backup:z \
|
-v ${RESTORE_LOCATION}:/backup:z \
|
||||||
-v $(docker volume ls -qf name=^${CMPS_PRJ}_crypt-vol-1$):/crypt:z \
|
-v $(docker volume ls -qf name=^${CMPS_PRJ}_crypt-vol-1$):/crypt:z \
|
||||||
${DEBIAN_DOCKER_IMAGE} /bin/tar -Pxvzf /backup/backup_crypt.tar.gz
|
${DEBIAN_DOCKER_IMAGE} /bin/tar --use-compress-program="pigz -d -p ${THREADS}" -Pxvf /backup/backup_crypt.tar.gz
|
||||||
docker start $(docker ps -aqf name=dovecot-mailcow)
|
docker start $(docker ps -aqf name=dovecot-mailcow)
|
||||||
;;
|
;;
|
||||||
rspamd)
|
rspamd)
|
||||||
@ -226,7 +235,7 @@ function restore() {
|
|||||||
docker run -it --name mailcow-backup --rm \
|
docker run -it --name mailcow-backup --rm \
|
||||||
-v ${RESTORE_LOCATION}:/backup:z \
|
-v ${RESTORE_LOCATION}:/backup:z \
|
||||||
-v $(docker volume ls -qf name=^${CMPS_PRJ}_rspamd-vol-1$):/rspamd:z \
|
-v $(docker volume ls -qf name=^${CMPS_PRJ}_rspamd-vol-1$):/rspamd:z \
|
||||||
${DEBIAN_DOCKER_IMAGE} /bin/tar -Pxvzf /backup/backup_rspamd.tar.gz
|
${DEBIAN_DOCKER_IMAGE} /bin/tar --use-compress-program="pigz -d -p ${THREADS}" -Pxvf /backup/backup_rspamd.tar.gz
|
||||||
docker start $(docker ps -aqf name=rspamd-mailcow)
|
docker start $(docker ps -aqf name=rspamd-mailcow)
|
||||||
;;
|
;;
|
||||||
postfix)
|
postfix)
|
||||||
@ -234,7 +243,7 @@ function restore() {
|
|||||||
docker run -it --name mailcow-backup --rm \
|
docker run -it --name mailcow-backup --rm \
|
||||||
-v ${RESTORE_LOCATION}:/backup:z \
|
-v ${RESTORE_LOCATION}:/backup:z \
|
||||||
-v $(docker volume ls -qf name=^${CMPS_PRJ}_postfix-vol-1$):/postfix:z \
|
-v $(docker volume ls -qf name=^${CMPS_PRJ}_postfix-vol-1$):/postfix:z \
|
||||||
${DEBIAN_DOCKER_IMAGE} /bin/tar -Pxvzf /backup/backup_postfix.tar.gz
|
${DEBIAN_DOCKER_IMAGE} /bin/tar --use-compress-program="pigz -d -p ${THREADS}" -Pxvf /backup/backup_postfix.tar.gz
|
||||||
docker start $(docker ps -aqf name=postfix-mailcow)
|
docker start $(docker ps -aqf name=postfix-mailcow)
|
||||||
;;
|
;;
|
||||||
mysql|mariadb)
|
mysql|mariadb)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env ruby
|
#!/usr/bin/env ruby
|
||||||
|
|
||||||
MASTER="en"
|
MASTER="en-gb"
|
||||||
|
|
||||||
DIR = "#{__dir__}/.."
|
DIR = "#{__dir__}/.."
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user