Compare commits

..

54 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
ad5f07f077 update.sh: add 2025-03 as major version 2025-03-24 11:47:27 +01:00
FreddleSpl0it
91c82e8a67 Merge pull request #6384 from mailcow/feat/update-components-alp-3.21
os: updated alpine containers to 3.21
2025-03-24 11:30:58 +01:00
FreddleSpl0it
4222f73ea0 Add switch to legacy version 2025-03-20 14:40:12 +01:00
DerLinkman
463e3ab78c rspamd: update rspamd to 3.11.1 (#6374) 2025-03-14 12:18:59 +01:00
FreddleSpl0it
2a15914324 Fix major update prompt 2025-03-14 11:22:57 +01:00
Marvin A. Ruder
062539b7d7 dkim: Add support for 3072 and 4096 bit RSA keys (#6365)
* dkim: Add support for 3072 and 4096 bit RSA keys

Signed-off-by: Marvin A. Ruder <signed@mruder.dev>

* php: added missing ; in dkim function

* php: make 4096 DKIM default

* db: update schema to set dkim 4096 as default

* Revert "db: update schema to set dkim 4096 as default"

This reverts commit 790b40a695.

* Revert "php: make 4096 DKIM default"

This reverts commit 7e643376c7.

---------

Signed-off-by: Marvin A. Ruder <signed@mruder.dev>
Co-authored-by: DerLinkman <niklas.meyer@servercow.de>
2025-03-11 15:30:46 +01:00
DerLinkman
18acbc7a4c cold-standby: changed texts + removed --no-parallel for pull 2025-03-11 12:35:13 +01:00
DerLinkman
2f93f1d0c5 os: fixes for newer mariadb-client versions (especially on alpine 3.21) 2025-03-10 16:45:57 +01:00
DerLinkman
0860a7503e os: updated alpine containers to 3.21 2025-03-10 11:56:12 +01:00
renovate[bot]
86df78255d chore(deps): update dependency composer/composer to v2.8.6 (#5719)
Signed-off-by: milkmaker <milkmaker@mailcow.de>
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-03-10 11:39:19 +01:00
milkmaker
03565df48d [Web] Updated lang.ko-kr.json (#6356)
Co-authored-by: dongsu8142 <dongsu8142@naver.com>
2025-03-07 21:37:31 +01:00
milkmaker
0435766c17 [Web] Updated lang.ko-kr.json (#6353)
Co-authored-by: dongsu8142 <dongsu8142@naver.com>
2025-03-05 17:43:37 +01:00
renovate[bot]
79f4cf4021 chore(deps): update docker/build-push-action action to v6 (#6334) 2025-03-05 16:35:46 +01:00
milkmaker
81803836f0 [Web] Updated lang.ko-kr.json (#6350)
Co-authored-by: dongsu8142 <dongsu8142@naver.com>
2025-03-03 22:49:23 +01:00
milkmaker
4bd267515a update postscreen_access.cidr (#6345) 2025-03-01 13:32:21 +01:00
DerLinkman
5296085189 update.sh: corrected typos inside update.sh 2025-02-27 11:47:08 +01:00
DerLinkman
a4c2cf4c67 scripts: adapted new docker image names to docker_garbage function + removed dup 2025-02-27 11:44:52 +01:00
Peter
3c9d0c9d57 use ghcr.io for backupimage (#6333)
* use ghcr.io for backup image

* backup script: use renamed script + improved build of image

---------

Co-authored-by: DerLinkman <niklas.meyer@servercow.de>
2025-02-27 10:58:23 +01:00
FreddleSpl0it
35a6f81d0d [Redis] use 7.4.2-alpine image 2025-02-27 09:28:52 +01:00
FreddleSpl0it
4b31c04e3e Merge pull request #6330 from mailcow/feat/major-update-prompt
Prompt user before applying major updates
2025-02-27 08:15:21 +01:00
FreddleSpl0it
3d9cc2f6dd add 2025-02 to major versions 2025-02-27 08:14:34 +01:00
DerLinkman
704dd50262 compose: use ghcr.io for new/current mailcow docker images instead of docker hub (#6332) 2025-02-26 15:20:57 +01:00
FreddleSpl0it
c4a0e370b7 Merge pull request #6155 from PseudoResonance/fix2752
Fix #2752 - Allow domain recipients for address rewrite
2025-02-26 10:01:03 +01:00
FreddleSpl0it
787fa49d0c prompt user before applying major updates 2025-02-25 12:08:21 +01:00
DerLinkman
a6c38590ca rspamd: upgraded rspamd to 3.11.0-2 (incl. NIXSPAM Removal) (#6328) 2025-02-25 09:23:10 +01:00
PseudoResonance
e52323bf1d Fix @ prefixing domain rewrite and update localization 2025-02-24 22:36:17 -08:00
PseudoResonance
f15ee39b63 Fix #2752: Domain recipient for address rewrite
(cherry picked from commit 40f6d691d8774d6f813153974f8fe462a8db9ab3)
2025-02-24 22:07:23 -08:00
FreddleSpl0it
6ec5e88793 Merge pull request #6309 from mailcow/fix/6308
[Dovecot][Netfilter] Fix dovecot failed login regex
2025-02-24 11:26:06 +01:00
FreddleSpl0it
7d35646342 [Netfilter] adjust dovecot failed login regex 2025-02-24 09:20:41 +01:00
FreddleSpl0it
321965adee [Netfilter] Fix dovecot password mismatch regex 2025-02-18 15:05:59 +01:00
Peter
7bce5d836b Move sed cmd to remove discontinued DNSBLs (#6315)
* Move sed cmd to remove discontinued DNSBLs

* compose: bump postfix version

---------

Co-authored-by: DerLinkman <niklas.meyer@servercow.de>
2025-02-18 11:20:03 +01:00
FreddleSpl0it
351f4ce787 [Redis] Add support for masterauth via env var 2025-02-18 11:16:06 +01:00
FreddleSpl0it
a567d5dc31 [Nginx] Add support for trusted proxies via env var 2025-02-18 11:03:34 +01:00
DerLinkman
4ac541f671 [Mariadb] Update to 10.11 (LTS) (#5152)
* [Mariadb] Update to 10.11 (LTS)

* mysql: set default collation to general_ci
2025-02-17 15:48:25 +01:00
Dmitriy Alekseev
f6dc0b463f Update Rspamd to 3.11.0 and enable SMTPUTF8 for outgoing mail (#6216)
* Update Rspamd to 3.11

* Enable SMTPUTF8 and hide it from SMTPD greeting

* Update options.inc

* compose: increased rspamd tag
2025-02-17 14:41:39 +01:00
DerLinkman
16e22e23dc sogo: switched apt source to sogo again (supports aarch64 now) 2025-02-17 14:31:50 +01:00
FreddleSpl0it
d8afa6f393 [Dovecot][Netfilter] Fix dovecot failed login regex 2025-02-14 13:12:12 +01:00
milkmaker
836e3f15b7 [Web] Updated lang.es-es.json (#6307)
Co-authored-by: Julie GINESTIERE <julien.ginestiere+git@gmail.com>
2025-02-13 19:32:39 +01:00
FreddleSpl0it
aaa7e4a184 [Web] Fix incorrect session lifetime in sogo-auth.php 2025-02-13 11:54:55 +01:00
FreddleSpl0it
3912341b32 [SOGo] rename custom logo 2025-02-12 11:31:14 +01:00
FreddleSpl0it
735d5f0e56 Merge pull request #6220 from Babybatrick/staging
Adding lines to docker-compose.yml to allow for simpler SOGo web client UI customisation
2025-02-12 10:54:16 +01:00
FreddleSpl0it
f375794fb7 Merge pull request #6223 from mailcow/ffdhe2048
Ffdhe2048
2025-02-12 10:48:22 +01:00
renovate[bot]
4ed3017a02 chore(deps): update devops-infra/action-pull-request action to v0.6.0 (#6302) 2025-02-12 06:56:10 +01:00
FreddleSpl0it
ef2f5f7be0 [Dovecot] Use Redis ACL user quota_notify with restricted access 2025-02-11 16:59:18 +01:00
Henry Williams
743e88fd67 Update generate_config.sh version checking for wider compatibility (#6270)
* Update generate_config.sh version checking for wider compatibility 

fix: replace `grep -oP` with `grep -oE` for broader compatibility

The `-P` option (Perl-compatible regex) is not supported in all versions of `grep`, particularly the default BSD `grep` on macOS. This change replaces `-P` with `-E` (extended regex), which is more widely available and ensures compatibility across different environments.

Tested on macOS and Linux.

* Update generate_config.sh to remove use of platform dependent grep

Replaced version checking using free-form text. Instead, uses Docker’s built-in templating instead of parsing free-form text. This gives cross-platform consistency without dependency on particular versions of grep.
2025-02-11 13:55:03 +01:00
DerLinkman
ac2f0c7db1 Merge pull request #6286 from mailcow/fix-workflow-staging
Fix check_prs_if_on_staging workflow
2025-02-11 13:52:44 +01:00
Peter
83fc2c6387 It's github-token now 2025-01-31 17:20:28 +01:00
Dmitriy Alekseev
d92aa4b15d Update dhparams.pem
Use https://ssl-config.mozilla.org/ffdhe2048.txt due to better security of the key
2024-12-20 15:39:41 +01:00
Amin
ade20d79d4 Uploading of the necessary files, after new volumes were added to docker-compose.yml (sogo-mailcow container)
After new volumes were added to docker-compose.yml in the sogo-mailcow container, it is necessary to include the specified files in the path, in order for docker to correctly start after running `docker compose up` command, otherwise error will appear, as necessary files would be missing.
The files uploaded are original SOGo UI elements, obtained from the sogo-mailcow container. Whenever users will need to change the UI elements, they would just need to change these files. Hence simplifying the process.
2024-12-19 22:13:27 +08:00
Amin
65bc8f0972 Update docker-compose.yml (sogo-mailcow)
This commit includes the addition of 3 lines, in the volumes part of the sogo-mailcow container, to allow for better customisation of the user interface on the web client page.
2024-12-19 21:59:05 +08:00
70 changed files with 579 additions and 237 deletions

View File

@@ -12,7 +12,7 @@ jobs:
- name: Send message
uses: thollander/actions-comment-pull-request@v3.0.1
with:
GITHUB_TOKEN: ${{ secrets.CHECKIFPRISSTAGING_ACTION_PAT }}
github-token: ${{ secrets.CHECKIFPRISSTAGING_ACTION_PAT }}
message: |
Thanks for contributing!

View File

@@ -12,7 +12,7 @@ jobs:
with:
fetch-depth: 0
- name: Run the Action
uses: devops-infra/action-pull-request@v0.5.5
uses: devops-infra/action-pull-request@v0.6.0
with:
github_token: ${{ secrets.PRTONIGHTLY_ACTION_PAT }}
title: Automatic PR to nightly from ${{ github.event.repository.updated_at}}

View File

@@ -9,6 +9,8 @@ on:
jobs:
docker_image_build:
runs-on: ubuntu-latest
permissions:
packages: write
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -19,11 +21,13 @@ jobs:
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
- name: Login to GHCR
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
username: ${{ secrets.BACKUPIMAGEBUILD_ACTION_DOCKERHUB_USERNAME }}
password: ${{ secrets.BACKUPIMAGEBUILD_ACTION_DOCKERHUB_TOKEN }}
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v6
@@ -32,4 +36,4 @@ jobs:
platforms: linux/amd64,linux/arm64
file: data/Dockerfiles/backup/Dockerfile
push: true
tags: mailcow/backup:latest
tags: ghcr.io/mailcow/backup:latest

4
.gitignore vendored
View File

@@ -47,7 +47,9 @@ data/conf/sogo/custom-theme.js
data/conf/sogo/plist_ldap
data/conf/sogo/sieve.creds
data/conf/sogo/cron.creds
data/conf/sogo/sogo-full.svg
data/conf/sogo/custom-fulllogo.svg
data/conf/sogo/custom-shortlogo.svg
data/conf/sogo/custom-fulllogo.png
data/gitea/
data/gogs/
data/hooks/dovecot/*

View File

@@ -1,8 +1,7 @@
FROM alpine:3.20
FROM alpine:3.21
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
RUN apk upgrade --no-cache \
&& apk add --update --no-cache \
bash \
@@ -15,7 +14,7 @@ RUN apk upgrade --no-cache \
tini \
tzdata \
python3 \
acme-tiny --repository=http://dl-cdn.alpinelinux.org/alpine/edge/community/
acme-tiny
COPY acme.sh /srv/acme.sh
COPY functions.sh /srv/functions.sh

View File

@@ -138,7 +138,7 @@ log_f "Resolver OK"
log_f "Waiting for domain table..."
while [[ -z ${DOMAIN_TABLE} ]]; do
curl --silent http://nginx.${COMPOSE_PROJECT_NAME}_mailcow-network/ >/dev/null 2>&1
DOMAIN_TABLE=$(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SHOW TABLES LIKE 'domain'" -Bs)
DOMAIN_TABLE=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SHOW TABLES LIKE 'domain'" -Bs)
[[ -z ${DOMAIN_TABLE} ]] && sleep 10
done
log_f "OK" no_date
@@ -231,7 +231,7 @@ while true; do
#########################################
# IP and webroot challenge verification #
SQL_DOMAINS=$(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM domain WHERE backupmx=0 and active=1" -Bs)
SQL_DOMAINS=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM domain WHERE backupmx=0 and active=1" -Bs)
if [[ ! $? -eq 0 ]]; then
log_f "Failed to read SQL domains, retrying in 1 minute..."
sleep 1m

View File

@@ -1,3 +1,3 @@
FROM debian:bookworm-slim
RUN apt update && apt install pigz
RUN apt update && apt install pigz -y --no-install-recommends

View File

@@ -1,4 +1,4 @@
FROM alpine:3.20
FROM alpine:3.21
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"

View File

@@ -1,4 +1,4 @@
FROM alpine:3.20
FROM alpine:3.21
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
@@ -65,7 +65,7 @@ RUN addgroup -g 5000 vmail \
perl-par-packer \
perl-parse-recdescent \
perl-lockfile-simple \
libproc \
libproc2 \
perl-readonly \
perl-regexp-common \
perl-sys-meminfo \

View File

@@ -15,6 +15,6 @@ if ! [[ ${MAX_AGE} =~ ${NUM_REGEXP} ]] ; then
exit 1
fi
TO_DELETE=$(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT COUNT(id) FROM quarantine WHERE created < NOW() - INTERVAL ${MAX_AGE//[!0-9]/} DAY" -BN)
mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "DELETE FROM quarantine WHERE created < NOW() - INTERVAL ${MAX_AGE//[!0-9]/} DAY"
TO_DELETE=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT COUNT(id) FROM quarantine WHERE created < NOW() - INTERVAL ${MAX_AGE//[!0-9]/} DAY" -BN)
mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "DELETE FROM quarantine WHERE created < NOW() - INTERVAL ${MAX_AGE//[!0-9]/} DAY"
echo "Deleted ${TO_DELETE} items from quarantine table (max age is ${MAX_AGE//[!0-9]/} days)"

View File

@@ -414,15 +414,15 @@ printenv | sed 's/^\(.*\)$/export \1/g' > /source_env.sh
# Clean stopped imapsync jobs
rm -f /tmp/imapsync_busy.lock
IMAPSYNC_TABLE=$(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SHOW TABLES LIKE 'imapsync'" -Bs)
[[ ! -z ${IMAPSYNC_TABLE} ]] && mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "UPDATE imapsync SET is_running='0'"
IMAPSYNC_TABLE=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SHOW TABLES LIKE 'imapsync'" -Bs)
[[ ! -z ${IMAPSYNC_TABLE} ]] && mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "UPDATE imapsync SET is_running='0'"
# Envsubst maildir_gc
echo "$(envsubst < /usr/local/bin/maildir_gc.sh)" > /usr/local/bin/maildir_gc.sh
# GUID generation
while [[ ${VERSIONS_OK} != 'OK' ]]; do
if [[ ! -z $(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = \"${DBNAME}\" AND TABLE_NAME = 'versions'") ]]; then
if [[ ! -z $(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = \"${DBNAME}\" AND TABLE_NAME = 'versions'") ]]; then
VERSIONS_OK=OK
else
echo "Waiting for versions table to be created..."
@@ -433,11 +433,11 @@ PUBKEY_MCRYPT=$(doveconf -P 2> /dev/null | grep -i mail_crypt_global_public_key
if [ -f ${PUBKEY_MCRYPT} ]; then
GUID=$(cat <(echo ${MAILCOW_HOSTNAME}) /mail_crypt/ecpubkey.pem | sha256sum | cut -d ' ' -f1 | tr -cd "[a-fA-F0-9.:/] ")
if [ ${#GUID} -eq 64 ]; then
mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
REPLACE INTO versions (application, version) VALUES ("GUID", "${GUID}");
EOF
else
mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
REPLACE INTO versions (application, version) VALUES ("GUID", "INVALID");
EOF
fi

View File

@@ -8,7 +8,8 @@ from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.utils import COMMASPACE, formatdate
import jinja2
from jinja2 import Template
from jinja2 import TemplateError
from jinja2.sandbox import SandboxedEnvironment
import json
import redis
import time
@@ -80,17 +81,22 @@ try:
if len(meta_query) == 0:
return
msg_count = len(meta_query)
env = SandboxedEnvironment()
if r.get('Q_HTML'):
try:
template = Template(r.get('Q_HTML'))
except:
print("Error: Cannot parse quarantine template, falling back to default template.")
with open('/templates/quarantine.tpl') as file_:
template = Template(file_.read())
try:
template = env.from_string(r.get('Q_HTML'))
except Exception:
print("Error: Cannot parse quarantine template, falling back to default template.")
with open('/templates/quarantine.tpl') as file_:
template = env.from_string(file_.read())
else:
with open('/templates/quarantine.tpl') as file_:
template = Template(file_.read())
html = template.render(meta=meta_query, username=rcpt, counter=msg_count, hostname=mailcow_hostname, quarantine_acl=quarantine_acl)
with open('/templates/quarantine.tpl') as file_:
template = env.from_string(file_.read())
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)
count = 0
while count < 15:
@@ -165,4 +171,4 @@ try:
notify_rcpt(record['rcpt'], record['counter'], record['quarantine_acl'], attrs['quarantine_category'])
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.utils import COMMASPACE, formatdate
import jinja2
from jinja2 import Template
from jinja2.sandbox import SandboxedEnvironment
import redis
import time
import json
@@ -23,7 +23,7 @@ else:
while True:
try:
r = redis.StrictRedis(host='redis', decode_responses=True, port=6379, db=0, password=os.environ['REDISPASS'])
r = redis.StrictRedis(host='redis', decode_responses=True, port=6379, db=0, username='quota_notify', password='')
r.ping()
except Exception as ex:
print('%s - trying again...' % (ex))
@@ -33,16 +33,24 @@ while True:
if r.get('QW_HTML'):
try:
template = Template(r.get('QW_HTML'))
except:
print("Error: Cannot parse quarantine template, falling back to default template.")
env = SandboxedEnvironment()
template = env.from_string(r.get('QW_HTML'))
except Exception:
print("Error: Cannot parse quota template, falling back to default template.")
with open('/templates/quota.tpl') as file_:
template = Template(file_.read())
env = SandboxedEnvironment()
template = env.from_string(file_.read())
else:
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)
try:
@@ -91,4 +99,4 @@ except:
try:
sys.stderr.close()
except:
pass
pass

View File

@@ -38,8 +38,13 @@ filter f_replica {
not match("User has no mail_replica in userdb" value("MESSAGE"));
not match("Error: sync: Unknown user in remote" value("MESSAGE"));
};
filter f_dovecot_auth_try {
not match("- trying the next passdb" value("MESSAGE")) and
not match("- trying the next userdb" value("MESSAGE"));
};
log {
source(s_dgram);
filter(f_dovecot_auth_try);
filter(f_replica);
destination(d_stdout);
filter(f_mail);

View File

@@ -38,8 +38,13 @@ filter f_replica {
not match("User has no mail_replica in userdb" value("MESSAGE"));
not match("Error: sync: Unknown user in remote" value("MESSAGE"));
};
filter f_dovecot_auth_try {
not match("- trying the next passdb" value("MESSAGE")) and
not match("- trying the next userdb" value("MESSAGE"));
};
log {
source(s_dgram);
filter(f_dovecot_auth_try);
filter(f_replica);
destination(d_stdout);
filter(f_mail);

View File

@@ -1,4 +1,4 @@
FROM alpine:3.20
FROM alpine:3.21
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"

View File

@@ -85,11 +85,10 @@ def refreshF2bregex():
f2bregex[3] = r'warning: .*\[([0-9a-f\.:]+)\]: SASL .+ authentication failed: (?!.*Connection lost to authentication server).+'
f2bregex[4] = r'warning: non-SMTP command from .*\[([0-9a-f\.:]+)]:.+'
f2bregex[5] = r'NOQUEUE: reject: RCPT from \[([0-9a-f\.:]+)].+Protocol error.+'
f2bregex[6] = r'-login: Disconnected.+ \(auth failed, .+\): user=.*, method=.+, rip=([0-9a-f\.:]+),'
f2bregex[7] = r'-login: Aborted login.+ \(auth failed .+\): user=.+, rip=([0-9a-f\.:]+), lip.+'
f2bregex[8] = r'-login: Aborted login.+ \(tried to use disallowed .+\): user=.+, rip=([0-9a-f\.:]+), lip.+'
f2bregex[9] = r'SOGo.+ Login from \'([0-9a-f\.:]+)\' for user .+ might not have worked'
f2bregex[10] = r'([0-9a-f\.:]+) \"GET \/SOGo\/.* HTTP.+\" 403 .+'
f2bregex[6] = r'\w+\([^,]+,([0-9a-f\.:]+),<[^>]+>\): Password mismatch \(SHA1 of given password: [a-f0-9]+\)'
f2bregex[7] = r'\w+\([^,]+,([0-9a-f\.:]+),<[^>]+>\): unknown user \(SHA1 of given password: [a-f0-9]+\)'
f2bregex[8] = r'SOGo.+ Login from \'([0-9a-f\.:]+)\' for user .+ might not have worked'
f2bregex[9] = r'([0-9a-f\.:]+) \"GET \/SOGo\/.* HTTP.+\" 403 .+'
r.set('F2B_REGEX', json.dumps(f2bregex, ensure_ascii=False))
else:
try:

View File

@@ -43,10 +43,11 @@ def nginx_conf(env, template_vars):
def prepare_template_vars():
ipv4_network = os.getenv("IPV4_NETWORK", "172.22.1")
additional_server_names = os.getenv("ADDITIONAL_SERVER_NAMES", "")
trusted_proxies = os.getenv("TRUSTED_PROXIES", "")
template_vars = {
'IPV4_NETWORK': ipv4_network,
'TRUSTED_NETWORK': os.getenv("TRUSTED_NETWORK", False),
'TRUSTED_PROXIES': [item.strip() for item in trusted_proxies.split(",") if item.strip()],
'SKIP_RSPAMD': os.getenv("SKIP_RSPAMD", "n").lower() in ("y", "yes"),
'SKIP_SOGO': os.getenv("SKIP_SOGO", "n").lower() in ("y", "yes"),
'NGINX_USE_PROXY_PROTOCOL': os.getenv("NGINX_USE_PROXY_PROTOCOL", "n").lower() in ("y", "yes"),

View File

@@ -1,4 +1,4 @@
FROM alpine:3.20
FROM alpine:3.21
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"

View File

@@ -1,4 +1,4 @@
FROM php:8.2-fpm-alpine3.20
FROM php:8.2-fpm-alpine3.21
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
@@ -13,7 +13,7 @@ ARG MEMCACHED_PECL_VERSION=3.2.0
# renovate: datasource=github-tags depName=phpredis/phpredis versioning=semver-coerced extractVersion=(?<version>.*)$
ARG REDIS_PECL_VERSION=6.1.0
# renovate: datasource=github-tags depName=composer/composer versioning=semver-coerced extractVersion=(?<version>.*)$
ARG COMPOSER_VERSION=2.6.6
ARG COMPOSER_VERSION=2.8.6
RUN apk add -U --no-cache autoconf \
aspell-dev \

View File

@@ -81,7 +81,7 @@ if [ ${SQL_CHANGED} -eq 1 ]; then
fi
# Check mysql tz import (master and slave)
TZ_CHECK=$(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT CONVERT_TZ('2019-11-02 23:33:00','Europe/Berlin','UTC') AS time;" -BN 2> /dev/null)
TZ_CHECK=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT CONVERT_TZ('2019-11-02 23:33:00','Europe/Berlin','UTC') AS time;" -BN 2> /dev/null)
if [[ -z ${TZ_CHECK} ]] || [[ "${TZ_CHECK}" == "NULL" ]]; then
SQL_FULL_TZINFO_IMPORT_RETURN=$(curl --silent --insecure -XPOST https://dockerapi.${COMPOSE_PROJECT_NAME}_mailcow-network/containers/${CONTAINER_ID}/exec -d '{"cmd":"system", "task":"mysql_tzinfo_to_sql"}' --silent -H 'Content-type: application/json')
echo "MySQL mysql_tzinfo_to_sql - debug output:"
@@ -120,11 +120,11 @@ if [[ "${MASTER}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
while read line
do
DOMAIN_ARR+=("$line")
done < <(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM domain" -Bs)
done < <(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain FROM domain" -Bs)
while read line
do
DOMAIN_ARR+=("$line")
done < <(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT alias_domain FROM alias_domain" -Bs)
done < <(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT alias_domain FROM alias_domain" -Bs)
if [[ ! -z ${DOMAIN_ARR} ]]; then
for domain in "${DOMAIN_ARR[@]}"; do
@@ -146,13 +146,13 @@ if [[ "${MASTER}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
VALIDATED_IPS=$(array_by_comma ${VALIDATED_API_ALLOW_FROM_ARR[*]})
if [[ ! -z ${VALIDATED_IPS} ]]; then
if [[ ${API_KEY} != "invalid" ]] && [[ ! -z ${API_KEY} ]]; then
mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
DELETE FROM api WHERE access = 'rw';
INSERT INTO api (api_key, active, allow_from, access) VALUES ("${API_KEY}", "1", "${VALIDATED_IPS}", "rw");
EOF
fi
if [[ ${API_KEY_READ_ONLY} != "invalid" ]] && [[ ! -z ${API_KEY_READ_ONLY} ]]; then
mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
DELETE FROM api WHERE access = 'ro';
INSERT INTO api (api_key, active, allow_from, access) VALUES ("${API_KEY_READ_ONLY}", "1", "${VALIDATED_IPS}", "ro");
EOF
@@ -161,7 +161,7 @@ EOF
fi
# Create events (master only, STATUS for event on slave will be SLAVESIDE_DISABLED)
mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
DROP EVENT IF EXISTS clean_spamalias;
DELIMITER //
CREATE EVENT clean_spamalias

View File

@@ -416,10 +416,11 @@ postscreen_dnsbl_sites = wl.mailspike.net=127.0.0.[18;19;20]*-2
bl.mailspike.net=127.0.0.[10;11;12]*4
EOF
fi
DNSBL_CONFIG=$(grep -v '^#' /opt/postfix/conf/dns_blocklists.cf | grep '\S')
# Remove discontinued Nixspam DNSBL from existing dns_blocklists.cf
sed -i '/ix\.dnsbl\.manitu\.net\*2/d' /opt/postfix/conf/dns_blocklists.cf
# Remove discontinued DNSBLs from existing dns_blocklists.cf
sed -i '/ix\.dnsbl\.manitu\.net\*2/d' /opt/postfix/conf/dns_blocklists.cf # Nixspam
DNSBL_CONFIG=$(grep -v '^#' /opt/postfix/conf/dns_blocklists.cf | grep '\S')
if [ ! -z "$DNSBL_CONFIG" ]; then
echo -e "\e[33mChecking if ASN for your IP is listed for Spamhaus Bad ASN List...\e[0m"

View File

@@ -2,7 +2,7 @@ FROM debian:bookworm-slim
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
ARG DEBIAN_FRONTEND=noninteractive
ARG RSPAMD_VER=rspamd_3.10.2-1~b8a232043
ARG RSPAMD_VER=rspamd_3.11.1-1~ab0b44951
ARG CODENAME=bookworm
ENV LC_ALL=C

View File

@@ -4,7 +4,7 @@ LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
ARG DEBIAN_FRONTEND=noninteractive
ARG DEBIAN_VERSION=bookworm
ARG SOGO_DEBIAN_REPOSITORY=http://www.axis.cz/linux/debian
ARG SOGO_DEBIAN_REPOSITORY=https://packagingv2.sogo.nu/sogo-nightly-debian/
# renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=^(?<version>.*)$
ARG GOSU_VERSION=1.17
ENV LC_ALL=C
@@ -33,9 +33,8 @@ RUN echo "Building from repository $SOGO_DEBIAN_REPOSITORY" \
&& gosu nobody true \
&& mkdir /usr/share/doc/sogo \
&& touch /usr/share/doc/sogo/empty.sh \
&& wget http://www.axis.cz/linux/debian/axis-archive-keyring.deb -O /tmp/axis-archive-keyring.deb \
&& apt install -y /tmp/axis-archive-keyring.deb \
&& echo "deb [trusted=yes] ${SOGO_DEBIAN_REPOSITORY} ${DEBIAN_VERSION} sogo-v5" > /etc/apt/sources.list.d/sogo.list \
&& wget -O- https://keys.openpgp.org/vks/v1/by-fingerprint/74FFC6D72B925A34B5D356BDF8A27B36A6E2EAE9 | gpg --dearmor | apt-key add - \
&& echo "deb [trusted=yes] ${SOGO_DEBIAN_REPOSITORY} ${DEBIAN_VERSION} main" > /etc/apt/sources.list.d/sogo.list \
&& apt-get update && apt-get install -y --no-install-recommends \
sogo \
sogo-activesync \

View File

@@ -14,11 +14,11 @@ do
done
# Wait for updated schema
DBV_NOW=$(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT version FROM versions WHERE application = 'db_schema';" -BN)
DBV_NOW=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT version FROM versions WHERE application = 'db_schema';" -BN)
DBV_NEW=$(grep -oE '\$db_version = .*;' init_db.inc.php | sed 's/$db_version = //g;s/;//g' | cut -d \" -f2)
while [[ "${DBV_NOW}" != "${DBV_NEW}" ]]; do
echo "Waiting for schema update..."
DBV_NOW=$(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT version FROM versions WHERE application = 'db_schema';" -BN)
DBV_NOW=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT version FROM versions WHERE application = 'db_schema';" -BN)
DBV_NEW=$(grep -oE '\$db_version = .*;' init_db.inc.php | sed 's/$db_version = //g;s/;//g' | cut -d \" -f2)
sleep 5
done
@@ -27,10 +27,10 @@ echo "DB schema is ${DBV_NOW}"
# Recreate view
if [[ "${MASTER}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
echo "We are master, preparing sogo_view..."
mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "DROP VIEW IF EXISTS sogo_view"
mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "DROP VIEW IF EXISTS sogo_view"
while [[ ${VIEW_OK} != 'OK' ]]; do
mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
CREATE VIEW sogo_view (c_uid, domain, c_name, c_password, c_cn, mail, aliases, ad_aliases, ext_acl, kind, multiple_bookings) AS
mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
CREATE VIEW sogo_view (c_uid, domain, c_name, c_password, c_cn, mail, aliases, ad_aliases, ext_acl, kind, multiple_bookings) AS
SELECT
mailbox.username,
mailbox.domain,
@@ -59,7 +59,7 @@ WHERE
GROUP BY
mailbox.username;
EOF
if [[ ! -z $(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sogo_view'") ]]; then
if [[ ! -z $(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sogo_view'") ]]; then
VIEW_OK=OK
else
echo "Will retry to setup SOGo view in 3s..."
@@ -68,7 +68,7 @@ EOF
done
else
while [[ ${VIEW_OK} != 'OK' ]]; do
if [[ ! -z $(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sogo_view'") ]]; then
if [[ ! -z $(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'sogo_view'") ]]; then
VIEW_OK=OK
else
echo "Waiting for SOGo view to be created by master..."
@@ -81,12 +81,12 @@ fi
if [[ "${MASTER}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
echo "We are master, preparing _sogo_static_view..."
while [[ ${STATIC_VIEW_OK} != 'OK' ]]; do
if [[ ! -z $(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '_sogo_static_view'") ]]; then
if [[ ! -z $(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '_sogo_static_view'") ]]; then
STATIC_VIEW_OK=OK
echo "Updating _sogo_static_view content..."
# If changed, also update init_db.inc.php
mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "REPLACE INTO _sogo_static_view (c_uid, domain, c_name, c_password, c_cn, mail, aliases, ad_aliases, ext_acl, kind, multiple_bookings) SELECT c_uid, domain, c_name, c_password, c_cn, mail, aliases, ad_aliases, ext_acl, kind, multiple_bookings from sogo_view;"
mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "DELETE FROM _sogo_static_view WHERE c_uid NOT IN (SELECT username FROM mailbox WHERE active = '1')"
mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "REPLACE INTO _sogo_static_view (c_uid, domain, c_name, c_password, c_cn, mail, aliases, ad_aliases, ext_acl, kind, multiple_bookings) SELECT c_uid, domain, c_name, c_password, c_cn, mail, aliases, ad_aliases, ext_acl, kind, multiple_bookings from sogo_view;"
mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "DELETE FROM _sogo_static_view WHERE c_uid NOT IN (SELECT username FROM mailbox WHERE active = '1')"
else
echo "Waiting for database initialization..."
sleep 3
@@ -94,7 +94,7 @@ if [[ "${MASTER}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
done
else
while [[ ${STATIC_VIEW_OK} != 'OK' ]]; do
if [[ ! -z $(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '_sogo_static_view'") ]]; then
if [[ ! -z $(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = '_sogo_static_view'") ]]; then
STATIC_VIEW_OK=OK
else
echo "Waiting for database initialization by master..."
@@ -107,9 +107,9 @@ fi
# Recreate password update trigger
if [[ "${MASTER}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
echo "We are master, preparing update trigger..."
mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "DROP TRIGGER IF EXISTS sogo_update_password"
mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "DROP TRIGGER IF EXISTS sogo_update_password"
while [[ ${TRIGGER_OK} != 'OK' ]]; do
mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} << EOF
DELIMITER -
CREATE TRIGGER sogo_update_password AFTER UPDATE ON _sogo_static_view
FOR EACH ROW
@@ -119,7 +119,7 @@ END;
-
DELIMITER ;
EOF
if [[ ! -z $(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TRIGGERS WHERE TRIGGER_NAME = 'sogo_update_password'") ]]; then
if [[ ! -z $(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -B -e "SELECT 'OK' FROM INFORMATION_SCHEMA.TRIGGERS WHERE TRIGGER_NAME = 'sogo_update_password'") ]]; then
TRIGGER_OK=OK
else
echo "Will retry to setup SOGo password update trigger in 3s"
@@ -216,7 +216,7 @@ while read -r line gal
line=${line} envsubst < /etc/sogo/plist_ldap >> /var/lib/sogo/GNUstep/Defaults/sogod.plist
echo " </array>
</dict>" >> /var/lib/sogo/GNUstep/Defaults/sogod.plist
done < <(mysql --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain, CASE gal WHEN '1' THEN 'YES' ELSE 'NO' END AS gal FROM domain;" -B -N)
done < <(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT domain, CASE gal WHEN '1' THEN 'YES' ELSE 'NO' END AS gal FROM domain;" -B -N)
# Generate footer
echo ' </dict>
@@ -240,8 +240,8 @@ chmod 600 /var/lib/sogo/GNUstep/Defaults/sogod.plist
# fi
#fi
# Copy logo, if any
[[ -f /etc/sogo/sogo-full.svg ]] && cp /etc/sogo/sogo-full.svg /usr/lib/GNUstep/SOGo/WebServerResources/img/sogo-full.svg
# Rename custom logo, if any
[[ -f /etc/sogo/sogo-full.svg ]] && mv /etc/sogo/sogo-full.svg /etc/sogo/custom-fulllogo.svg
# Rsync web content
echo "Syncing web content with named volume"

View File

@@ -1,4 +1,4 @@
FROM alpine:3.20
FROM alpine:3.21
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"

View File

@@ -1,4 +1,4 @@
FROM alpine:3.20
FROM alpine:3.21
LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"

View File

@@ -132,9 +132,9 @@ fi
# Connect to the DB server and store output in vars
if [[ -n $socket ]]; then
ConnectionResult=$(mysql ${optfile} ${socket} ${user} -e "show slave ${connection} status\G" 2>&1)
ConnectionResult=$(mariadb --skip-ssl ${optfile} ${socket} ${user} -e "show slave ${connection} status\G" 2>&1)
else
ConnectionResult=$(mysql ${optfile} ${host} ${port} ${user} -e "show slave ${connection} status\G" 2>&1)
ConnectionResult=$(mariadb --skip-ssl ${optfile} ${host} ${port} ${user} -e "show slave ${connection} status\G" 2>&1)
fi
if [ -z "`echo "${ConnectionResult}" |grep Slave_IO_State`" ]; then

View File

@@ -234,7 +234,7 @@ external_checks() {
diff_c=0
THRESHOLD=${EXTERNAL_CHECKS_THRESHOLD}
# Reduce error count by 2 after restarting an unhealthy container
GUID=$(mysql -u${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT version FROM versions WHERE application = 'GUID'" -BN)
GUID=$(mariadb --skip-ssl -u${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT version FROM versions WHERE application = 'GUID'" -BN)
trap "[ ${err_count} -gt 1 ] && err_count=$(( ${err_count} - 2 ))" USR1
while [ ${err_count} -lt ${THRESHOLD} ]; do
err_c_cur=${err_count}

View File

@@ -1,8 +1,8 @@
-----BEGIN DH PARAMETERS-----
MIIBCAKCAQEA9iHB0CRDhV8wfBgqnmvuJpl0fzL3qL75R4ZvQHlfMNLrxuIz2x9D
9zcDhPcBTVzV5Ay0AAkke4wP6r6wDQqXqBP4Y8IOkYAyLh3jM40jfHQzQt+5JdQl
ond3kiscBsFOch/vMfSLMu3lAb0YhPNTvrxhMz7LcVAWYl82swASupdiKR+MgaQr
XsugpmDKsHW60VmIM9B7K9Y+rNHwvMWkmISd0KxA8oOy1WJvsVEissMALZDE3c4w
2xHmO2lXxgEx3aez28736t4m/KW3g9Zr31a1M0KusmfY//fGkPk4NUrLBOS2xrgp
Y/rG1qSBdcVyerM0Ki93qCyHKYu4ene0OwIBAg==
MIIBCAKCAQEA//////////+t+FRYortKmq/cViAnPTzx2LnFg84tNpWp4TZBFGQz
+8yTnc4kmz75fS/jY2MMddj2gbICrsRhetPfHtXV/WVhJDP1H18GbtCFY2VVPe0a
87VXE15/V8k1mE8McODmi3fipona8+/och3xWKE2rec1MKzKT0g6eXq8CrGCsyT7
YdEIqUuyyOP7uWrat2DX9GgdT0Kj3jlN9K5W7edjcrsZCwenyO4KbXCeAvzhzffi
7MA0BM0oNC9hkXL+nOmFg/+OTxIy7vKBg8P+OxtMb61zO7X8vC7CIAXFjvGDfRaD
ssbzSibBsu/6iGtCOGEoXJf//////////wIBAg==
-----END DH PARAMETERS-----

View File

@@ -278,6 +278,7 @@ imap_max_line_length = 2 M
#auth_cache_negative_ttl = 0
#auth_cache_ttl = 30 s
#auth_cache_size = 2 M
auth_verbose_passwords = sha1:6
service replicator {
process_min_avail = 1
}

View File

@@ -1,7 +1,7 @@
[mysqld]
character-set-client-handshake = FALSE
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
collation-server = utf8mb4_general_ci
#innodb_file_per_table = TRUE
#innodb_file_format = barracuda
#innodb_large_prefix = TRUE
@@ -20,7 +20,7 @@ thread_cache_size = 8
query_cache_type = 0
query_cache_size = 0
max_heap_table_size = 48M
thread_stack = 192K
thread_stack = 256K
skip-host-cache
skip-name-resolve
log-warnings = 0

View File

@@ -52,10 +52,12 @@ set_real_ip_from 10.0.0.0/8;
set_real_ip_from 172.16.0.0/12;
set_real_ip_from 192.168.0.0/16;
set_real_ip_from fc00::/7;
{% if not TRUSTED_NETWORK %}
{% for TRUSTED_PROXY in TRUSTED_PROXIES %}
set_real_ip_from {{ TRUSTED_PROXY }};
{% endfor %}
{% if not NGINX_USE_PROXY_PROTOCOL %}
real_ip_header X-Forwarded-For;
{% else %}
set_real_ip_from {{ TRUSTED_NETWORK }};
real_ip_header proxy_protocol;
{% endif %}
real_ip_recursive on;

View File

@@ -162,10 +162,9 @@ transport_maps = pcre:/opt/postfix/conf/custom_transport.pcre,
proxy:mysql:/opt/postfix/conf/sql/mysql_relay_ne.cf,
proxy:mysql:/opt/postfix/conf/sql/mysql_transport_maps.cf
smtp_sasl_auth_soft_bounce = no
postscreen_discard_ehlo_keywords = silent-discard, dsn, chunking
smtpd_discard_ehlo_keywords = chunking, silent-discard
postscreen_discard_ehlo_keywords = chunking, silent-discard, smtputf8, dsn
smtpd_discard_ehlo_keywords = chunking, silent-discard, smtputf8
compatibility_level = 3.7
smtputf8_enable = no
# Define protocols for SMTPS and submission service
submission_smtpd_tls_mandatory_protocols = >=TLSv1.2
smtps_smtpd_tls_mandatory_protocols = >=TLSv1.2

View File

@@ -1,6 +1,6 @@
# Whitelist generated by Postwhite v3.4 on Sat Feb 1 00:18:03 UTC 2025
# Whitelist generated by Postwhite v3.4 on Sat Mar 1 00:19:29 UTC 2025
# https://github.com/stevejenkins/postwhite/
# 1984 total rules
# 2000 total rules
2a00:1450:4000::/36 permit
2a01:111:f400::/48 permit
2a01:111:f403:8000::/50 permit
@@ -8,6 +8,13 @@
2a01:111:f403::/49 permit
2a01:111:f403:c000::/51 permit
2a01:111:f403:f000::/52 permit
2a01:b747:3000:200::/56 permit
2a01:b747:3001:200::/56 permit
2a01:b747:3002:200::/56 permit
2a01:b747:3003:200::/56 permit
2a01:b747:3004:200::/56 permit
2a01:b747:3005:200::/56 permit
2a01:b747:3006:200::/56 permit
2a02:a60:0:5::/64 permit
2c0f:fb50:4000::/36 permit
2.207.151.53 permit
@@ -19,7 +26,6 @@
8.20.114.31 permit
8.25.194.0/23 permit
8.25.196.0/23 permit
10.162.0.0/16 permit
12.130.86.238 permit
13.110.208.0/21 permit
13.110.209.0/24 permit
@@ -35,7 +41,9 @@
17.57.156.0/24 permit
17.58.0.0/16 permit
17.142.0.0/15 permit
17.143.234.140/30 permit
18.97.0.8/30 permit
18.97.1.184/29 permit
18.97.2.64/26 permit
18.156.89.250 permit
18.157.243.190 permit
18.194.95.56 permit
@@ -283,6 +291,9 @@
64.207.219.13 permit
64.207.219.14 permit
64.207.219.15 permit
64.207.219.24 permit
64.207.219.25 permit
64.207.219.26 permit
64.207.219.71 permit
64.207.219.72 permit
64.207.219.73 permit
@@ -292,6 +303,9 @@
64.207.219.77 permit
64.207.219.78 permit
64.207.219.79 permit
64.207.219.88 permit
64.207.219.89 permit
64.207.219.90 permit
64.207.219.135 permit
64.207.219.136 permit
64.207.219.137 permit
@@ -1464,6 +1478,8 @@
159.135.224.0/20 permit
159.135.228.10 permit
159.183.0.0/16 permit
159.183.68.71 permit
159.183.79.38 permit
160.1.62.192 permit
161.38.192.0/20 permit
161.38.204.0/22 permit

View File

@@ -2,6 +2,11 @@
cat <<EOF > /redis.conf
requirepass $REDISPASS
user quota_notify on nopass ~QW_* -@all +get +hget +ping
EOF
if [ -n "$REDISMASTERPASS" ]; then
echo "masterauth $REDISMASTERPASS" >> /redis.conf
fi
exec redis-server /redis.conf

View File

@@ -3,6 +3,7 @@ dns {
}
map_watch_interval = 30s;
task_timeout = 30s;
enable_mime_utf = true;
disable_monitoring = true;
# In case a task times out (like DNS lookup), soft reject the message
# instead of silently accepting the message without further processing.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY st0 "fill:#50BD37;">
]>
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="640px" height="350px" viewBox="78.712 58.488 640 350" style="enable-background:new 78.712 58.488 640 350;"
xml:space="preserve">
<path style="&st0;" d="M648.541,145.679c-9.947,0-17.009-7.278-17.009-17.048c0-9.777,7.062-17.057,17.009-17.057
c10.024,0,17.086,7.279,17.086,17.057C665.627,138.401,658.565,145.679,648.541,145.679z M648.511,94.893
c-19.693,0-33.679,14.4-33.679,33.738c0,19.33,13.985,33.729,33.679,33.729c19.822,0,33.808-14.4,33.808-33.729
C682.318,109.293,668.333,94.893,648.511,94.893z M648.482,179.843c-29.889,0-51.123-21.868-51.123-51.212
c0-29.353,21.234-51.209,51.123-51.209c30.082,0,51.307,21.856,51.307,51.209C699.789,157.975,678.564,179.843,648.482,179.843z
M648.442,58.488c-40.929,0-69.995,29.946-69.995,70.143c0,40.189,29.066,70.125,69.995,70.125c41.194,0,70.27-29.937,70.27-70.125
C718.712,88.434,689.637,58.488,648.442,58.488z M158.166,183.902l-21.018-5.008c-19.131-4.396-28.849-9.413-28.849-23.21
c0-15.684,15.99-21.965,30.419-21.965c14.667,0,25.382,7.329,31.693,18.737c0.02,0.048,0.051,0.097,0.09,0.157
c0.127,0.247,0.276,0.484,0.403,0.731l0.03-0.02c1.985,3.002,5.323,5.008,8.919,5.008c6.122,0,10.558-4.425,10.558-10.547
c0-2.341-0.504-4.82-1.601-6.688c-10.764-18.302-28.513-26.192-48.838-26.192c-27.594,0-54.262,13.797-54.262,44.218
c0,27.921,27.605,36.079,37.64,38.578l20.069,4.71c15.368,3.763,27.912,8.791,27.912,23.517c0,16.938-17.561,23.943-34.499,23.943
c-17.245,0-30.015-9.37-38.814-22.37h-0.01c-1.956-3-4.988-4.328-8.702-4.328c-5.984,0-10.805,5.185-10.587,11.162
c0.098,2.438,0.909,4.637,2.153,6.405c13.787,20.633,33.728,28.41,55.96,28.41c28.543,0,57.085-13.143,57.085-45.132
C193.918,203.325,178.551,188.613,158.166,183.902z M298.479,250.312c-33.866,0-55.199-25.403-55.199-58.331
c0-32.939,21.333-58.343,55.199-58.343c34.192,0,55.516,25.403,55.516,58.343C353.996,224.91,332.672,250.312,298.479,250.312z
M298.479,114.823c-45.471,0-77.777,32.93-77.777,77.158c0,44.217,32.306,77.146,77.777,77.146
c45.786,0,78.093-32.929,78.093-77.146C376.572,147.753,344.266,114.823,298.479,114.823z M518.715,234.312
c-0.771,0.74-1.549,1.472-2.399,2.175c-1.106,1.014-2.391,2.112-3.854,3.208c-8.829,6.391-19.979,10.094-33.017,10.094
c-33.876,0-55.198-25.402-55.198-58.332c0-32.939,21.322-58.342,55.198-58.342c34.183,0,55.506,25.403,55.506,58.342
C534.951,208.653,529.135,223.774,518.715,234.312z M468.097,317.938c2.528,0,5.146-0.168,7.863-0.504
c5.018-0.631,9.588-0.909,13.729-0.909c19.24,0.109,29.036,5.7,34.943,12.158c5.895,6.499,8.168,15.311,8.158,22.796
c0.01,3.586-0.555,6.795-1.177,8.721c-2.944,8.93-8.888,15.002-17.996,19.576c-9.035,4.484-21.095,6.777-33.707,6.757
c-4.514,0-9.105-0.288-13.639-0.831c-8.573-0.987-19.911-4.671-28.13-11.093c-4.138-3.199-6.458-6.991-8.858-11.485
c-2.379-4.514-2.783-9.748-2.783-16.442v-0.742c0-12.346,4.84-20.544,11.051-26.5c3.07-2.904,5.69-5.064,7.99-6.438
c0.366-0.218,0.438-0.416,0.755-0.593C452.39,316.014,459.684,317.968,468.097,317.938z M479.445,114.301
c-45.471,0-77.786,32.929-77.786,77.157c0,29.887,14.765,54.598,38.378,67.489c-0.314,0.314-0.621,0.641-0.916,0.966
c-6.104,6.687-9.226,15.25-9.236,23.913c-0.008,3.821,0.624,7.741,1.977,11.494c-3.062,1.956-6.717,4.634-10.46,8.147
c-9.026,8.408-18.734,22.541-19.021,42.097c-0.01,0.454-0.01,0.829-0.01,1.118c-0.01,10.071,2.379,19.157,6.459,26.774
c6.133,11.466,15.683,19.445,25.539,24.77c9.917,5.334,20.257,8.166,29.273,9.274c5.373,0.643,10.826,0.988,16.268,0.988
c15.151-0.02,30.261-2.578,43.409-9.019c13.085-6.34,24.333-17.253,29.192-32.562c1.443-4.553,2.212-9.719,2.231-15.428
c-0.02-11.595-3.349-25.759-13.767-37.452c-10.421-11.734-27.654-19.566-51.288-19.459c-5.138,0-10.606,0.356-16.426,1.078
c-1.877,0.227-3.596,0.334-5.166,0.334c-7.239-0.048-10.872-2.053-13.036-4.098c-2.133-2.084-3.2-4.839-3.229-8.058
c-0.01-3.28,1.284-6.727,3.467-9.078c2.231-2.332,5.008-3.91,9.846-3.97c0.436,0,0.9,0.01,1.374,0.05
c3.101,0.216,6.112,0.325,9.037,0.325c24.188,0.047,42.38-7.448,54.756-17.759c12.415-10.312,18.971-22.854,22.071-32.76l-0.04-0.01
c3.37-8.899,5.197-18.715,5.197-29.166C557.539,147.229,525.234,114.301,479.445,114.301z"/>
</svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" [
<!ENTITY st0 "fill:#50BD37;">
]>
<svg version="1.1" id="SOGo" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="140.263px" height="140.269px" viewBox="499.737 0 140.263 140.269"
style="enable-background:new 499.737 0 140.263 140.269;" xml:space="preserve">
<path style="&st0;" d="M569.697,87.024c-9.928,0-16.975-7.264-16.975-17.017c0-9.757,7.047-17.022,16.975-17.022
c10.006,0,17.054,7.265,17.054,17.022C586.751,79.76,579.703,87.024,569.697,87.024z M569.667,36.335
c-19.657,0-33.614,14.372-33.614,33.673c0,19.294,13.955,33.667,33.614,33.667c19.787,0,33.745-14.372,33.745-33.667
C603.411,50.707,589.454,36.335,569.667,36.335z M569.639,121.123c-29.833,0-51.025-21.825-51.025-51.115
c0-29.296,21.192-51.111,51.025-51.111c30.025,0,51.213,21.815,51.213,51.111C620.852,99.298,599.664,121.123,569.639,121.123z
M569.602,0c-40.854,0-69.864,29.889-69.864,70.007c0,40.112,29.01,69.993,69.864,69.993c41.116,0,70.136-29.88,70.136-69.993
C639.737,29.889,610.719,0,569.602,0z"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -409,7 +409,7 @@ paths:
description: a list of domains for which a dkim key should be generated
type: string
key_size:
description: the key size (1024 or 2048)
description: the key size (1024, 2048, 3072 or 4096)
type: number
type: object
summary: Generate DKIM Key

View File

@@ -166,6 +166,9 @@ if (isset($_SESSION['mailcow_cc_role'])) {
if (substr($result['recipient_map_old'], 0, 1) == '@') {
$result['recipient_map_old'] = substr($result['recipient_map_old'], 1);
}
if (substr($result['recipient_map_new'], 0, 1) == '@') {
$result['recipient_map_new'] = substr($result['recipient_map_new'], 1);
}
$template = 'edit/recipient_map.twig';
$template_data = ['map' => $map];
}

View File

@@ -270,6 +270,9 @@ function recipient_map($_action, $_data = null, $attr = null) {
$old_dest = substr($old_dest, 1);
}
$new_dest = strtolower(trim($_data['recipient_map_new']));
if (substr($new_dest, 0, 1) == '@') {
$new_dest = substr($new_dest, 1);
}
$active = intval($_data['active']);
if (is_valid_domain_name($old_dest)) {
$old_dest_sane = '@' . idn_to_ascii($old_dest, 0, INTL_IDNA_VARIANT_UTS46);
@@ -285,7 +288,13 @@ function recipient_map($_action, $_data = null, $attr = null) {
);
return false;
}
if (!filter_var($new_dest, FILTER_VALIDATE_EMAIL)) {
if (is_valid_domain_name($new_dest)) {
$new_dest_sane = '@' . idn_to_ascii($new_dest, 0, INTL_IDNA_VARIANT_UTS46);
}
elseif (filter_var($new_dest, FILTER_VALIDATE_EMAIL)) {
$new_dest_sane = $new_dest;
}
else {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data, $_attr),
@@ -308,7 +317,7 @@ function recipient_map($_action, $_data = null, $attr = null) {
(:old_dest, :new_dest, :active)");
$stmt->execute(array(
':old_dest' => $old_dest_sane,
':new_dest' => $new_dest,
':new_dest' => $new_dest_sane,
':active' => $active
));
$_SESSION['return'][] = array(
@@ -325,6 +334,9 @@ function recipient_map($_action, $_data = null, $attr = null) {
$active = (isset($_data['active'])) ? intval($_data['active']) : $is_now['active'];
$new_dest = (!empty($_data['recipient_map_new'])) ? $_data['recipient_map_new'] : $is_now['recipient_map_new'];
$old_dest = (!empty($_data['recipient_map_old'])) ? $_data['recipient_map_old'] : $is_now['recipient_map_old'];
if (substr($new_dest, 0, 1) == '@') {
$new_dest = substr($new_dest, 1);
}
if (substr($old_dest, 0, 1) == '@') {
$old_dest = substr($old_dest, 1);
}
@@ -351,7 +363,13 @@ function recipient_map($_action, $_data = null, $attr = null) {
);
continue;
}
if (!filter_var($new_dest, FILTER_VALIDATE_EMAIL)) {
if (is_valid_domain_name($new_dest)) {
$new_dest_sane = '@' . idn_to_ascii($new_dest, 0, INTL_IDNA_VARIANT_UTS46);
}
elseif (filter_var($new_dest, FILTER_VALIDATE_EMAIL)) {
$new_dest_sane = $new_dest;
}
else {
$_SESSION['return'][] = array(
'type' => 'danger',
'log' => array(__FUNCTION__, $_action, $_data, $_attr),
@@ -378,7 +396,7 @@ function recipient_map($_action, $_data = null, $attr = null) {
WHERE `id`= :id");
$stmt->execute(array(
':old_dest' => $old_dest_sane,
':new_dest' => $new_dest,
':new_dest' => $new_dest_sane,
':active' => $active,
':id' => $id
));

View File

@@ -240,9 +240,12 @@ function dkim($_action, $_data = null, $privkey = false) {
if (strlen($dkimdata['pubkey']) < 391) {
$dkimdata['length'] = "1024";
}
elseif (strlen($dkimdata['pubkey']) < 736) {
elseif (strlen($dkimdata['pubkey']) < 564) {
$dkimdata['length'] = "2048";
}
elseif (strlen($dkimdata['pubkey']) < 736) {
$dkimdata['length'] = "3072";
}
elseif (strlen($dkimdata['pubkey']) < 1416) {
$dkimdata['length'] = "4096";
}

View File

@@ -869,7 +869,7 @@
"recipient_map": "Mapa příjemce",
"recipient_map_info": "Mapy příjemců slouží k nahrazení cílové adresy zprávy před doručením.",
"recipient_map_new": "Nový přijemce",
"recipient_map_new_info": "Cílová adresa mapy příjemce musí být platná emailová adresa.",
"recipient_map_new_info": "Cílová adresa mapy příjemce musí být emailová adresa nebo název domény.",
"recipient_map_old": "Původní příjemce",
"recipient_map_old_info": "Původní příjemce musí být platná emailová adresa nebo název domény.",
"recipient_maps": "Mapy příjemců",
@@ -1307,4 +1307,4 @@
"session_token": "Token formuláře není platný: Token mismatch",
"session_ua": "Token formuláře není platný: User-Agent validation error"
}
}
}

View File

@@ -706,7 +706,7 @@
"recipient_map": "Modtagerkort",
"recipient_map_info": "Modtagerkort bruges til at erstatte destinationsadressen i en meddelelse, før den leveres.",
"recipient_map_new": "Ny modtager",
"recipient_map_new_info": "Modtagerkortdestination skal være en gyldig e-mail-adresse.",
"recipient_map_new_info": "Modtagerkortdestination skal være gyldige e-mail-adresser eller et domænenavn.",
"recipient_map_old": "Original modtager",
"recipient_map_old_info": "En modtager kortlægger den originale destination, skal være gyldige e-mail-adresser eller et domænenavn.",
"recipient_maps": "Modtagerkort",
@@ -1089,4 +1089,4 @@
"first": "Først"
}
}
}
}

View File

@@ -881,7 +881,7 @@
"recipient_map": "Empfängerumschreibung",
"recipient_map_info": "Empfängerumschreibung ersetzen den Empfänger einer E-Mail vor dem Versand.",
"recipient_map_new": "Neuer Empfänger",
"recipient_map_new_info": "Der neue Empfänger muss eine E-Mail-Adresse sein.",
"recipient_map_new_info": "Der neue Empfänger muss eine E-Mail-Adresse oder ein Domainname sein.",
"recipient_map_old": "Original-Empfänger",
"recipient_map_old_info": "Der originale Empfänger muss eine E-Mail-Adresse oder ein Domainname sein.",
"recipient_maps": "Empfängerumschreibungen",
@@ -1336,4 +1336,4 @@
"hour": "Nachrichten / Stunde",
"day": "Nachrichten / Tag"
}
}
}

View File

@@ -887,9 +887,9 @@
"recipient_map": "Recipient map",
"recipient_map_info": "Recipient maps are used to replace the destination address on a message before it is delivered.",
"recipient_map_new": "New recipient",
"recipient_map_new_info": "Recipient map destination must be a valid email address.",
"recipient_map_new_info": "Recipient map destination must be a valid email addresses or a domain name.",
"recipient_map_old": "Original recipient",
"recipient_map_old_info": "A recipient maps original destination must be valid email addresses or a domain name.",
"recipient_map_old_info": "A recipient maps original destination must be a valid email addresses or a domain name.",
"recipient_maps": "Recipient maps",
"relay_all": "Relay all recipients",
"relay_unknown": "Relay unknown mailboxes",
@@ -1336,4 +1336,4 @@
"session_token": "Form token invalid: Token mismatch",
"session_ua": "Form token invalid: User-Agent validation error"
}
}
}

View File

@@ -24,7 +24,9 @@
"protocol_access": "Cambiar protocolo de acceso",
"quarantine_category": "Cambiar categoría de las notificaciones de cuarentena",
"domain_relayhost": "Cambiar relayhost por un dominio",
"extend_sender_acl": "Permitir extender la ACL del remitente por direcciones externas"
"extend_sender_acl": "Permitir extender la ACL del remitente por direcciones externas",
"pw_reset": "Permitir el reset de la contraseña del usario mailcow",
"sogo_access": "Permitir la gestión del acceso a SOGo"
},
"add": {
"activate_filter_warn": "Todos los demás filtros se desactivarán cuando este filtro se active.",
@@ -95,7 +97,10 @@
"app_password": "Añadir contraseña para la app",
"public_comment": "Comentarios públicos",
"disable_login": "Desactivar login (el correo entrante seguirá activo)",
"comment_info": "Los comentarios privados no son visibles al usuario, mientras que los comentarios públicos aparecerán sobre la información general del usuario"
"comment_info": "Los comentarios privados no son visibles al usuario, mientras que los comentarios públicos aparecerán sobre la información general del usuario",
"dry": "Simular la sincronización",
"private_comment": "Comentario privado",
"app_passwd_protocols": "Protocolos autorizados para la contraseña de la aplicación"
},
"admin": {
"access": "Acceso",
@@ -539,7 +544,7 @@
"recipient_map": "Regla de destinatario",
"recipient_map_info": "Las reglas de destinatarios se utilizan para reemplazar la dirección de destino en un mensaje antes de que se entregue.",
"recipient_map_new": "Destinatario nuevo",
"recipient_map_new_info": "El destino de la regla debe ser una dirección de correo válida.",
"recipient_map_new_info": "El destino de la regla debe ser una dirección de correo electrónico válida o un nombre de dominio.",
"recipient_map_old": "Destinatario original",
"recipient_map_old_info": "El destino original de una regla de destinatario debe ser una dirección de correo electrónico válida o un nombre de dominio.",
"recipient_maps": "Reglas de destinatario",
@@ -777,4 +782,4 @@
"fuzzy_learn_error": "Error aprendiendo hash: %s",
"ip_invalid": "IP inválida omitida: %s"
}
}
}

View File

@@ -607,7 +607,7 @@
"recipient_map": "Vastaanottajien yhdistämis määritykset",
"recipient_map_info": "Vastaanottajan karttoja käytetään korvaamaan viestin kohde osoite ennen sen toimittamista.",
"recipient_map_new": "Uusi vastaanottaja",
"recipient_map_new_info": "Vastaanottajan yhdistämis kartan kohteen on oltava kelvollinen sähköposti osoite.",
"recipient_map_new_info": "Vastaanottajan yhdistämis kartan kohteen on oltava kelvollinen sähköposti osoite tai verkkotunnus alueen nimi.",
"recipient_map_old": "Alkuperäinen vastaanottaja",
"recipient_map_old_info": "Vastaanottajan yhdistämis määritysten alkuperäisen kohteen on oltava kelvollinen sähköposti osoite tai verkkotunnus alueen nimi.",
"recipient_maps": "Vastaanottajien yhdistämis määritykset",
@@ -906,4 +906,4 @@
"last": "Edellinen"
}
}
}
}

View File

@@ -834,7 +834,7 @@
"recipient_map": "Carte du destinataire",
"recipient_map_info": "Les cartes des destinataires sont utilisées pour remplacer ladresse de destination dun message avant sa livraison.",
"recipient_map_new": "Nouveau destinataire",
"recipient_map_new_info": "La destination de la carte du destinataire doit être une adresse de courriel valide.",
"recipient_map_new_info": "La destination de la carte du destinataire doit être une adresse de courriel valide ou un nom de domaine.",
"recipient_map_old": "Destinataire original",
"recipient_map_old_info": "Une carte de destination originale doit être une adresse de courriel valide ou un nom de domaine.",
"recipient_maps": "Cartes des bénéficiaires",
@@ -1335,4 +1335,4 @@
"hour": "msgs / heure",
"day": "msgs / jour"
}
}
}

View File

@@ -822,9 +822,9 @@
"recipient_map": "Recipient map",
"recipient_map_info": "Recipient maps are used to replace the destination address on a message before it is delivered.",
"recipient_map_new": "New recipient",
"recipient_map_new_info": "Recipient map destination must be a valid email address.",
"recipient_map_new_info": "Recipient map destination must be a valid email addresses or a domain name.",
"recipient_map_old": "Original recipient",
"recipient_map_old_info": "A recipient maps original destination must be valid email addresses or a domain name.",
"recipient_map_old_info": "A recipient maps original destination must be a valid email addresses or a domain name.",
"recipient_maps": "Recipient maps",
"relay_all": "Trasmettere a tutti i destinatari",
"remove": "Rimuovi",
@@ -1306,4 +1306,4 @@
},
"decimal": "."
}
}
}

View File

@@ -887,7 +887,7 @@
"recipient_map": "受信者マップ",
"recipient_map_info": "受信者マップは、メッセージが配信される前に宛先アドレスを置き換えるために使用されます。",
"recipient_map_new": "新しい受信者",
"recipient_map_new_info": "受信者マップの宛先は有効なメールアドレスである必要があります。",
"recipient_map_new_info": "受信者マップの宛先は有効なメールアドレスまたはドメイン名である必要があります。",
"recipient_map_old": "元の受信者",
"recipient_map_old_info": "受信者マップの元の宛先は有効なメールアドレスまたはドメイン名である必要があります。",
"recipient_maps": "受信者マップ",

View File

@@ -25,7 +25,11 @@
"syncjobs": "동기화 작업",
"tls_policy": "TLS 정책",
"unlimited_quota": "메일에 무제한 할당",
"domain_desc": "도메인 설명 변경"
"domain_desc": "도메인 설명 변경",
"pw_reset": "mailcow 사용자 비밀번호 재설정 허용",
"domain_relayhost": "도메인의 릴레이 호스트 변경",
"mailbox_relayhost": "메일함의 릴레이 호스트 변경",
"quarantine_category": "검역소 알림 카테고리 변경"
},
"add": {
"activate_filter_warn": "활성화가 체크되어 있으면 모든 다른 필터들은 비활성화됩니다.",
@@ -101,7 +105,9 @@
"timeout2": "로컬 호스트 연결 시간 초과",
"username": "사용자명",
"validate": "확인하기",
"validation_success": "성공적으로 확인됨"
"validation_success": "성공적으로 확인됨",
"tags": "태그",
"app_passwd_protocols": "앱 비밀번호에 대해 허용되는 프로토콜"
},
"admin": {
"access": "접근",
@@ -195,7 +201,7 @@
"link": "Link",
"loading": "잠시만 기다려주세요...",
"logo_info": "이미지 크기는 상단 탐색 막대의 경우 40px, 시작 페이지의 경우 최대 너비 250px로 조정됩니다. 확장 가능한 그래픽을 권장합니다.",
"lookup_mx": "MX와 목적지 일치 (.outlook.com이 홉을 통해서 MX *.outlook.com을 대상으로 는 모든 메일을 라우트한다.)",
"lookup_mx": "목적지가 MX 이름과 일치하는 정규 표현식입니다. (<code>.*\\.google\\.com</code>를 사용하여 이 홉을 통해 google.com으로 끝나는 모든 메일을 대상으로 하는 MX로 라우팅합니다.)",
"main_name": "\"mailcow UI\" 이름",
"merged_vars_hint": "회색으로 표시된 행은 <code>vars.(local.)php</code> 에서 병합되었고 이는 수정할 수 없습니다.",
"message": "메세지",
@@ -301,7 +307,53 @@
"username": "사용자 이름",
"validate_license_now": "라이선스 서버와 GUID 확인",
"verify": "확인",
"yes": "&#10003;"
"yes": "&#10003;",
"domain_admin": "도메인 관리자",
"f2b_filter": "정규식 필터",
"f2b_manage_external": "외부에서 Fail2Ban 관리",
"f2b_max_ban_time": "최대 차단 시간(초)",
"f2b_regex_info": "고려되는 로그: SOGo, Postfix, Dovecot, PHP-FPM.",
"html": "HTML",
"oauth2_apps": "OAuth2 앱",
"oauth2_add_client": "OAuth2 클라이언트 추가",
"optional": "선택 사항",
"options": "옵션",
"password_length": "비밀번호 길이",
"password_policy_chars": "하나 이상의 알파벳 문자를 포함해야 합니다.",
"password_policy_length": "최소 암호 길이가 %d입니다.",
"password_policy_numbers": "숫자 하나 이상을 포함해야 합니다.",
"password_policy_special_chars": "특수 문자를 포함해야 합니다.",
"password_reset_info": "복구 이메일이 제공되지 않으면 이 기능을 사용할 수 없습니다.",
"password_reset_settings": "비밀번호 복구 설정",
"password_reset_tmpl_html": "HTML 템플릿",
"password_reset_tmpl_text": "Text 템플릿",
"password_settings": "비밀번호 설정",
"queue_unban": "차단 해제",
"restore_template": "기본 템플릿을 복원하려면 비워둡니다.",
"service": "서비스",
"success": "성공",
"dkim_overwrite_key": "기존 DKIM 키 덮어쓰기",
"f2b_ban_time_increment": "차단 시간은 차단될 때마다 증가합니다.",
"password_policy": "비밀번호 정책",
"quarantine_max_score": "메일의 스팸 점수가 이 값보다 높으면 알림을 삭제합니다:<br><small>기본값: 9999.0</small>",
"f2b_manage_external_info": "Fail2ban은 차단 목록을 유지하지만 트래픽을 차단하는 규칙을 능동적으로 설정하지는 않습니다. 트래픽을 외부에서 차단하려면 아래 생성된 차단 목록을 사용하세요.",
"password_policy_lowerupper": "소문자 및 대문자를 포함해야 합니다.",
"transport_test_rcpt_info": "&#8226; null@hosted.mailcow.de 을 사용하여 해외 목적지로 릴레이를 테스트하세요.",
"ip_check_disabled": "IP 확인이 비활성화됩니다. 아래에서 활성화할 수 있습니다<br> <strong>시스템 > 구성 > 옵션 > 사용자 정의</strong>",
"logo_normal_label": "일반",
"logo_dark_label": "다크 모드의 경우 반전",
"convert_html_to_text": "HTML을 일반 텍스트로 변환",
"copy_to_clipboard": "클립보드에 텍스트가 복사되었습니다!",
"cors_settings": "CORS 설정",
"rsettings_preset_4": "도메인에 대해 Rspamd 비활성화",
"ip_check": "IP 확인",
"admins": "관리자",
"admins_ldap": "LDAP 관리자",
"api_read_only": "읽기 전용 액세스",
"api_read_write": "읽기-쓰기 액세스",
"is_mx_based": "MX 기반",
"login_time": "로그인 시간",
"ip_check_opt_in": "외부 IP 주소 확인을 위해 타사 서비스 <strong>ipv4.mailcow.email</strong> 및 <strong>ipv6.mailcow.email</strong>을 사용하도록 설정합니다."
},
"danger": {
"access_denied": "접근이 거부되거나 잘못된 데이터 양식",
@@ -415,7 +467,30 @@
"username_invalid": "%s는 사용지 이름으로 사용할 수 없습니다.",
"validity_missing": "유효 기간을 지정해주세요.",
"value_missing": "모든 값을 입력해주세요.",
"yotp_verification_failed": "Yubico OTP 검증 실패: %s"
"yotp_verification_failed": "Yubico OTP 검증 실패: %s",
"dkim_domain_or_sel_exists": "“%s\"에 대한 DKIM 키가 존재하며 덮어쓰지 않습니다.",
"img_size_exceeded": "이미지가 최대 파일 크기를 초과합니다.",
"invalid_reset_token": "잘못된 리셋 토큰",
"nginx_reload_failed": "Nginx 리로드 실패: %s",
"password_reset_na": "현재 비밀번호 복구를 사용할 수 없습니다. 관리자에게 문의하세요.",
"reset_f2b_regex": "정규식 필터를 제때 재설정하지 못했습니다. 다시 시도하거나 몇 초 더 기다렸다가 웹사이트를 다시 로드하세요.",
"template_exists": "템플릿 %s이(가) 이미 존재합니다.",
"template_id_invalid": "템플릿 ID %s가 잘못되었습니다.",
"template_name_invalid": "템플릿 이름이 잘못되었습니다.",
"tfa_token_invalid": "TFA 토큰이 유효하지 않습니다.",
"to_invalid": "수신자가 비어 있지 않아야 합니다.",
"webauthn_authenticator_failed": "선택한 인증기를 찾을 수 없습니다.",
"webauthn_username_failed": "선택한 인증기가 다른 계정에 속해 있습니다.",
"demo_mode_enabled": "데모 모드가 활성화됨",
"recovery_email_failed": "복구 이메일을 보낼 수 없습니다. 관리자에게 문의하세요.",
"password_reset_invalid_user": "사서함을 찾을 수 없거나 복구 이메일이 설정되어 있지 않습니다.",
"webauthn_publickey_failed": "선택한 인증기에 대한 공개 키가 저장되지 않았습니다.",
"fido2_verification_failed": "FIDO2 인증 실패: %s",
"extended_sender_acl_denied": "외부 발신자 주소를 설정하는 ACL 누락",
"img_dimensions_exceeded": "이미지가 최대 이미지 크기를 초과합니다.",
"reset_token_limit_exceeded": "토큰 재설정 한도를 초과했습니다. 나중에 다시 시도해 주세요.",
"cors_invalid_method": "잘못된 허용 메서드를 지정했습니다.",
"cors_invalid_origin": "잘못된 허용 원본을 지정했습니다."
},
"debug": {
"chart_this_server": "Chart (this server)",
@@ -434,7 +509,24 @@
"uptime": "Uptime",
"started_on": "Started on",
"static_logs": "Static logs",
"system_containers": "System & Containers"
"system_containers": "System & Containers",
"current_time": "시스템 시간",
"no_update_available": "시스템이 최신 버전입니다.",
"architecture": "아키텍처",
"container_running": "실행 중",
"container_disabled": "컨테이너 중지 또는 비활성화",
"container_stopped": "중지됨",
"online_users": "온라인 사용자",
"service": "서비스",
"success": "성공",
"show_ip": "공인 IP 표시",
"timezone": "시간대",
"update_available": "사용 가능한 업데이트가 있습니다.",
"update_failed": "업데이트를 확인할 수 없습니다",
"username": "사용자 이름",
"memory": "메모리",
"error_show_ip": "공인 IP 주소를 확인할 수 없습니다",
"login_time": "시간"
},
"diagnostics": {
"cname_from_a": "Value derived from A/AAAA record. This is supported as long as the record points to the correct resource.",
@@ -542,7 +634,13 @@
"title": "Edit object",
"unchanged_if_empty": "If unchanged leave blank",
"username": "Username",
"validate_save": "Validate and save"
"validate_save": "Validate and save",
"allow_from_smtp": "다음 IP만 <b>SMTP</b>를 사용하도록 허용합니다.",
"allow_from_smtp_info": "모든 발신자를 허용하려면 비워둡니다.<br>IPv4/IPv6 주소 및 네트워크.",
"allowed_protocols": "허용된 프로토콜",
"app_passwd_protocols": "앱 비밀번호에 대해 허용되는 프로토콜",
"acl": "ACL (권한)",
"admin": "관리자 수정"
},
"footer": {
"cancel": "Cancel",
@@ -560,13 +658,14 @@
"header": {
"administration": "Configuration & Details",
"apps": "Apps",
"debug": "System Information",
"debug": "정보",
"email": "E-Mail",
"mailcow_config": "Configuration",
"quarantine": "Quarantine",
"restart_netfilter": "Restart netfilter",
"restart_sogo": "Restart SOGo",
"user_settings": "User Settings"
"user_settings": "User Settings",
"mailcow_system": "시스템"
},
"info": {
"awaiting_tfa_confirmation": "Awaiting TFA confirmation",
@@ -673,9 +772,9 @@
"recipient_map": "Recipient map",
"recipient_map_info": "Recipient maps are used to replace the destination address on a message before it is delivered.",
"recipient_map_new": "New recipient",
"recipient_map_new_info": "Recipient map destination must be a valid email address.",
"recipient_map_new_info": "Recipient map destination must be a valid email addresses or a domain name.",
"recipient_map_old": "Original recipient",
"recipient_map_old_info": "A recipient maps original destination must be valid email addresses or a domain name.",
"recipient_map_old_info": "A recipient maps original destination must be a valid email addresses or a domain name.",
"recipient_maps": "Recipient maps",
"relay_all": "모든 수신자에게 릴레이",
"remove": "Remove",
@@ -1017,5 +1116,25 @@
"quota_exceeded_scope": "Domain quota exceeded: Only unlimited mailboxes can be created in this domain scope.",
"session_token": "Form token invalid: Token mismatch",
"session_ua": "Form token invalid: User-Agent validation error"
},
"datatables": {
"collapse_all": "모두 접기",
"decimal": ".",
"emptyTable": "테이블에 사용 가능한 데이터가 없습니다.",
"expand_all": "모두 펼치기",
"infoEmpty": "0개 항목 중 0개부터 0개까지 표시",
"infoFiltered": "(_MAX_ 총 항목에서 필터링됨)",
"thousands": ",",
"lengthMenu": "_MENU_ 항목 표시",
"loadingRecords": "로딩 중...",
"processing": "잠시만 기다려 주세요...",
"search": "검색:",
"zeroRecords": "일치하는 레코드가 없습니다.",
"paginate": {
"first": "처음",
"last": "마지막",
"next": "다음",
"previous": "이전"
}
}
}
}

View File

@@ -764,7 +764,7 @@
"recipient_map": "Ontvanger-map",
"recipient_map_info": "Ontvanger-maps worden gebruikt om het doeladres van een bericht te vervangen voordat het in een mailbox terecht komt.",
"recipient_map_new": "Nieuwe ontvanger",
"recipient_map_new_info": "De bestemming van een ontvanger-map dient een geldig mailadres te zijn.",
"recipient_map_new_info": "De bestemming van een ontvanger-map dient een geldig mailadres of domeinnaam te zijn.",
"recipient_map_old": "Oorspronkelijke ontvanger",
"recipient_map_old_info": "De oorspronkelijke bestemming van een ontvanger-map dient een geldig mailadres of domeinnaam te zijn.",
"recipient_maps": "Ontvanger-maps",
@@ -1165,4 +1165,4 @@
"search": "Zoeken:",
"zeroRecords": "Geen overeenkomsten gevonden"
}
}
}

View File

@@ -882,7 +882,7 @@
"recipient_map": "Mapa do destinatário",
"recipient_map_info": "Os mapas de destinatários são usados para substituir o endereço de destino em uma mensagem antes que ela seja entregue.",
"recipient_map_new": "Novo destinatário",
"recipient_map_new_info": "O destino do mapa do destinatário deve ser um endereço de e-mail válido.",
"recipient_map_new_info": "O destino do mapa do destinatário deve ser um endereço de e-mail válido ou um nome de domínio.",
"recipient_map_old": "Destinatário original",
"recipient_map_old_info": "O destino original do mapa de um destinatário deve ser um endereço de e-mail válido ou um nome de domínio.",
"recipient_maps": "Mapas de destinatários",
@@ -1330,4 +1330,4 @@
"session_token": "Token de formulário inválido: incompatibilidade de token",
"session_ua": "Token de formulário inválido: erro de validação do agente de usuário"
}
}
}

View File

@@ -789,7 +789,7 @@
"recipient_map": "Hartă destinatar",
"recipient_map_info": "Hărțile destinatarilor sunt folosite pentru a înlocui adresa de destinație a unui mesaj înainte de a fi livrat.",
"recipient_map_new": "Destinatar nou",
"recipient_map_new_info": "Destinația hărții destinatarului trebuie să fie o adresă de email validă.",
"recipient_map_new_info": "Destinația hărții destinatarului trebuie să fie adrese de email valide sau nume de domeniu.",
"recipient_map_old": "Destinatar original",
"recipient_map_old_info": "Destinația originală a hărților destinatarilor trebuie să fie adrese de email valide sau nume de domeniu.",
"recipient_maps": "Hărți destinatar",
@@ -1208,4 +1208,4 @@
"expand_all": "Expandează tot",
"decimal": ","
}
}
}

View File

@@ -887,7 +887,7 @@
"recipient_map": "Перезапись получателя",
"recipient_map_info": "Перезапись получателя используются для замены получателя в сообщении до его доставки.",
"recipient_map_new": "Перезапись на",
"recipient_map_new_info": "Должен быть действующим почтовым ящиком.",
"recipient_map_new_info": "Должен быть действующим почтовым ящиком или доменом.",
"recipient_map_old": "Получатель",
"recipient_map_old_info": "Должен быть действующим почтовым ящиком или доменом.",
"recipient_maps": "Перезапись получателя",
@@ -1336,4 +1336,4 @@
"session_token": "Неверный токен формы: несоответствие токена",
"session_ua": "Неверный токен формы: ошибка проверки User-Agent"
}
}
}

View File

@@ -822,7 +822,7 @@
"recipient_map": "Mapa príjemcu",
"recipient_map_info": "Mapy príjemcov sú používané ako náhrada cieľovej adresy u správy pred doručením.",
"recipient_map_new": "Nový príjemca",
"recipient_map_new_info": "Mapový cieľ príjemcu musí byť platná emailová adresa.",
"recipient_map_new_info": "Mapový cieľ príjemcu musí byť platná emailová adresa alebo meno domény.",
"recipient_map_old": "Originálny príjemca",
"recipient_map_old_info": "Originálny cieľ mapy príjemcu musí byť platná emailová adresa alebo meno domény.",
"recipient_maps": "Mapy príjemcov",
@@ -1256,4 +1256,4 @@
"session_token": "Formulárový token neplatný: Tokenová nezhoda",
"session_ua": "Formulárový token neplatný: User-Agent validation error"
}
}
}

View File

@@ -726,7 +726,7 @@
"recipient_map": "Mottagaromskrivning",
"recipient_map_info": "En omskrivning av mottagaradressen används för att ersätta destinationsadressen i ett meddelande innan den levereras.",
"recipient_map_new": "Ny mottagare",
"recipient_map_new_info": "Den ursprungliga mottagaren måste vara en giltig e-postadress.",
"recipient_map_new_info": "Den ursprungliga mottagaren måste vara en giltiga e-postadresser eller ett domännamn.",
"recipient_map_old": "Ursprunglig mottagaren",
"recipient_map_old_info": "Den ursprungliga mottagaren måste vara en giltiga e-postadresser eller ett domännamn.",
"recipient_maps": "Skriv om mottagaradressen",
@@ -1110,4 +1110,4 @@
"session_token": "Formulär-nyckeln är ogiltig: Nyckeln matchar inte",
"session_ua": "Formulär-nyckeln är ogiltig: User-Agenten kunde inte valideras"
}
}
}

View File

@@ -72,7 +72,12 @@ elseif (isset($_GET['login'])) {
// only check for admin-login on sogo GUI requests
elseif (isset($_SERVER['HTTP_X_ORIGINAL_URI']) && strcasecmp(substr($_SERVER['HTTP_X_ORIGINAL_URI'], 0, 9), "/SOGo/so/") === 0) {
// this is an nginx auth_request call, we check for existing sogo-sso session variables
session_start();
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/vars.inc.php';
if (file_exists($_SERVER['DOCUMENT_ROOT'] . '/inc/vars.local.inc.php')) {
include_once $_SERVER['DOCUMENT_ROOT'] . '/inc/vars.local.inc.php';
}
require_once $_SERVER['DOCUMENT_ROOT'] . '/inc/sessions.inc.php';
// extract email address from "/SOGo/so/user@domain/xy"
$url_parts = explode("/", $_SERVER['HTTP_X_ORIGINAL_URI']);
$email_list = array(

View File

@@ -117,6 +117,8 @@
<select data-style="btn btn-light btn-sm" class="form-control" id="key_size" name="key_size" title="{{ lang.admin.dkim_key_length }}" required>
<option data-subtext="bits">1024</option>
<option data-subtext="bits">2048</option>
<option data-subtext="bits">3072</option>
<option data-subtext="bits">4096</option>
</select>
</div>
</div>

View File

@@ -235,7 +235,7 @@ function recursiveBase64StrToArrayBuffer(obj) {
$(".totp-authenticator-selection").click(function(){
$(".totp-authenticator-selection").removeClass("active");
$(this).addClass("active");
var id = $(this).children('input').first().val();
$("#totp_selected_id").val(id);
@@ -244,7 +244,7 @@ function recursiveBase64StrToArrayBuffer(obj) {
if ($('.totp-authenticator-selection').length == 1 &&
$('#pending_tfa_tab_yubi_otp').length == 0 &&
$('.webauthn-authenticator-selection').length == 0){
// select default if only one authenticator exists
$('.totp-authenticator-selection').addClass("active");
@@ -257,7 +257,7 @@ function recursiveBase64StrToArrayBuffer(obj) {
$('#pending_tfa_tab_totp').on('shown.bs.tab', function() {
// autofocus
setTimeout(function() { $("#collapseTotpTFA").find('input[name="token"]').focus(); }, 200);
});
});
// validate Yubi OTP tfa
if ($('.webauthn-authenticator-selection').length == 0){
// autofocus
@@ -276,10 +276,10 @@ function recursiveBase64StrToArrayBuffer(obj) {
$(".webauthn-authenticator-selection").click(function(){
$(".webauthn-authenticator-selection").removeClass("active");
$(this).addClass("active");
var id = $(this).children('input').first().val();
$("#webauthn_selected_id").val(id);
var webauthn_status_auth = document.getElementById('webauthn_status_auth');
webauthn_status_auth.style.setProperty('display', 'flex', 'important');
var webauthn_return_code = document.getElementById('webauthn_return_code');
@@ -302,7 +302,7 @@ function recursiveBase64StrToArrayBuffer(obj) {
console.log(json);
if (json.success === false) throw new Error();
if (json.type === "error") throw new Error(json.msg);
recursiveBase64StrToArrayBuffer(json);
return json;
}).then(getCredentialArgs => {
@@ -329,7 +329,7 @@ function recursiveBase64StrToArrayBuffer(obj) {
webauthn_return_code.style.setProperty('display', 'block', 'important');
webauthn_return_code.innerHTML = lang_tfa.error_code + ': ' + err + ' ' + lang_tfa.reload_retry;
});
}
}
});
$('#ConfirmTFAModal').on('hidden.bs.modal', function(){
// 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 }}
</a>
</span>
{% endif %}
{% endif %}
{% if mailcow_cc_username and mailcow_info.mailcow_branch|lower == "nightly" and mailcow_info.version_tag|default %}
<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>
{% 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>
</body>
</html>

View File

@@ -103,6 +103,8 @@
<select data-style="btn btn-light" class="form-control" id="key_size" name="key_size">
<option value="1024" data-subtext="bits" {% if template.attributes.key_size == 1024 %} selected{% endif %}>1024</option>
<option value="2048" data-subtext="bits" {% if template.attributes.key_size == 2048 %} selected{% endif %}>2048</option>
<option value="3072" data-subtext="bits" {% if template.attributes.key_size == 3072 %} selected{% endif %}>3072</option>
<option value="4096" data-subtext="bits" {% if template.attributes.key_size == 4096 %} selected{% endif %}>4096</option>
</select>
</div>
</div>

View File

@@ -490,6 +490,8 @@
<select data-style="btn btn-light" class="form-control" id="key_size" name="key_size">
<option data-subtext="bits" value="1024">1024</option>
<option data-subtext="bits" value="2048" selected>2048</option>
<option data-subtext="bits" value="3072">3072</option>
<option data-subtext="bits" value="4096">4096</option>
</select>
</div>
</div>
@@ -628,6 +630,8 @@
<select data-style="btn btn-light" class="form-control" id="key_size" name="key_size">
<option data-subtext="bits">1024</option>
<option data-subtext="bits" selected>2048</option>
<option data-subtext="bits">3072</option>
<option data-subtext="bits">4096</option>
</select>
</div>
</div>
@@ -843,6 +847,8 @@
<select data-style="btn btn-light" class="form-control" id="key_size2" name="key_size">
<option data-subtext="bits">1024</option>
<option data-subtext="bits" selected>2048</option>
<option data-subtext="bits">3072</option>
<option data-subtext="bits">4096</option>
</select>
</div>
</div>

View File

@@ -1,7 +1,7 @@
services:
unbound-mailcow:
image: mailcow/unbound:1.23
image: ghcr.io/mailcow/unbound:1.24
environment:
- TZ=${TZ}
- SKIP_UNBOUND_HEALTHCHECK=${SKIP_UNBOUND_HEALTHCHECK:-n}
@@ -17,7 +17,7 @@ services:
- unbound
mysql-mailcow:
image: mariadb:10.5
image: mariadb:10.11
depends_on:
- unbound-mailcow
- netfilter-mailcow
@@ -42,8 +42,8 @@ services:
- mysql
redis-mailcow:
image: redis:7-alpine
entrypoint: /redis-conf.sh
image: redis:7.4.6-alpine
entrypoint: ["/bin/sh","/redis-conf.sh"]
volumes:
- redis-vol-1:/data/
- ./data/conf/redis/redis-conf.sh:/redis-conf.sh:z
@@ -55,6 +55,7 @@ services:
environment:
- TZ=${TZ}
- REDISPASS=${REDISPASS}
- REDISMASTERPASS=${REDISMASTERPASS:-}
sysctls:
- net.core.somaxconn=4096
networks:
@@ -64,7 +65,7 @@ services:
- redis
clamd-mailcow:
image: mailcow/clamd:1.70
image: ghcr.io/mailcow/clamd:1.70
restart: always
depends_on:
unbound-mailcow:
@@ -83,7 +84,7 @@ services:
- clamd
rspamd-mailcow:
image: mailcow/rspamd:1.99
image: ghcr.io/mailcow/rspamd:2.1
stop_grace_period: 30s
depends_on:
- dovecot-mailcow
@@ -116,7 +117,7 @@ services:
- rspamd
php-fpm-mailcow:
image: mailcow/phpfpm:1.92
image: ghcr.io/mailcow/phpfpm:1.93
command: "php-fpm -d date.timezone=${TZ} -d expose_php=0"
depends_on:
- redis-mailcow
@@ -182,7 +183,7 @@ services:
- phpfpm
sogo-mailcow:
image: mailcow/sogo:1.128
image: ghcr.io/mailcow/sogo:1.129
environment:
- DBNAME=${DBNAME}
- DBUSER=${DBUSER}
@@ -207,6 +208,9 @@ services:
- ./data/conf/sogo/:/etc/sogo/:z
- ./data/web/inc/init_db.inc.php:/init_db.inc.php:z
- ./data/conf/sogo/custom-favicon.ico:/usr/lib/GNUstep/SOGo/WebServerResources/img/sogo.ico:z
- ./data/conf/sogo/custom-shortlogo.svg:/usr/lib/GNUstep/SOGo/WebServerResources/img/sogo-compact.svg:z
- ./data/conf/sogo/custom-fulllogo.svg:/usr/lib/GNUstep/SOGo/WebServerResources/img/sogo-full.svg:z
- ./data/conf/sogo/custom-fulllogo.png:/usr/lib/GNUstep/SOGo/WebServerResources/img/sogo-logo.png:z
- ./data/conf/sogo/custom-theme.js:/usr/lib/GNUstep/SOGo/WebServerResources/js/theme.js:z
- ./data/conf/sogo/custom-sogo.js:/usr/lib/GNUstep/SOGo/WebServerResources/js/custom-sogo.js:z
- mysql-socket-vol-1:/var/run/mysqld/
@@ -230,7 +234,7 @@ services:
- sogo
dovecot-mailcow:
image: mailcow/dovecot:2.3
image: ghcr.io/mailcow/dovecot:2.34-legacy
depends_on:
- mysql-mailcow
- netfilter-mailcow
@@ -317,7 +321,7 @@ services:
- dovecot
postfix-mailcow:
image: mailcow/postfix:1.79
image: ghcr.io/mailcow/postfix:1.80
depends_on:
mysql-mailcow:
condition: service_started
@@ -373,7 +377,7 @@ services:
- php-fpm-mailcow
- sogo-mailcow
- rspamd-mailcow
image: mailcow/nginx:1.02
image: ghcr.io/mailcow/nginx:1.03
dns:
- ${IPV4_NETWORK:-172.22.1}.254
environment:
@@ -391,6 +395,8 @@ services:
- RSPAMDHOST=${RSPAMDHOST:-}
- REDISHOST=${REDISHOST:-}
- IPV4_NETWORK=${IPV4_NETWORK:-172.22.1}
- NGINX_USE_PROXY_PROTOCOL=${NGINX_USE_PROXY_PROTOCOL:-n}
- TRUSTED_PROXIES=${TRUSTED_PROXIES:-}
volumes:
- ./data/web:/web:ro,z
- ./data/conf/rspamd/dynmaps:/dynmaps:ro,z
@@ -413,7 +419,7 @@ services:
condition: service_started
unbound-mailcow:
condition: service_healthy
image: mailcow/acme:1.91
image: ghcr.io/mailcow/acme:1.92
dns:
- ${IPV4_NETWORK:-172.22.1}.254
environment:
@@ -451,7 +457,7 @@ services:
- acme
netfilter-mailcow:
image: mailcow/netfilter:1.60
image: ghcr.io/mailcow/netfilter:1.62
stop_grace_period: 30s
restart: always
privileged: true
@@ -471,7 +477,7 @@ services:
- /lib/modules:/lib/modules:ro
watchdog-mailcow:
image: mailcow/watchdog:2.06
image: ghcr.io/mailcow/watchdog:2.07
dns:
- ${IPV4_NETWORK:-172.22.1}.254
tmpfs:
@@ -543,7 +549,7 @@ services:
- watchdog
dockerapi-mailcow:
image: mailcow/dockerapi:2.10
image: ghcr.io/mailcow/dockerapi:2.11
security_opt:
- label=disable
restart: always
@@ -563,7 +569,7 @@ services:
- dockerapi
olefy-mailcow:
image: mailcow/olefy:1.13
image: ghcr.io/mailcow/olefy:1.14
restart: always
environment:
- TZ=${TZ}

View File

@@ -26,7 +26,7 @@ for bin in openssl curl docker git awk sha1sum grep cut; do
done
# Check Docker Version (need at least 24.X)
docker_version=$(docker -v | grep -oP '\d+\.\d+\.\d+' | head -n 1 | cut -d '.' -f 1)
docker_version=$(docker version --format '{{.Server.Version}}' | cut -d '.' -f 1)
if [[ $docker_version -lt 24 ]]; then
echo -e "\e[31mCannot find Docker with a Version higher or equals 24.0.0\e[0m"
@@ -181,11 +181,15 @@ if [[ ${SKIP_BRANCH} != y ]]; then
echo "Available Branches:"
echo "- master branch (stable updates) | default, recommended [1]"
echo "- nightly branch (unstable updates, testing) | not-production ready [2]"
echo "- legacy branch (supported until February 2026) | deprecated, security updates only [3]"
sleep 1
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
[3])
MAILCOW_BRANCH="legacy"
;;
[2])
MAILCOW_BRANCH="nightly"
;;
@@ -534,6 +538,10 @@ case ${git_branch} in
mailcow_git_version=$(git rev-parse --short $(git rev-parse @{upstream}))
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_last_git_version=""

View File

@@ -10,46 +10,6 @@ echo "If this script is run automatically by cron or a timer AND you are using b
echo "The snapshots of your backup destination should run AFTER the cold standby script finished to ensure consistent snapshots."
echo
function docker_garbage() {
IMGS_TO_DELETE=()
for container in $(grep -oP "image: \Kmailcow.+" docker-compose.yml); do
REPOSITORY=${container/:*}
TAG=${container/*:}
V_MAIN=${container/*.}
V_SUB=${container/*.}
EXISTING_TAGS=$(docker images | grep ${REPOSITORY} | awk '{ print $2 }')
for existing_tag in ${EXISTING_TAGS[@]}; do
V_MAIN_EXISTING=${existing_tag/*.}
V_SUB_EXISTING=${existing_tag/*.}
# Not an integer
[[ ! ${V_MAIN_EXISTING} =~ ^[0-9]+$ ]] && continue
[[ ! ${V_SUB_EXISTING} =~ ^[0-9]+$ ]] && continue
if [[ ${V_MAIN_EXISTING} == "latest" ]]; then
echo "Found deprecated label \"latest\" for repository ${REPOSITORY}, it should be deleted."
IMGS_TO_DELETE+=(${REPOSITORY}:${existing_tag})
elif [[ ${V_MAIN_EXISTING} -lt ${V_MAIN} ]]; then
echo "Found tag ${existing_tag} for ${REPOSITORY}, which is older than the current tag ${TAG} and should be deleted."
IMGS_TO_DELETE+=(${REPOSITORY}:${existing_tag})
elif [[ ${V_SUB_EXISTING} -lt ${V_SUB} ]]; then
echo "Found tag ${existing_tag} for ${REPOSITORY}, which is older than the current tag ${TAG} and should be deleted."
IMGS_TO_DELETE+=(${REPOSITORY}:${existing_tag})
fi
done
done
if [[ ! -z ${IMGS_TO_DELETE[*]} ]]; then
docker rmi ${IMGS_TO_DELETE[*]}
fi
}
function preflight_local_checks() {
if [[ -z "${REMOTE_SSH_KEY}" ]]; then
>&2 echo -e "\e[31mREMOTE_SSH_KEY is not set\e[0m"
@@ -139,11 +99,11 @@ EOF
if [ $? = 0 ]; then
COMPOSE_COMMAND="docker compose"
echo "DEBUG: Using native docker compose on remote"
echo "INFO: Using native docker compose on remote"
elif [ $? = 1 ]; then
COMPOSE_COMMAND="docker-compose"
echo "DEBUG: Using standalone docker compose on remote"
echo "INFO: Using standalone docker compose on remote"
else
echo -e "\e[31mCannot find any Docker Compose on remote, exiting...\e[0m"
@@ -324,7 +284,7 @@ echo "OK"
-i "${REMOTE_SSH_KEY}" \
${REMOTE_SSH_HOST} \
-p ${REMOTE_SSH_PORT} \
${COMPOSE_COMMAND} -f "${SCRIPT_DIR}/../docker-compose.yml" pull --no-parallel --quiet 2>&1 ; then
${COMPOSE_COMMAND} -f "${SCRIPT_DIR}/../docker-compose.yml" pull --quiet 2>&1 ; then
>&2 echo -e "\e[31m[ERR]\e[0m - Could not pull images on remote"
fi

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env bash
DEBIAN_DOCKER_IMAGE="mailcow/backup:latest"
DEBIAN_DOCKER_IMAGE="ghcr.io/mailcow/backup:latest"
if [[ ! -z ${MAILCOW_BACKUP_LOCATION} ]]; then
BACKUP_LOCATION="${MAILCOW_BACKUP_LOCATION}"

102
update.sh
View File

@@ -36,13 +36,19 @@ docker_garbage() {
IMGS_TO_DELETE=()
declare -A IMAGES_INFO
COMPOSE_IMAGES=($(grep -oP "image: \Kmailcow.+" "${SCRIPT_DIR}/docker-compose.yml"))
COMPOSE_IMAGES=($(grep -oP "image: \K(ghcr\.io/)?mailcow.+" "${SCRIPT_DIR}/docker-compose.yml"))
for existing_image in $(docker images --format "{{.ID}}:{{.Repository}}:{{.Tag}}" | grep 'mailcow/'); do
for existing_image in $(docker images --format "{{.ID}}:{{.Repository}}:{{.Tag}}" | grep -E '(mailcow/|ghcr\.io/mailcow/)'); do
ID=$(echo "$existing_image" | cut -d ':' -f 1)
REPOSITORY=$(echo "$existing_image" | cut -d ':' -f 2)
TAG=$(echo "$existing_image" | cut -d ':' -f 3)
if [[ "$REPOSITORY" == "mailcow/backup" || "$REPOSITORY" == "ghcr.io/mailcow/backup" ]]; then
if [[ "$TAG" != "<none>" ]]; then
continue
fi
fi
if [[ " ${COMPOSE_IMAGES[@]} " =~ " ${REPOSITORY}:${TAG} " ]]; then
continue
else
@@ -57,7 +63,7 @@ docker_garbage() {
echo " ${IMAGES_INFO[$id]} ($id)"
done
if [ ! $FORCE ]; then
if [ -z "$FORCE" ]; then
read -r -p "Do you want to delete them to free up some space? [y/N] " response
if [[ "$response" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
docker rmi ${IMGS_TO_DELETE[*]}
@@ -171,7 +177,7 @@ remove_obsolete_nginx_ports() {
detect_docker_compose_command(){
if ! [[ "${DOCKER_COMPOSE_VERSION}" =~ ^(native|standalone)$ ]]; 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
COMPOSE_COMMAND="docker compose"
echo -e "\e[33mFound Docker Compose Plugin (native).\e[0m"
@@ -181,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"
else
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
fi
elif docker-compose > /dev/null 2>&1; 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
COMPOSE_COMMAND="docker-compose"
echo -e "\e[33mFound Docker Compose Standalone.\e[0m"
@@ -196,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"
else
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
fi
fi
@@ -710,6 +716,50 @@ migrate_solr_config_options() {
fi
}
detect_major_update() {
if [ ${BRANCH} == "master" ]; then
# Array with major versions
# Add major versions here
MAJOR_VERSIONS=(
"2025-02"
"2025-03"
)
current_version=""
if [[ -f "${SCRIPT_DIR}/data/web/inc/app_info.inc.php" ]]; then
current_version=$(grep 'MAILCOW_GIT_VERSION' ${SCRIPT_DIR}/data/web/inc/app_info.inc.php | sed -E 's/.*MAILCOW_GIT_VERSION="([^"]+)".*/\1/')
fi
if [[ -z "$current_version" ]]; then
return 1
fi
release_url="https://github.com/mailcow/mailcow-dockerized/releases/tag"
updates_to_apply=()
for version in "${MAJOR_VERSIONS[@]}"; do
if [[ "$current_version" < "$version" ]]; then
updates_to_apply+=("$version")
fi
done
if [[ ${#updates_to_apply[@]} -gt 0 ]]; then
echo -e "\e[33m\nMAJOR UPDATES to be applied:\e[0m"
for update in "${updates_to_apply[@]}"; do
echo "$update - $release_url/$update"
done
echo -e "\nPlease read the release notes before proceeding."
read -p "Do you want to proceed with the update? [y/n] " response
if [[ "${response}" =~ ^([yY][eE][sS]|[yY])+$ ]]; then
echo "Proceeding with the update..."
else
echo "Update canceled. Exiting."
exit 1
fi
fi
fi
}
############## End Function Section ##############
# Check permissions
@@ -845,8 +895,12 @@ while (($#)); do
echo -e "\e[32mRunning in Developer mode...\e[0m"
DEV=y
;;
--legacy)
CURRENT_BRANCH="$(cd "${SCRIPT_DIR}"; git rev-parse --abbrev-ref HEAD)"
NEW_BRANCH="legacy"
;;
--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)
--check-tags - Check for newer tags and exit (exit codes => 0: newer tag available, 3: no newer tag)
@@ -856,7 +910,8 @@ while (($#)); do
--prefetch - Only prefetch new images and exit (useful to prepare updates)
--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)
--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
-d|--dev - Enables Developer Mode (No Checkout of update.sh for tests)
'
@@ -1262,6 +1317,11 @@ if ! [ "$NEW_BRANCH" ]; then
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"
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
echo -e "\e[33mYou are receiving updates from an unsupported branch.\e[0m"
sleep 1
@@ -1324,6 +1384,29 @@ elif [ "$NEW_BRANCH" == "nightly" ] && [ "$CURRENT_BRANCH" != "nightly" ]; then
fi
git fetch origin
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
if [ ! "$DEV" ]; then
@@ -1345,6 +1428,7 @@ if [ ! "$FORCE" ]; then
echo "OK, exiting."
exit 0
fi
detect_major_update
migrate_docker_nat
fi