Compare commits

...

5 Commits

Author SHA1 Message Date
DerLinkman
d8a856d1cb update: backport compose v5 fix for legacy branch 2025-12-08 14:48:55 +01:00
FreddleSpl0it
11356674ba [Redis] Update to 7.4.6 2025-10-15 09:25:01 +02:00
FreddleSpl0it
8c5f6c0321 [Dovecot] Use Jinja2 sandbox for rendering quota and quarantine notifications 2025-07-15 10:46:50 +02:00
FreddleSpl0it
6a16a4886c Merge branch 'staging' into legacy 2025-03-24 11:48:11 +01:00
FreddleSpl0it
4222f73ea0 Add switch to legacy version 2025-03-20 14:40:12 +01:00
6 changed files with 99 additions and 36 deletions

View File

@@ -8,7 +8,8 @@ from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText from email.mime.text import MIMEText
from email.utils import COMMASPACE, formatdate from email.utils import COMMASPACE, formatdate
import jinja2 import jinja2
from jinja2 import Template from jinja2 import TemplateError
from jinja2.sandbox import SandboxedEnvironment
import json import json
import redis import redis
import time import time
@@ -80,17 +81,22 @@ try:
if len(meta_query) == 0: if len(meta_query) == 0:
return return
msg_count = len(meta_query) msg_count = len(meta_query)
env = SandboxedEnvironment()
if r.get('Q_HTML'): if r.get('Q_HTML'):
try: try:
template = Template(r.get('Q_HTML')) template = env.from_string(r.get('Q_HTML'))
except: except Exception:
print("Error: Cannot parse quarantine template, falling back to default template.") print("Error: Cannot parse quarantine template, falling back to default template.")
with open('/templates/quarantine.tpl') as file_: with open('/templates/quarantine.tpl') as file_:
template = Template(file_.read()) template = env.from_string(file_.read())
else: else:
with open('/templates/quarantine.tpl') as file_: with open('/templates/quarantine.tpl') as file_:
template = Template(file_.read()) template = env.from_string(file_.read())
html = template.render(meta=meta_query, username=rcpt, counter=msg_count, hostname=mailcow_hostname, quarantine_acl=quarantine_acl) try:
html = template.render(meta=meta_query, username=rcpt, counter=msg_count, hostname=mailcow_hostname, quarantine_acl=quarantine_acl)
except (jinja2.exceptions.SecurityError, TemplateError) as ex:
print(f"SecurityError or TemplateError in template rendering: {ex}")
return
text = html2text.html2text(html) text = html2text.html2text(html)
count = 0 count = 0
while count < 15: while count < 15:
@@ -165,4 +171,4 @@ try:
notify_rcpt(record['rcpt'], record['counter'], record['quarantine_acl'], attrs['quarantine_category']) notify_rcpt(record['rcpt'], record['counter'], record['quarantine_acl'], attrs['quarantine_category'])
finally: finally:
os.unlink(pidfile) os.unlink(pidfile)

View File

@@ -6,7 +6,7 @@ from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText from email.mime.text import MIMEText
from email.utils import COMMASPACE, formatdate from email.utils import COMMASPACE, formatdate
import jinja2 import jinja2
from jinja2 import Template from jinja2.sandbox import SandboxedEnvironment
import redis import redis
import time import time
import json import json
@@ -33,16 +33,24 @@ while True:
if r.get('QW_HTML'): if r.get('QW_HTML'):
try: try:
template = Template(r.get('QW_HTML')) env = SandboxedEnvironment()
except: template = env.from_string(r.get('QW_HTML'))
print("Error: Cannot parse quarantine template, falling back to default template.") except Exception:
print("Error: Cannot parse quota template, falling back to default template.")
with open('/templates/quota.tpl') as file_: with open('/templates/quota.tpl') as file_:
template = Template(file_.read()) env = SandboxedEnvironment()
template = env.from_string(file_.read())
else: else:
with open('/templates/quota.tpl') as file_: with open('/templates/quota.tpl') as file_:
template = Template(file_.read()) env = SandboxedEnvironment()
template = env.from_string(file_.read())
try:
html = template.render(username=username, percent=percent)
except (jinja2.exceptions.SecurityError, jinja2.TemplateError) as ex:
print(f"SecurityError or TemplateError in template rendering: {ex}")
sys.exit(1)
html = template.render(username=username, percent=percent)
text = html2text.html2text(html) text = html2text.html2text(html)
try: try:
@@ -91,4 +99,4 @@ except:
try: try:
sys.stderr.close() sys.stderr.close()
except: except:
pass pass

View File

@@ -235,7 +235,7 @@ function recursiveBase64StrToArrayBuffer(obj) {
$(".totp-authenticator-selection").click(function(){ $(".totp-authenticator-selection").click(function(){
$(".totp-authenticator-selection").removeClass("active"); $(".totp-authenticator-selection").removeClass("active");
$(this).addClass("active"); $(this).addClass("active");
var id = $(this).children('input').first().val(); var id = $(this).children('input').first().val();
$("#totp_selected_id").val(id); $("#totp_selected_id").val(id);
@@ -244,7 +244,7 @@ function recursiveBase64StrToArrayBuffer(obj) {
if ($('.totp-authenticator-selection').length == 1 && if ($('.totp-authenticator-selection').length == 1 &&
$('#pending_tfa_tab_yubi_otp').length == 0 && $('#pending_tfa_tab_yubi_otp').length == 0 &&
$('.webauthn-authenticator-selection').length == 0){ $('.webauthn-authenticator-selection').length == 0){
// select default if only one authenticator exists // select default if only one authenticator exists
$('.totp-authenticator-selection').addClass("active"); $('.totp-authenticator-selection').addClass("active");
@@ -257,7 +257,7 @@ function recursiveBase64StrToArrayBuffer(obj) {
$('#pending_tfa_tab_totp').on('shown.bs.tab', function() { $('#pending_tfa_tab_totp').on('shown.bs.tab', function() {
// autofocus // autofocus
setTimeout(function() { $("#collapseTotpTFA").find('input[name="token"]').focus(); }, 200); setTimeout(function() { $("#collapseTotpTFA").find('input[name="token"]').focus(); }, 200);
}); });
// validate Yubi OTP tfa // validate Yubi OTP tfa
if ($('.webauthn-authenticator-selection').length == 0){ if ($('.webauthn-authenticator-selection').length == 0){
// autofocus // autofocus
@@ -276,10 +276,10 @@ function recursiveBase64StrToArrayBuffer(obj) {
$(".webauthn-authenticator-selection").click(function(){ $(".webauthn-authenticator-selection").click(function(){
$(".webauthn-authenticator-selection").removeClass("active"); $(".webauthn-authenticator-selection").removeClass("active");
$(this).addClass("active"); $(this).addClass("active");
var id = $(this).children('input').first().val(); var id = $(this).children('input').first().val();
$("#webauthn_selected_id").val(id); $("#webauthn_selected_id").val(id);
var webauthn_status_auth = document.getElementById('webauthn_status_auth'); var webauthn_status_auth = document.getElementById('webauthn_status_auth');
webauthn_status_auth.style.setProperty('display', 'flex', 'important'); webauthn_status_auth.style.setProperty('display', 'flex', 'important');
var webauthn_return_code = document.getElementById('webauthn_return_code'); var webauthn_return_code = document.getElementById('webauthn_return_code');
@@ -302,7 +302,7 @@ function recursiveBase64StrToArrayBuffer(obj) {
console.log(json); console.log(json);
if (json.success === false) throw new Error(); if (json.success === false) throw new Error();
if (json.type === "error") throw new Error(json.msg); if (json.type === "error") throw new Error(json.msg);
recursiveBase64StrToArrayBuffer(json); recursiveBase64StrToArrayBuffer(json);
return json; return json;
}).then(getCredentialArgs => { }).then(getCredentialArgs => {
@@ -329,7 +329,7 @@ function recursiveBase64StrToArrayBuffer(obj) {
webauthn_return_code.style.setProperty('display', 'block', 'important'); webauthn_return_code.style.setProperty('display', 'block', 'important');
webauthn_return_code.innerHTML = lang_tfa.error_code + ': ' + err + ' ' + lang_tfa.reload_retry; webauthn_return_code.innerHTML = lang_tfa.error_code + ': ' + err + ' ' + lang_tfa.reload_retry;
}); });
} }
}); });
$('#ConfirmTFAModal').on('hidden.bs.modal', function(){ $('#ConfirmTFAModal').on('hidden.bs.modal', function(){
// cancel pending login // cancel pending login
@@ -540,7 +540,7 @@ function recursiveBase64StrToArrayBuffer(obj) {
Version: <a href="{{ mailcow_info.git_project_url }}/releases/tag/{{ mailcow_info.version_tag }}" target="_blank">{{ mailcow_info.version_tag }} Version: <a href="{{ mailcow_info.git_project_url }}/releases/tag/{{ mailcow_info.version_tag }}" target="_blank">{{ mailcow_info.version_tag }}
</a> </a>
</span> </span>
{% endif %} {% endif %}
{% if mailcow_cc_username and mailcow_info.mailcow_branch|lower == "nightly" and mailcow_info.version_tag|default %} {% if mailcow_cc_username and mailcow_info.mailcow_branch|lower == "nightly" and mailcow_info.version_tag|default %}
<span class="version"> <span class="version">
🛠️🐮 + 🐋 = 💕 🛠️🐮 + 🐋 = 💕
@@ -549,6 +549,14 @@ function recursiveBase64StrToArrayBuffer(obj) {
<span style="text-align:right;display:block;">Build: {{ mailcow_info.git_commit_date }}</span> <span style="text-align:right;display:block;">Build: {{ mailcow_info.git_commit_date }}</span>
</span> </span>
{% endif %} {% endif %}
{% if mailcow_cc_username and mailcow_info.mailcow_branch|lower == "legacy" and mailcow_info.version_tag|default %}
<span class="version">
⚰️🐮 + 🐋 = 💕
Legacy: <a href="{{ mailcow_info.git_project_url }}/commit/{{ mailcow_info.git_commit }}" target="_blank">{{ mailcow_info.version_tag }}
</a><br>
<span style="text-align:right;display:block;">Build: {{ mailcow_info.git_commit_date }}</span>
</span>
{% endif %}
</div> </div>
</body> </body>
</html> </html>

View File

@@ -42,7 +42,7 @@ services:
- mysql - mysql
redis-mailcow: redis-mailcow:
image: redis:7.4.2-alpine image: redis:7.4.6-alpine
entrypoint: ["/bin/sh","/redis-conf.sh"] entrypoint: ["/bin/sh","/redis-conf.sh"]
volumes: volumes:
- redis-vol-1:/data/ - redis-vol-1:/data/
@@ -234,7 +234,7 @@ services:
- sogo - sogo
dovecot-mailcow: dovecot-mailcow:
image: ghcr.io/mailcow/dovecot:2.32 image: ghcr.io/mailcow/dovecot:2.34-legacy
depends_on: depends_on:
- mysql-mailcow - mysql-mailcow
- netfilter-mailcow - netfilter-mailcow

View File

@@ -181,11 +181,15 @@ if [[ ${SKIP_BRANCH} != y ]]; then
echo "Available Branches:" echo "Available Branches:"
echo "- master branch (stable updates) | default, recommended [1]" echo "- master branch (stable updates) | default, recommended [1]"
echo "- nightly branch (unstable updates, testing) | not-production ready [2]" echo "- nightly branch (unstable updates, testing) | not-production ready [2]"
echo "- legacy branch (supported until February 2026) | deprecated, security updates only [3]"
sleep 1 sleep 1
while [ -z "${MAILCOW_BRANCH}" ]; do while [ -z "${MAILCOW_BRANCH}" ]; do
read -r -p "Choose the Branch with it's number [1/2] " branch read -r -p "Choose the Branch with it's number [1/2/3] " branch
case $branch in case $branch in
[3])
MAILCOW_BRANCH="legacy"
;;
[2]) [2])
MAILCOW_BRANCH="nightly" MAILCOW_BRANCH="nightly"
;; ;;
@@ -534,6 +538,10 @@ case ${git_branch} in
mailcow_git_version=$(git rev-parse --short $(git rev-parse @{upstream})) mailcow_git_version=$(git rev-parse --short $(git rev-parse @{upstream}))
mailcow_last_git_version="" mailcow_last_git_version=""
;; ;;
legacy)
mailcow_git_version=$(git rev-parse --short $(git rev-parse @{upstream}))
mailcow_last_git_version=""
;;
*) *)
mailcow_git_version=$(git rev-parse --short HEAD) mailcow_git_version=$(git rev-parse --short HEAD)
mailcow_last_git_version="" mailcow_last_git_version=""

View File

@@ -177,7 +177,7 @@ remove_obsolete_nginx_ports() {
detect_docker_compose_command(){ detect_docker_compose_command(){
if ! [[ "${DOCKER_COMPOSE_VERSION}" =~ ^(native|standalone)$ ]]; then if ! [[ "${DOCKER_COMPOSE_VERSION}" =~ ^(native|standalone)$ ]]; then
if docker compose > /dev/null 2>&1; then if docker compose > /dev/null 2>&1; then
if docker compose version --short | grep -e "^2." -e "^v2." > /dev/null 2>&1; then if docker compose version --short | grep -e "^[2-9]\." -e "^v[2-9]\." -e "^[1-9][0-9]\." -e "^v[1-9][0-9]\." > /dev/null 2>&1; then
DOCKER_COMPOSE_VERSION=native DOCKER_COMPOSE_VERSION=native
COMPOSE_COMMAND="docker compose" COMPOSE_COMMAND="docker compose"
echo -e "\e[33mFound Docker Compose Plugin (native).\e[0m" echo -e "\e[33mFound Docker Compose Plugin (native).\e[0m"
@@ -187,12 +187,12 @@ if ! [[ "${DOCKER_COMPOSE_VERSION}" =~ ^(native|standalone)$ ]]; then
echo -e "\e[33mNotice: You'll have to update this Compose Version via your Package Manager manually!\e[0m" echo -e "\e[33mNotice: You'll have to update this Compose Version via your Package Manager manually!\e[0m"
else else
echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m" echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m"
echo -e "\e[31mPlease update/install it manually regarding to this doc site: https://docs.mailcow.email/install/\e[0m" echo -e "\e[31mPlease update/install manually regarding to this doc site: https://docs.mailcow.email/install/\e[0m"
exit 1 exit 1
fi fi
elif docker-compose > /dev/null 2>&1; then elif docker-compose > /dev/null 2>&1; then
if ! [[ $(alias docker-compose 2> /dev/null) ]] ; then if ! [[ $(alias docker-compose 2> /dev/null) ]] ; then
if docker-compose version --short | grep "^2." > /dev/null 2>&1; then if docker-compose version --short | grep -e "^[2-9]\." -e "^[1-9][0-9]\." > /dev/null 2>&1; then
DOCKER_COMPOSE_VERSION=standalone DOCKER_COMPOSE_VERSION=standalone
COMPOSE_COMMAND="docker-compose" COMPOSE_COMMAND="docker-compose"
echo -e "\e[33mFound Docker Compose Standalone.\e[0m" echo -e "\e[33mFound Docker Compose Standalone.\e[0m"
@@ -202,7 +202,7 @@ if ! [[ "${DOCKER_COMPOSE_VERSION}" =~ ^(native|standalone)$ ]]; then
echo -e "\e[33mNotice: For an automatic update of docker-compose please use the update_compose.sh scripts located at the helper-scripts folder.\e[0m" echo -e "\e[33mNotice: For an automatic update of docker-compose please use the update_compose.sh scripts located at the helper-scripts folder.\e[0m"
else else
echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m" echo -e "\e[31mCannot find Docker Compose with a Version Higher than 2.X.X.\e[0m"
echo -e "\e[31mPlease update/install regarding to this doc site: https://docs.mailcow.email/install/\e[0m" echo -e "\e[31mPlease update/install manually regarding to this doc site: https://docs.mailcow.email/install/\e[0m"
exit 1 exit 1
fi fi
fi fi
@@ -895,8 +895,12 @@ while (($#)); do
echo -e "\e[32mRunning in Developer mode...\e[0m" echo -e "\e[32mRunning in Developer mode...\e[0m"
DEV=y DEV=y
;; ;;
--legacy)
CURRENT_BRANCH="$(cd "${SCRIPT_DIR}"; git rev-parse --abbrev-ref HEAD)"
NEW_BRANCH="legacy"
;;
--help|-h) --help|-h)
echo './update.sh [-c|--check, --check-tags, --ours, --gc, --nightly, --prefetch, --skip-start, --skip-ping-check, --stable, -f|--force, -d|--dev, -h|--help] echo './update.sh [-c|--check, --check-tags, --ours, --gc, --nightly, --prefetch, --skip-start, --skip-ping-check, --stable, --legacy, -f|--force, -d|--dev, -h|--help]
-c|--check - Check for updates and exit (exit codes => 0: update available, 3: no updates) -c|--check - Check for updates and exit (exit codes => 0: update available, 3: no updates)
--check-tags - Check for newer tags and exit (exit codes => 0: newer tag available, 3: no newer tag) --check-tags - Check for newer tags and exit (exit codes => 0: newer tag available, 3: no newer tag)
@@ -906,7 +910,8 @@ while (($#)); do
--prefetch - Only prefetch new images and exit (useful to prepare updates) --prefetch - Only prefetch new images and exit (useful to prepare updates)
--skip-start - Do not start mailcow after update --skip-start - Do not start mailcow after update
--skip-ping-check - Skip ICMP Check to public DNS resolvers (Use it only if you'\''ve blocked any ICMP Connections to your mailcow machine) --skip-ping-check - Skip ICMP Check to public DNS resolvers (Use it only if you'\''ve blocked any ICMP Connections to your mailcow machine)
--stable - Switch your mailcow updates to the stable (master) branch. Default unless you changed it with --nightly. --stable - Switch your mailcow updates to the stable (master) branch. Default unless you changed it with --nightly or --legacy.
--legacy - Switch your mailcow updates to the legacy branch. The legacy branch will only recieve security updates until February 2026.
-f|--force - Force update, do not ask questions -f|--force - Force update, do not ask questions
-d|--dev - Enables Developer Mode (No Checkout of update.sh for tests) -d|--dev - Enables Developer Mode (No Checkout of update.sh for tests)
' '
@@ -1312,6 +1317,11 @@ if ! [ "$NEW_BRANCH" ]; then
sleep 1 sleep 1
echo -e "\e[33mTo change that run the update.sh Script one time with the --stable parameter to switch to stable builds.\e[0m" echo -e "\e[33mTo change that run the update.sh Script one time with the --stable parameter to switch to stable builds.\e[0m"
elif [ "${BRANCH}" == "legacy" ]; then
echo -e "\e[31mYou are receiving legacy updates. The legacy branch will only recieve security updates until February 2026.\e[0m"
sleep 1
echo -e "\e[33mTo change that run the update.sh Script one time with the --stable parameter to switch to stable builds.\e[0m"
else else
echo -e "\e[33mYou are receiving updates from an unsupported branch.\e[0m" echo -e "\e[33mYou are receiving updates from an unsupported branch.\e[0m"
sleep 1 sleep 1
@@ -1374,6 +1384,29 @@ elif [ "$NEW_BRANCH" == "nightly" ] && [ "$CURRENT_BRANCH" != "nightly" ]; then
fi fi
git fetch origin git fetch origin
git checkout -f "${BRANCH}" git checkout -f "${BRANCH}"
elif [ "$NEW_BRANCH" == "legacy" ] && [ "$CURRENT_BRANCH" != "legacy" ]; then
echo -e "\e[33mYou are about to switch your mailcow Updates to the legacy branch.\e[0m"
sleep 1
echo -e "\e[33mBefore you do: Please take a backup of all components to ensure that no Data is lost...\e[0m"
sleep 1
echo -e "\e[31mWARNING: A switch to stable or nightly is possible any time.\e[0m"
read -r -p "Are you sure you want to continue upgrading to the legacy branch? [y/N] " response
if [[ ! "${response}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
echo "OK. If you prepared yourself for that please run the update.sh Script with the --legacy parameter again to trigger this process here."
exit 0
fi
BRANCH=$NEW_BRANCH
DIFF_DIRECTORY=update_diffs
DIFF_FILE=${DIFF_DIRECTORY}/diff_before_upgrade_to_legacy_$(date +"%Y-%m-%d-%H-%M-%S")
mv diff_before_upgrade* ${DIFF_DIRECTORY}/ 2> /dev/null
if ! git diff-index --quiet HEAD; then
echo -e "\e[32mSaving diff to ${DIFF_FILE}...\e[0m"
mkdir -p ${DIFF_DIRECTORY}
git diff "${BRANCH}" --stat > "${DIFF_FILE}"
git diff "${BRANCH}" >> "${DIFF_FILE}"
fi
git fetch origin
git checkout -f "${BRANCH}"
fi fi
if [ ! "$DEV" ]; then if [ ! "$DEV" ]; then