mirror of
https://github.com/mailcow/mailcow-dockerized.git
synced 2026-01-22 21:27:14 +00:00
Merge branch 'staging' into nightly
This commit is contained in:
@@ -14,7 +14,7 @@ jobs:
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Mark/Close Stale Issues and Pull Requests 🗑️
|
||||
uses: actions/stale@v10.1.0
|
||||
uses: actions/stale@v10.1.1
|
||||
with:
|
||||
repo-token: ${{ secrets.STALE_ACTION_PAT }}
|
||||
days-before-stale: 60
|
||||
|
||||
2
.github/workflows/image_builds.yml
vendored
2
.github/workflows/image_builds.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
||||
- "watchdog-mailcow"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/checkout@v6
|
||||
- name: Setup Docker
|
||||
run: |
|
||||
curl -sSL https://get.docker.com/ | CHANNEL=stable sudo sh
|
||||
|
||||
4
.github/workflows/pr_to_nightly.yml
vendored
4
.github/workflows/pr_to_nightly.yml
vendored
@@ -8,11 +8,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Run the Action
|
||||
uses: devops-infra/action-pull-request@v0.6.1
|
||||
uses: devops-infra/action-pull-request@v1.0.2
|
||||
with:
|
||||
github_token: ${{ secrets.PRTONIGHTLY_ACTION_PAT }}
|
||||
title: Automatic PR to nightly from ${{ github.event.repository.updated_at}}
|
||||
|
||||
2
.github/workflows/rebuild_backup_image.yml
vendored
2
.github/workflows/rebuild_backup_image.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
packages: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
@@ -15,14 +15,14 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Generate postscreen_access.cidr
|
||||
run: |
|
||||
bash helper-scripts/update_postscreen_whitelist.sh
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
uses: peter-evans/create-pull-request@v8
|
||||
with:
|
||||
token: ${{ secrets.mailcow_action_Update_postscreen_access_cidr_pat }}
|
||||
commit-message: update postscreen_access.cidr
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
# Contribution Guidelines
|
||||
**_Last modified on 15th August 2024_**
|
||||
**_Last modified on 12th November 2025_**
|
||||
|
||||
First of all, thank you for wanting to provide a bugfix or a new feature for the mailcow community, it's because of your help that the project can continue to grow!
|
||||
|
||||
As we want to keep mailcow's development structured we setup these Guidelines which helps you to create your issue/pull request accordingly.
|
||||
|
||||
**PLEASE NOTE, THAT WE MIGHT CLOSE ISSUES/PULL REQUESTS IF THEY DON'T FULLFIL OUR WRITTEN GUIDELINES WRITTEN INSIDE THIS DOCUMENT**. So please check this guidelines before you propose a Issue/Pull Request.
|
||||
**PLEASE NOTE, THAT WE WILL CLOSE ISSUES/PULL REQUESTS IF THEY DON'T FULFILL OUR WRITTEN GUIDELINES WRITTEN INSIDE THIS DOCUMENT**. So please check this guidelines before you propose a Issue/Pull Request.
|
||||
|
||||
## Topics
|
||||
|
||||
@@ -27,14 +27,18 @@ However, please note the following regarding pull requests:
|
||||
6. Please **ALWAYS** create the actual pull request against the staging branch and **NEVER** directly against the master branch. *If you forget to do this, our moobot will remind you to switch the branch to staging.*
|
||||
7. Wait for a merge commit: It may happen that we do not accept your pull request immediately or sometimes not at all for various reasons. Please do not be disappointed if this is the case. We always endeavor to incorporate any meaningful changes from the community into the mailcow project.
|
||||
8. If you are planning larger and therefore more complex pull requests, it would be advisable to first announce this in a separate issue and then start implementing it after the idea has been accepted in order to avoid unnecessary frustration and effort!
|
||||
9. If your PR requires a Docker image rebuild (changes to Dockerfiles or files in data/Dockerfiles/), update the image tag in docker-compose.yml. Use the base-image versioning (e.g. ghcr.io/mailcow/sogo:5.12.4 → :5.12.5 for version bumps; append a letter for patch fixes, e.g. :5.12.4a). Follow this scheme.
|
||||
|
||||
---
|
||||
|
||||
## Issue Reporting
|
||||
**_Last modified on 15th August 2024_**
|
||||
**_Last modified on 12th November 2025_**
|
||||
|
||||
If you plan to report a issue within mailcow please read and understand the following rules:
|
||||
|
||||
### Security disclosures / Security-related fixes
|
||||
- Security vulnerabilities and security fixes must always be reported confidentially first to the contact address specified in SECURITY.md before they are integrated, published, or publicly disclosed in issues/PRs. Please wait for a response from the specified contact to ensure coordinated and responsible disclosure.
|
||||
|
||||
### Issue Reporting Guidelines
|
||||
|
||||
1. **ONLY** use the issue tracker for bug reports or improvement requests and NOT for support questions. For support questions you can either contact the [mailcow community on Telegram](https://docs.mailcow.email/#community-support-and-chat) or the mailcow team directly in exchange for a [support fee](https://docs.mailcow.email/#commercial-support).
|
||||
|
||||
@@ -38,45 +38,45 @@ get_docker_version(){
|
||||
}
|
||||
|
||||
get_compose_type(){
|
||||
if docker compose > /dev/null 2>&1; then
|
||||
if docker compose version --short | grep -e "^2." -e "^v2." > /dev/null 2>&1; then
|
||||
COMPOSE_VERSION=native
|
||||
COMPOSE_COMMAND="docker compose"
|
||||
if [[ "$caller" == "update.sh" ]]; then
|
||||
sed -i 's/^DOCKER_COMPOSE_VERSION=.*/DOCKER_COMPOSE_VERSION=native/' "$SCRIPT_DIR/mailcow.conf"
|
||||
fi
|
||||
echo -e "\e[33mFound Docker Compose Plugin (native).\e[0m"
|
||||
echo -e "\e[33mSetting the DOCKER_COMPOSE_VERSION Variable to native\e[0m"
|
||||
sleep 2
|
||||
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"
|
||||
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
|
||||
COMPOSE_VERSION=standalone
|
||||
COMPOSE_COMMAND="docker-compose"
|
||||
if [[ "$caller" == "update.sh" ]]; then
|
||||
sed -i 's/^DOCKER_COMPOSE_VERSION=.*/DOCKER_COMPOSE_VERSION=standalone/' "$SCRIPT_DIR/mailcow.conf"
|
||||
fi
|
||||
echo -e "\e[33mFound Docker Compose Standalone.\e[0m"
|
||||
echo -e "\e[33mSetting the DOCKER_COMPOSE_VERSION Variable to standalone\e[0m"
|
||||
sleep 2
|
||||
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 manually regarding to this doc site: https://docs.mailcow.email/install/\e[0m"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
if docker compose > /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
|
||||
COMPOSE_VERSION=native
|
||||
COMPOSE_COMMAND="docker compose"
|
||||
if [[ "$caller" == "update.sh" ]]; then
|
||||
sed -i 's/^DOCKER_COMPOSE_VERSION=.*/DOCKER_COMPOSE_VERSION=native/' "$SCRIPT_DIR/mailcow.conf"
|
||||
fi
|
||||
echo -e "\e[33mFound Docker Compose Plugin (native).\e[0m"
|
||||
echo -e "\e[33mSetting the DOCKER_COMPOSE_VERSION Variable to native\e[0m"
|
||||
sleep 2
|
||||
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.\e[0m"
|
||||
echo -e "\e[31mPlease install it regarding to this doc site: https://docs.mailcow.email/install/\e[0m"
|
||||
exit 1
|
||||
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"
|
||||
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 -e "^[2-9]\." -e "^[1-9][0-9]\." > /dev/null 2>&1; then
|
||||
COMPOSE_VERSION=standalone
|
||||
COMPOSE_COMMAND="docker-compose"
|
||||
if [[ "$caller" == "update.sh" ]]; then
|
||||
sed -i 's/^DOCKER_COMPOSE_VERSION=.*/DOCKER_COMPOSE_VERSION=standalone/' "$SCRIPT_DIR/mailcow.conf"
|
||||
fi
|
||||
echo -e "\e[33mFound Docker Compose Standalone.\e[0m"
|
||||
echo -e "\e[33mSetting the DOCKER_COMPOSE_VERSION Variable to standalone\e[0m"
|
||||
sleep 2
|
||||
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 manually regarding to this doc site: https://docs.mailcow.email/install/\e[0m"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
else
|
||||
echo -e "\e[31mCannot find Docker Compose.\e[0m"
|
||||
echo -e "\e[31mPlease install it regarding to this doc site: https://docs.mailcow.email/install/\e[0m"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
detect_bad_asn() {
|
||||
|
||||
@@ -246,6 +246,25 @@ while true; do
|
||||
done
|
||||
VALIDATED_CONFIG_DOMAINS+=("${VALIDATED_CONFIG_DOMAINS_SUBDOMAINS[*]}")
|
||||
done
|
||||
|
||||
# Fetch alias domains where target domain has MTA-STS enabled
|
||||
if [[ ${AUTODISCOVER_SAN} == "y" ]]; then
|
||||
SQL_ALIAS_DOMAINS=$(mariadb --skip-ssl --socket=/var/run/mysqld/mysqld.sock -u ${DBUSER} -p${DBPASS} ${DBNAME} -e "SELECT ad.alias_domain FROM alias_domain ad INNER JOIN mta_sts m ON ad.target_domain = m.domain WHERE ad.active = 1 AND m.active = 1" -Bs)
|
||||
if [[ $? -eq 0 ]]; then
|
||||
while read alias_domain; do
|
||||
if [[ -z "${alias_domain}" ]]; then
|
||||
# ignore empty lines
|
||||
continue
|
||||
fi
|
||||
# Only add mta-sts subdomain for alias domains
|
||||
if [[ "mta-sts.${alias_domain}" != "${MAILCOW_HOSTNAME}" ]]; then
|
||||
if check_domain "mta-sts.${alias_domain}"; then
|
||||
VALIDATED_CONFIG_DOMAINS+=("mta-sts.${alias_domain}")
|
||||
fi
|
||||
fi
|
||||
done <<< "${SQL_ALIAS_DOMAINS}"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if check_domain ${MAILCOW_HOSTNAME}; then
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
FROM debian:bookworm-slim
|
||||
FROM debian:trixie-slim
|
||||
|
||||
RUN apt update && apt install pigz -y --no-install-recommends
|
||||
RUN apt update && apt install pigz zstd -y --no-install-recommends
|
||||
@@ -204,16 +204,17 @@ EOF
|
||||
# Create random master Password for SOGo SSO
|
||||
RAND_PASS=$(cat /dev/urandom | tr -dc 'a-z0-9' | fold -w 32 | head -n 1)
|
||||
echo -n ${RAND_PASS} > /etc/phpfpm/sogo-sso.pass
|
||||
# Creating additional creds file for SOGo notify crons (calendars, etc)
|
||||
echo -n ${RAND_USER}@mailcow.local:${RAND_PASS} > /etc/sogo/cron.creds
|
||||
cat <<EOF > /etc/dovecot/sogo-sso.conf
|
||||
# Autogenerated by mailcow
|
||||
passdb {
|
||||
driver = static
|
||||
args = allow_real_nets=${IPV4_NETWORK}.248/32 password={plain}${RAND_PASS}
|
||||
args = allow_nets=${IPV4_NETWORK}.248/32 password={plain}${RAND_PASS}
|
||||
}
|
||||
EOF
|
||||
|
||||
# Creating additional creds file for SOGo notify crons (calendars, etc) (dummy user, sso password)
|
||||
echo -n ${RAND_USER}@mailcow.local:${RAND_PASS} > /etc/sogo/cron.creds
|
||||
|
||||
if [[ "${MASTER}" =~ ^([nN][oO]|[nN])+$ ]]; then
|
||||
# Toggling MASTER will result in a rebuild of containers, so the quota script will be recreated
|
||||
cat <<'EOF' > /usr/local/bin/quota_notify.py
|
||||
|
||||
@@ -167,7 +167,7 @@ DELIMITER //
|
||||
CREATE EVENT clean_spamalias
|
||||
ON SCHEDULE EVERY 1 DAY DO
|
||||
BEGIN
|
||||
DELETE FROM spamalias WHERE validity < UNIX_TIMESTAMP();
|
||||
DELETE FROM spamalias WHERE validity < UNIX_TIMESTAMP() AND permanent = 0;
|
||||
END;
|
||||
//
|
||||
DELIMITER ;
|
||||
|
||||
@@ -4,7 +4,7 @@ WORKDIR /src
|
||||
ENV CGO_ENABLED=0 \
|
||||
GO111MODULE=on \
|
||||
NOOPT=1 \
|
||||
VERSION=1.8.14
|
||||
VERSION=1.8.22
|
||||
|
||||
RUN git clone --branch v${VERSION} https://github.com/Zuplu/postfix-tlspol && \
|
||||
cd /src/postfix-tlspol && \
|
||||
|
||||
@@ -390,7 +390,7 @@ hosts = unix:/var/run/mysqld/mysqld.sock
|
||||
dbname = ${DBNAME}
|
||||
query = SELECT goto FROM spamalias
|
||||
WHERE address='%s'
|
||||
AND validity >= UNIX_TIMESTAMP()
|
||||
AND (validity >= UNIX_TIMESTAMP() OR permanent != 0)
|
||||
EOF
|
||||
|
||||
if [ ! -f /opt/postfix/conf/dns_blocklists.cf ]; then
|
||||
@@ -524,4 +524,4 @@ if [[ $? != 0 ]]; then
|
||||
else
|
||||
postfix -c /opt/postfix/conf start
|
||||
sleep 126144000
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
FROM debian:bookworm-slim
|
||||
FROM debian:trixie-slim
|
||||
LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
ARG RSPAMD_VER=rspamd_3.13.2-1~8bf602278
|
||||
ARG CODENAME=bookworm
|
||||
ARG RSPAMD_VER=rspamd_3.14.2-82~90302bc
|
||||
ARG CODENAME=trixie
|
||||
ENV LC_ALL=C
|
||||
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||
|
||||
@@ -50,10 +50,6 @@ cat <<EOF > /var/lib/sogo/GNUstep/Defaults/sogod.plist
|
||||
<string>YES</string>
|
||||
<key>SOGoEncryptionKey</key>
|
||||
<string>${RAND_PASS}</string>
|
||||
<key>SOGoURLEncryptionEnabled</key>
|
||||
<string>YES</string>
|
||||
<key>SOGoURLEncryptionPassphrase</key>
|
||||
<string>${SOGO_URL_ENCRYPTION_KEY}</string>
|
||||
<key>OCSAdminURL</key>
|
||||
<string>mysql://${DBUSER}:${DBPASS}@%2Fvar%2Frun%2Fmysqld%2Fmysqld.sock/${DBNAME}/sogo_admin</string>
|
||||
<key>OCSCacheFolderURL</key>
|
||||
|
||||
@@ -13,6 +13,7 @@ events {
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
server_tokens off;
|
||||
|
||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||
'$status $body_bytes_sent "$http_referer" '
|
||||
|
||||
@@ -14,7 +14,6 @@ ssl_session_tickets off;
|
||||
|
||||
add_header Strict-Transport-Security "max-age=15768000;";
|
||||
add_header X-Content-Type-Options nosniff;
|
||||
add_header X-XSS-Protection "1; mode=block";
|
||||
add_header X-Robots-Tag none;
|
||||
add_header X-Download-Options noopen;
|
||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||
|
||||
@@ -7,9 +7,10 @@ opcache.interned_strings_buffer=16
|
||||
opcache.max_accelerated_files=10000
|
||||
opcache.memory_consumption=128
|
||||
opcache.save_comments=1
|
||||
opcache.revalidate_freq=120
|
||||
opcache.validate_timestamps=0
|
||||
|
||||
; JIT
|
||||
opcache.jit=1255
|
||||
opcache.jit_buffer_size=8M
|
||||
; Disabled for now due to some PHP segmentation faults observed
|
||||
; in certain environments. Possibly some PHP or PHP extension bug.
|
||||
opcache.jit=disable
|
||||
opcache.jit_buffer_size=0
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Whitelist generated by Postwhite v3.4 on Wed Oct 1 00:21:33 UTC 2025
|
||||
# Whitelist generated by Postwhite v3.4 on Mon Dec 1 00:24:43 UTC 2025
|
||||
# https://github.com/stevejenkins/postwhite/
|
||||
# 2216 total rules
|
||||
# 2186 total rules
|
||||
2a00:1450:4000::/36 permit
|
||||
2a01:111:f400::/48 permit
|
||||
2a01:111:f403:2800::/53 permit
|
||||
@@ -29,7 +29,9 @@
|
||||
2a01:b747:3005:200::/56 permit
|
||||
2a01:b747:3006:200::/56 permit
|
||||
2a02:a60:0:5::/64 permit
|
||||
2a0f:f640::/56 permit
|
||||
2c0f:fb50:4000::/36 permit
|
||||
2.207.151.53 permit
|
||||
2.207.217.30 permit
|
||||
3.64.237.68 permit
|
||||
3.65.3.180 permit
|
||||
@@ -50,14 +52,11 @@
|
||||
8.25.194.0/23 permit
|
||||
8.25.196.0/23 permit
|
||||
8.36.116.0/24 permit
|
||||
8.39.54.0/23 permit
|
||||
8.39.54.250/31 permit
|
||||
8.39.144.0/24 permit
|
||||
8.40.222.0/23 permit
|
||||
8.40.222.250/31 permit
|
||||
12.130.86.238 permit
|
||||
13.107.213.41 permit
|
||||
13.107.246.41 permit
|
||||
13.107.213.69 permit
|
||||
13.107.246.69 permit
|
||||
13.108.16.0/20 permit
|
||||
13.110.208.0/21 permit
|
||||
13.110.209.0/24 permit
|
||||
13.110.216.0/22 permit
|
||||
@@ -66,6 +65,7 @@
|
||||
13.111.191.0/24 permit
|
||||
13.216.7.111 permit
|
||||
13.216.54.180 permit
|
||||
13.247.164.219 permit
|
||||
15.200.21.50 permit
|
||||
15.200.44.248 permit
|
||||
15.200.201.185 permit
|
||||
@@ -169,7 +169,6 @@
|
||||
34.215.104.144 permit
|
||||
34.218.115.239 permit
|
||||
34.225.212.172 permit
|
||||
34.241.242.183 permit
|
||||
35.83.148.184 permit
|
||||
35.155.198.111 permit
|
||||
35.158.23.94 permit
|
||||
@@ -193,7 +192,6 @@
|
||||
40.233.64.216 permit
|
||||
40.233.83.78 permit
|
||||
40.233.88.28 permit
|
||||
43.239.212.33 permit
|
||||
44.206.138.57 permit
|
||||
44.210.169.44 permit
|
||||
44.217.45.156 permit
|
||||
@@ -275,7 +273,6 @@
|
||||
50.112.246.219 permit
|
||||
52.1.14.157 permit
|
||||
52.5.230.59 permit
|
||||
52.6.74.205 permit
|
||||
52.12.53.23 permit
|
||||
52.13.214.179 permit
|
||||
52.26.1.71 permit
|
||||
@@ -302,7 +299,6 @@
|
||||
52.96.91.34 permit
|
||||
52.96.111.82 permit
|
||||
52.96.172.98 permit
|
||||
52.96.214.50 permit
|
||||
52.96.222.194 permit
|
||||
52.96.222.226 permit
|
||||
52.96.223.2 permit
|
||||
@@ -341,7 +337,6 @@
|
||||
54.244.54.130 permit
|
||||
54.244.242.0/24 permit
|
||||
54.255.61.23 permit
|
||||
56.124.6.228 permit
|
||||
57.103.64.0/18 permit
|
||||
57.129.93.249 permit
|
||||
62.13.128.0/24 permit
|
||||
@@ -402,23 +397,15 @@
|
||||
64.207.219.143 permit
|
||||
64.233.160.0/19 permit
|
||||
65.52.80.137 permit
|
||||
65.54.51.64/26 permit
|
||||
65.54.61.64/26 permit
|
||||
65.54.121.120/29 permit
|
||||
65.54.190.0/24 permit
|
||||
65.54.241.0/24 permit
|
||||
65.55.29.77 permit
|
||||
65.55.33.64/28 permit
|
||||
65.55.34.0/24 permit
|
||||
65.55.42.224/28 permit
|
||||
65.55.52.224/27 permit
|
||||
65.55.78.128/25 permit
|
||||
65.55.81.48/28 permit
|
||||
65.55.90.0/24 permit
|
||||
65.55.94.0/25 permit
|
||||
65.55.111.0/24 permit
|
||||
65.55.113.64/26 permit
|
||||
65.55.116.0/25 permit
|
||||
65.55.126.0/25 permit
|
||||
65.55.174.0/25 permit
|
||||
65.55.178.128/27 permit
|
||||
@@ -426,7 +413,6 @@
|
||||
65.110.161.77 permit
|
||||
65.123.29.213 permit
|
||||
65.123.29.220 permit
|
||||
65.154.166.0/24 permit
|
||||
65.212.180.36 permit
|
||||
66.102.0.0/20 permit
|
||||
66.119.150.192/26 permit
|
||||
@@ -640,7 +626,6 @@
|
||||
74.208.4.220 permit
|
||||
74.208.4.221 permit
|
||||
74.209.250.0/24 permit
|
||||
75.2.70.75 permit
|
||||
76.223.128.0/19 permit
|
||||
76.223.176.0/20 permit
|
||||
77.238.176.0/24 permit
|
||||
@@ -669,18 +654,12 @@
|
||||
81.169.146.245 permit
|
||||
81.169.146.246 permit
|
||||
81.223.46.0/27 permit
|
||||
82.165.159.2 permit
|
||||
82.165.159.3 permit
|
||||
82.165.159.4 permit
|
||||
82.165.159.12 permit
|
||||
82.165.159.13 permit
|
||||
82.165.159.14 permit
|
||||
82.165.159.34 permit
|
||||
82.165.159.35 permit
|
||||
82.165.159.40 permit
|
||||
82.165.159.41 permit
|
||||
82.165.159.42 permit
|
||||
82.165.159.45 permit
|
||||
82.165.159.130 permit
|
||||
82.165.159.131 permit
|
||||
85.9.206.169 permit
|
||||
@@ -1230,11 +1209,8 @@
|
||||
98.139.245.208/30 permit
|
||||
98.139.245.212/31 permit
|
||||
99.78.197.208/28 permit
|
||||
99.83.190.102 permit
|
||||
103.9.96.0/22 permit
|
||||
103.28.42.0/24 permit
|
||||
103.84.217.238 permit
|
||||
103.89.75.238 permit
|
||||
103.151.192.0/23 permit
|
||||
103.168.172.128/27 permit
|
||||
103.237.104.0/22 permit
|
||||
@@ -1400,9 +1376,6 @@
|
||||
117.120.16.0/21 permit
|
||||
119.42.242.52/31 permit
|
||||
119.42.242.156 permit
|
||||
121.244.91.48 permit
|
||||
121.244.91.52 permit
|
||||
122.15.156.182 permit
|
||||
123.126.78.64/29 permit
|
||||
124.108.96.24/31 permit
|
||||
124.108.96.28/31 permit
|
||||
@@ -1430,6 +1403,7 @@
|
||||
128.245.248.0/21 permit
|
||||
129.41.77.70 permit
|
||||
129.41.169.249 permit
|
||||
129.77.16.0/20 permit
|
||||
129.80.5.164 permit
|
||||
129.80.64.36 permit
|
||||
129.80.67.121 permit
|
||||
@@ -1466,10 +1440,6 @@
|
||||
134.170.141.64/26 permit
|
||||
134.170.143.0/24 permit
|
||||
134.170.174.0/24 permit
|
||||
135.84.80.0/24 permit
|
||||
135.84.81.0/24 permit
|
||||
135.84.82.0/24 permit
|
||||
135.84.83.0/24 permit
|
||||
135.84.216.0/22 permit
|
||||
136.143.160.0/24 permit
|
||||
136.143.161.0/24 permit
|
||||
@@ -1481,6 +1451,7 @@
|
||||
136.143.184.0/24 permit
|
||||
136.143.188.0/24 permit
|
||||
136.143.190.0/23 permit
|
||||
136.146.128.0/20 permit
|
||||
136.147.128.0/20 permit
|
||||
136.147.135.0/24 permit
|
||||
136.147.176.0/20 permit
|
||||
@@ -1495,7 +1466,6 @@
|
||||
139.138.46.219 permit
|
||||
139.138.57.55 permit
|
||||
139.138.58.119 permit
|
||||
139.167.79.86 permit
|
||||
139.180.17.0/24 permit
|
||||
140.238.148.191 permit
|
||||
141.148.159.229 permit
|
||||
@@ -1552,9 +1522,6 @@
|
||||
155.248.220.138 permit
|
||||
155.248.234.149 permit
|
||||
155.248.237.141 permit
|
||||
157.55.0.192/26 permit
|
||||
157.55.1.128/26 permit
|
||||
157.55.2.0/25 permit
|
||||
157.55.9.128/25 permit
|
||||
157.55.11.0/25 permit
|
||||
157.55.49.0/25 permit
|
||||
@@ -1618,9 +1585,6 @@
|
||||
164.152.23.32 permit
|
||||
164.152.25.241 permit
|
||||
164.177.132.168/30 permit
|
||||
165.173.128.0/24 permit
|
||||
165.173.180.250/31 permit
|
||||
165.173.182.250/31 permit
|
||||
166.78.68.0/22 permit
|
||||
166.78.68.221 permit
|
||||
166.78.69.169 permit
|
||||
@@ -1650,29 +1614,16 @@
|
||||
168.245.12.252 permit
|
||||
168.245.46.9 permit
|
||||
168.245.127.231 permit
|
||||
169.148.129.0/24 permit
|
||||
169.148.131.0/24 permit
|
||||
169.148.138.0/24 permit
|
||||
169.148.142.10 permit
|
||||
169.148.142.33 permit
|
||||
169.148.144.0/25 permit
|
||||
169.148.144.10 permit
|
||||
169.148.146.0/23 permit
|
||||
169.148.175.3 permit
|
||||
169.148.188.0/24 permit
|
||||
169.148.188.182 permit
|
||||
170.10.128.0/24 permit
|
||||
170.10.129.0/24 permit
|
||||
170.10.132.56/29 permit
|
||||
170.10.132.64/29 permit
|
||||
170.10.133.0/24 permit
|
||||
172.217.32.0/20 permit
|
||||
172.253.56.0/21 permit
|
||||
172.253.112.0/20 permit
|
||||
173.0.84.0/29 permit
|
||||
173.0.84.224/27 permit
|
||||
173.0.94.244/30 permit
|
||||
173.194.0.0/16 permit
|
||||
173.194.0.0/17 permit
|
||||
173.203.79.182 permit
|
||||
173.203.81.39 permit
|
||||
173.224.161.128/25 permit
|
||||
@@ -1812,6 +1763,7 @@
|
||||
194.97.212.12 permit
|
||||
194.106.220.0/23 permit
|
||||
194.113.24.0/22 permit
|
||||
194.113.42.0/26 permit
|
||||
194.154.193.192/27 permit
|
||||
195.4.92.0/23 permit
|
||||
195.54.172.0/23 permit
|
||||
@@ -1825,6 +1777,7 @@
|
||||
198.61.254.21 permit
|
||||
198.61.254.231 permit
|
||||
198.178.234.57 permit
|
||||
198.202.211.1 permit
|
||||
198.244.48.0/20 permit
|
||||
198.244.56.107 permit
|
||||
198.244.56.108 permit
|
||||
@@ -1846,16 +1799,7 @@
|
||||
199.16.156.0/22 permit
|
||||
199.33.145.1 permit
|
||||
199.33.145.32 permit
|
||||
199.34.22.36 permit
|
||||
199.59.148.0/22 permit
|
||||
199.67.80.2 permit
|
||||
199.67.80.20 permit
|
||||
199.67.82.2 permit
|
||||
199.67.82.20 permit
|
||||
199.67.84.0/24 permit
|
||||
199.67.86.0/24 permit
|
||||
199.67.88.0/24 permit
|
||||
199.67.90.0/24 permit
|
||||
199.101.161.130 permit
|
||||
199.101.162.0/25 permit
|
||||
199.122.120.0/21 permit
|
||||
@@ -1912,8 +1856,6 @@
|
||||
204.92.114.187 permit
|
||||
204.92.114.203 permit
|
||||
204.92.114.204/31 permit
|
||||
204.141.32.0/23 permit
|
||||
204.141.42.0/23 permit
|
||||
204.216.164.202 permit
|
||||
204.220.160.0/21 permit
|
||||
204.220.168.0/21 permit
|
||||
@@ -1946,7 +1888,6 @@
|
||||
207.46.52.79 permit
|
||||
207.46.58.128/25 permit
|
||||
207.46.116.128/29 permit
|
||||
207.46.117.0/24 permit
|
||||
207.46.132.128/27 permit
|
||||
207.46.198.0/25 permit
|
||||
207.46.200.0/27 permit
|
||||
@@ -2052,19 +1993,11 @@
|
||||
212.82.111.228/31 permit
|
||||
212.82.111.230 permit
|
||||
212.123.28.40 permit
|
||||
212.227.15.3 permit
|
||||
212.227.15.4 permit
|
||||
212.227.15.5 permit
|
||||
212.227.15.6 permit
|
||||
212.227.15.7 permit
|
||||
212.227.15.8 permit
|
||||
212.227.15.14 permit
|
||||
212.227.15.15 permit
|
||||
212.227.15.18 permit
|
||||
212.227.15.19 permit
|
||||
212.227.15.25 permit
|
||||
212.227.15.26 permit
|
||||
212.227.15.29 permit
|
||||
212.227.15.44 permit
|
||||
212.227.15.45 permit
|
||||
212.227.15.46 permit
|
||||
@@ -2072,17 +2005,11 @@
|
||||
212.227.15.50 permit
|
||||
212.227.15.52 permit
|
||||
212.227.15.53 permit
|
||||
212.227.15.54 permit
|
||||
212.227.15.55 permit
|
||||
212.227.17.1 permit
|
||||
212.227.17.2 permit
|
||||
212.227.17.7 permit
|
||||
212.227.17.11 permit
|
||||
212.227.17.12 permit
|
||||
212.227.17.16 permit
|
||||
212.227.17.17 permit
|
||||
212.227.17.18 permit
|
||||
212.227.17.19 permit
|
||||
212.227.17.20 permit
|
||||
212.227.17.21 permit
|
||||
212.227.17.22 permit
|
||||
@@ -2167,8 +2094,6 @@
|
||||
216.205.24.0/24 permit
|
||||
216.221.160.0/19 permit
|
||||
216.239.32.0/19 permit
|
||||
217.72.192.77 permit
|
||||
217.72.192.78 permit
|
||||
217.77.141.52 permit
|
||||
217.77.141.59 permit
|
||||
217.175.194.0/24 permit
|
||||
@@ -2201,9 +2126,6 @@
|
||||
2603:1030:20e:3::23c permit
|
||||
2603:1030:b:3::152 permit
|
||||
2603:1030:c02:8::14 permit
|
||||
2607:13c0:0001:0000:0000:0000:0000:7000/116 permit
|
||||
2607:13c0:0002:0000:0000:0000:0000:1000/116 permit
|
||||
2607:13c0:0004:0000:0000:0000:0000:0000/116 permit
|
||||
2607:f8b0:4000::/36 permit
|
||||
2620:109:c003:104::/64 permit
|
||||
2620:109:c003:104::215 permit
|
||||
|
||||
@@ -146,8 +146,171 @@ rspamd_config:register_symbol({
|
||||
return false
|
||||
end
|
||||
|
||||
-- Helper function to parse IPv6 into 8 segments
|
||||
local function ipv6_to_segments(ip_str)
|
||||
-- Remove zone identifier if present (e.g., %eth0)
|
||||
ip_str = ip_str:gsub("%%.*$", "")
|
||||
|
||||
local segments = {}
|
||||
|
||||
-- Handle :: compression
|
||||
if ip_str:find('::') then
|
||||
local before, after = ip_str:match('^(.*)::(.*)$')
|
||||
before = before or ''
|
||||
after = after or ''
|
||||
|
||||
local before_parts = {}
|
||||
local after_parts = {}
|
||||
|
||||
if before ~= '' then
|
||||
for seg in before:gmatch('[^:]+') do
|
||||
table.insert(before_parts, tonumber(seg, 16) or 0)
|
||||
end
|
||||
end
|
||||
|
||||
if after ~= '' then
|
||||
for seg in after:gmatch('[^:]+') do
|
||||
table.insert(after_parts, tonumber(seg, 16) or 0)
|
||||
end
|
||||
end
|
||||
|
||||
-- Add before segments
|
||||
for _, seg in ipairs(before_parts) do
|
||||
table.insert(segments, seg)
|
||||
end
|
||||
|
||||
-- Add compressed zeros
|
||||
local zeros_needed = 8 - #before_parts - #after_parts
|
||||
for i = 1, zeros_needed do
|
||||
table.insert(segments, 0)
|
||||
end
|
||||
|
||||
-- Add after segments
|
||||
for _, seg in ipairs(after_parts) do
|
||||
table.insert(segments, seg)
|
||||
end
|
||||
else
|
||||
-- No compression
|
||||
for seg in ip_str:gmatch('[^:]+') do
|
||||
table.insert(segments, tonumber(seg, 16) or 0)
|
||||
end
|
||||
end
|
||||
|
||||
-- Ensure we have exactly 8 segments
|
||||
while #segments < 8 do
|
||||
table.insert(segments, 0)
|
||||
end
|
||||
|
||||
return segments
|
||||
end
|
||||
|
||||
-- Generate all common IPv6 notations
|
||||
local function get_ipv6_variants(ip_str)
|
||||
local variants = {}
|
||||
local seen = {}
|
||||
|
||||
local function add_variant(v)
|
||||
if v and not seen[v] then
|
||||
table.insert(variants, v)
|
||||
seen[v] = true
|
||||
end
|
||||
end
|
||||
|
||||
-- For IPv4, just return the original
|
||||
if not ip_str:find(':') then
|
||||
add_variant(ip_str)
|
||||
return variants
|
||||
end
|
||||
|
||||
local segments = ipv6_to_segments(ip_str)
|
||||
|
||||
-- 1. Fully expanded form (all zeros shown as 0000)
|
||||
local expanded_parts = {}
|
||||
for _, seg in ipairs(segments) do
|
||||
table.insert(expanded_parts, string.format('%04x', seg))
|
||||
end
|
||||
add_variant(table.concat(expanded_parts, ':'))
|
||||
|
||||
-- 2. Standard form (no leading zeros, but all segments present)
|
||||
local standard_parts = {}
|
||||
for _, seg in ipairs(segments) do
|
||||
table.insert(standard_parts, string.format('%x', seg))
|
||||
end
|
||||
add_variant(table.concat(standard_parts, ':'))
|
||||
|
||||
-- 3. Find all possible :: compressions
|
||||
-- RFC 5952: compress the longest run of consecutive zeros
|
||||
-- But we need to check all possibilities since Redis might have any form
|
||||
|
||||
-- Find all zero runs
|
||||
local zero_runs = {}
|
||||
local in_run = false
|
||||
local run_start = 0
|
||||
local run_length = 0
|
||||
|
||||
for i = 1, 8 do
|
||||
if segments[i] == 0 then
|
||||
if not in_run then
|
||||
in_run = true
|
||||
run_start = i
|
||||
run_length = 1
|
||||
else
|
||||
run_length = run_length + 1
|
||||
end
|
||||
else
|
||||
if in_run then
|
||||
if run_length >= 1 then -- Allow single zero compression too
|
||||
table.insert(zero_runs, {start = run_start, length = run_length})
|
||||
end
|
||||
in_run = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Don't forget the last run
|
||||
if in_run and run_length >= 1 then
|
||||
table.insert(zero_runs, {start = run_start, length = run_length})
|
||||
end
|
||||
|
||||
-- Generate variant for each zero run compression
|
||||
for _, run in ipairs(zero_runs) do
|
||||
local parts = {}
|
||||
|
||||
-- Before compression
|
||||
for i = 1, run.start - 1 do
|
||||
table.insert(parts, string.format('%x', segments[i]))
|
||||
end
|
||||
|
||||
-- The compression
|
||||
if run.start == 1 then
|
||||
table.insert(parts, '')
|
||||
table.insert(parts, '')
|
||||
elseif run.start + run.length - 1 == 8 then
|
||||
table.insert(parts, '')
|
||||
table.insert(parts, '')
|
||||
else
|
||||
table.insert(parts, '')
|
||||
end
|
||||
|
||||
-- After compression
|
||||
for i = run.start + run.length, 8 do
|
||||
table.insert(parts, string.format('%x', segments[i]))
|
||||
end
|
||||
|
||||
local compressed = table.concat(parts, ':'):gsub('::+', '::')
|
||||
add_variant(compressed)
|
||||
end
|
||||
|
||||
return variants
|
||||
end
|
||||
|
||||
local from_ip_string = tostring(ip)
|
||||
ip_check_table = {from_ip_string}
|
||||
local ip_check_table = {}
|
||||
|
||||
-- Add all variants of the exact IP
|
||||
for _, variant in ipairs(get_ipv6_variants(from_ip_string)) do
|
||||
table.insert(ip_check_table, variant)
|
||||
end
|
||||
|
||||
local maxbits = 128
|
||||
local minbits = 32
|
||||
@@ -155,10 +318,18 @@ rspamd_config:register_symbol({
|
||||
maxbits = 32
|
||||
minbits = 8
|
||||
end
|
||||
|
||||
-- Add all CIDR notations with variants
|
||||
for i=maxbits,minbits,-1 do
|
||||
local nip = ip:apply_mask(i):to_string() .. "/" .. i
|
||||
table.insert(ip_check_table, nip)
|
||||
local masked_ip = ip:apply_mask(i)
|
||||
local cidr_base = masked_ip:to_string()
|
||||
|
||||
for _, variant in ipairs(get_ipv6_variants(cidr_base)) do
|
||||
local cidr = variant .. "/" .. i
|
||||
table.insert(ip_check_table, cidr)
|
||||
end
|
||||
end
|
||||
|
||||
local function keep_spam_cb(err, data)
|
||||
if err then
|
||||
rspamd_logger.infox(rspamd_config, "keep_spam query request for ip %s returned invalid or empty data (\"%s\") or error (\"%s\")", ip, data, err)
|
||||
@@ -166,12 +337,15 @@ rspamd_config:register_symbol({
|
||||
else
|
||||
for k,v in pairs(data) do
|
||||
if (v and v ~= userdata and v == '1') then
|
||||
rspamd_logger.infox(rspamd_config, "found ip in keep_spam map, setting pre-result")
|
||||
rspamd_logger.infox(rspamd_config, "found ip %s (checked as: %s) in keep_spam map, setting pre-result accept", from_ip_string, ip_check_table[k])
|
||||
task:set_pre_result('accept', 'ip matched with forward hosts', 'keep_spam')
|
||||
task:set_flag('no_stat')
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
table.insert(ip_check_table, 1, 'KEEP_SPAM')
|
||||
local redis_ret_user = rspamd_redis_make_request(task,
|
||||
redis_params, -- connect params
|
||||
@@ -210,6 +384,7 @@ rspamd_config:register_symbol({
|
||||
rspamd_config:register_symbol({
|
||||
name = 'TAG_MOO',
|
||||
type = 'postfilter',
|
||||
flags = 'ignore_passthrough',
|
||||
callback = function(task)
|
||||
local util = require("rspamd_util")
|
||||
local rspamd_logger = require "rspamd_logger"
|
||||
@@ -218,9 +393,6 @@ rspamd_config:register_symbol({
|
||||
local rcpts = task:get_recipients('smtp')
|
||||
local lua_util = require "lua_util"
|
||||
|
||||
local tagged_rcpt = task:get_symbol("TAGGED_RCPT")
|
||||
local mailcow_domain = task:get_symbol("RCPT_MAILCOW_DOMAIN")
|
||||
|
||||
local function remove_moo_tag()
|
||||
local moo_tag_header = task:get_header('X-Moo-Tag', false)
|
||||
if moo_tag_header then
|
||||
@@ -231,101 +403,149 @@ rspamd_config:register_symbol({
|
||||
return true
|
||||
end
|
||||
|
||||
if tagged_rcpt and tagged_rcpt[1].options and mailcow_domain then
|
||||
local tag = tagged_rcpt[1].options[1]
|
||||
rspamd_logger.infox("found tag: %s", tag)
|
||||
local action = task:get_metric_action('default')
|
||||
rspamd_logger.infox("metric action now: %s", action)
|
||||
-- Check if we have exactly one recipient
|
||||
if not (rcpts and #rcpts == 1) then
|
||||
rspamd_logger.infox("TAG_MOO: not exactly one rcpt (%s), removing moo tag", rcpts and #rcpts or 0)
|
||||
remove_moo_tag()
|
||||
return
|
||||
end
|
||||
|
||||
if action ~= 'no action' and action ~= 'greylist' then
|
||||
rspamd_logger.infox("skipping tag handler for action: %s", action)
|
||||
remove_moo_tag()
|
||||
return true
|
||||
local rcpt_addr = rcpts[1]['addr']
|
||||
local rcpt_user = rcpts[1]['user']
|
||||
local rcpt_domain = rcpts[1]['domain']
|
||||
|
||||
-- Check if recipient has a tag (contains '+')
|
||||
local tag = nil
|
||||
if rcpt_user:find('%+') then
|
||||
local base_user, tag_part = rcpt_user:match('^(.-)%+(.+)$')
|
||||
if base_user and tag_part then
|
||||
tag = tag_part
|
||||
rspamd_logger.infox("TAG_MOO: found tag in recipient: %s (base: %s, tag: %s)", rcpt_addr, base_user, tag)
|
||||
end
|
||||
end
|
||||
|
||||
local function http_callback(err_message, code, body, headers)
|
||||
if body ~= nil and body ~= "" then
|
||||
rspamd_logger.infox(rspamd_config, "expanding rcpt to \"%s\"", body)
|
||||
if not tag then
|
||||
rspamd_logger.infox("TAG_MOO: no tag found in recipient %s, removing moo tag", rcpt_addr)
|
||||
remove_moo_tag()
|
||||
return
|
||||
end
|
||||
|
||||
local function tag_callback_subject(err, data)
|
||||
if err or type(data) ~= 'string' then
|
||||
rspamd_logger.infox(rspamd_config, "subject tag handler rcpt %s returned invalid or empty data (\"%s\") or error (\"%s\") - trying subfolder tag handler...", body, data, err)
|
||||
-- Optional: Check if domain is a mailcow domain
|
||||
-- When KEEP_SPAM is active, RCPT_MAILCOW_DOMAIN might not be set
|
||||
-- If the mail is being delivered, we can assume it's valid
|
||||
local mailcow_domain = task:get_symbol("RCPT_MAILCOW_DOMAIN")
|
||||
if not mailcow_domain then
|
||||
rspamd_logger.infox("TAG_MOO: RCPT_MAILCOW_DOMAIN not set (possibly due to pre-result), proceeding anyway for domain %s", rcpt_domain)
|
||||
end
|
||||
|
||||
local function tag_callback_subfolder(err, data)
|
||||
if err or type(data) ~= 'string' then
|
||||
rspamd_logger.infox(rspamd_config, "subfolder tag handler for rcpt %s returned invalid or empty data (\"%s\") or error (\"%s\")", body, data, err)
|
||||
remove_moo_tag()
|
||||
else
|
||||
rspamd_logger.infox("Add X-Moo-Tag header")
|
||||
task:set_milter_reply({
|
||||
add_headers = {['X-Moo-Tag'] = 'YES'}
|
||||
})
|
||||
end
|
||||
end
|
||||
local action = task:get_metric_action('default')
|
||||
rspamd_logger.infox("TAG_MOO: metric action: %s", action)
|
||||
|
||||
local redis_ret_subfolder = rspamd_redis_make_request(task,
|
||||
redis_params, -- connect params
|
||||
body, -- hash key
|
||||
false, -- is write
|
||||
tag_callback_subfolder, --callback
|
||||
'HGET', -- command
|
||||
{'RCPT_WANTS_SUBFOLDER_TAG', body} -- arguments
|
||||
)
|
||||
if not redis_ret_subfolder then
|
||||
rspamd_logger.infox(rspamd_config, "cannot make request to load tag handler for rcpt")
|
||||
-- Check if we have a pre-result (e.g., from KEEP_SPAM or POSTMASTER_HANDLER)
|
||||
local allow_processing = false
|
||||
|
||||
if task.has_pre_result then
|
||||
local has_pre, pre_action = task:has_pre_result()
|
||||
if has_pre then
|
||||
rspamd_logger.infox("TAG_MOO: pre-result detected: %s", tostring(pre_action))
|
||||
if pre_action == 'accept' then
|
||||
allow_processing = true
|
||||
rspamd_logger.infox("TAG_MOO: pre-result is accept, will process")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Allow processing for mild actions or when we have pre-result accept
|
||||
if not allow_processing and action ~= 'no action' and action ~= 'greylist' then
|
||||
rspamd_logger.infox("TAG_MOO: skipping tag handler for action: %s", action)
|
||||
remove_moo_tag()
|
||||
return true
|
||||
end
|
||||
|
||||
rspamd_logger.infox("TAG_MOO: processing allowed")
|
||||
|
||||
local function http_callback(err_message, code, body, headers)
|
||||
if body ~= nil and body ~= "" then
|
||||
rspamd_logger.infox(rspamd_config, "TAG_MOO: expanding rcpt to \"%s\"", body)
|
||||
|
||||
local function tag_callback_subject(err, data)
|
||||
if err or type(data) ~= 'string' or data == '' then
|
||||
rspamd_logger.infox(rspamd_config, "TAG_MOO: subject tag handler rcpt %s returned invalid or empty data (\"%s\") or error (\"%s\") - trying subfolder tag handler...", body, data, err)
|
||||
|
||||
local function tag_callback_subfolder(err, data)
|
||||
if err or type(data) ~= 'string' or data == '' then
|
||||
rspamd_logger.infox(rspamd_config, "TAG_MOO: subfolder tag handler for rcpt %s returned invalid or empty data (\"%s\") or error (\"%s\")", body, data, err)
|
||||
remove_moo_tag()
|
||||
else
|
||||
rspamd_logger.infox("TAG_MOO: User wants subfolder tag, adding X-Moo-Tag header")
|
||||
task:set_milter_reply({
|
||||
add_headers = {['X-Moo-Tag'] = 'YES'}
|
||||
})
|
||||
end
|
||||
|
||||
else
|
||||
rspamd_logger.infox("user wants subject modified for tagged mail")
|
||||
local sbj = task:get_header('Subject')
|
||||
new_sbj = '=?UTF-8?B?' .. tostring(util.encode_base64('[' .. tag .. '] ' .. sbj)) .. '?='
|
||||
task:set_milter_reply({
|
||||
remove_headers = {
|
||||
['Subject'] = 1,
|
||||
['X-Moo-Tag'] = 0
|
||||
},
|
||||
add_headers = {['Subject'] = new_sbj}
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
local redis_ret_subject = rspamd_redis_make_request(task,
|
||||
redis_params, -- connect params
|
||||
body, -- hash key
|
||||
false, -- is write
|
||||
tag_callback_subject, --callback
|
||||
'HGET', -- command
|
||||
{'RCPT_WANTS_SUBJECT_TAG', body} -- arguments
|
||||
)
|
||||
if not redis_ret_subject then
|
||||
rspamd_logger.infox(rspamd_config, "cannot make request to load tag handler for rcpt")
|
||||
remove_moo_tag()
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
if rcpts and #rcpts == 1 then
|
||||
for _,rcpt in ipairs(rcpts) do
|
||||
local rcpt_split = rspamd_str_split(rcpt['addr'], '@')
|
||||
if #rcpt_split == 2 then
|
||||
if rcpt_split[1] == 'postmaster' then
|
||||
rspamd_logger.infox(rspamd_config, "not expanding postmaster alias")
|
||||
local redis_ret_subfolder = rspamd_redis_make_request(task,
|
||||
redis_params, -- connect params
|
||||
body, -- hash key
|
||||
false, -- is write
|
||||
tag_callback_subfolder, --callback
|
||||
'HGET', -- command
|
||||
{'RCPT_WANTS_SUBFOLDER_TAG', body} -- arguments
|
||||
)
|
||||
if not redis_ret_subfolder then
|
||||
rspamd_logger.infox(rspamd_config, "TAG_MOO: cannot make request to load tag handler for rcpt")
|
||||
remove_moo_tag()
|
||||
else
|
||||
rspamd_http.request({
|
||||
task=task,
|
||||
url='http://nginx:8081/aliasexp.php',
|
||||
body='',
|
||||
callback=http_callback,
|
||||
headers={Rcpt=rcpt['addr']},
|
||||
})
|
||||
end
|
||||
|
||||
else
|
||||
rspamd_logger.infox("TAG_MOO: user wants subject modified for tagged mail")
|
||||
local sbj = task:get_header('Subject') or ''
|
||||
new_sbj = '=?UTF-8?B?' .. tostring(util.encode_base64('[' .. tag .. '] ' .. sbj)) .. '?='
|
||||
task:set_milter_reply({
|
||||
remove_headers = {
|
||||
['Subject'] = 1,
|
||||
['X-Moo-Tag'] = 0
|
||||
},
|
||||
add_headers = {['Subject'] = new_sbj}
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
local redis_ret_subject = rspamd_redis_make_request(task,
|
||||
redis_params, -- connect params
|
||||
body, -- hash key
|
||||
false, -- is write
|
||||
tag_callback_subject, --callback
|
||||
'HGET', -- command
|
||||
{'RCPT_WANTS_SUBJECT_TAG', body} -- arguments
|
||||
)
|
||||
if not redis_ret_subject then
|
||||
rspamd_logger.infox(rspamd_config, "TAG_MOO: cannot make request to load tag handler for rcpt")
|
||||
remove_moo_tag()
|
||||
end
|
||||
else
|
||||
rspamd_logger.infox("TAG_MOO: alias expansion returned empty body")
|
||||
remove_moo_tag()
|
||||
end
|
||||
end
|
||||
|
||||
local rcpt_split = rspamd_str_split(rcpt_addr, '@')
|
||||
if #rcpt_split == 2 then
|
||||
if rcpt_split[1]:match('^postmaster') then
|
||||
rspamd_logger.infox(rspamd_config, "TAG_MOO: not expanding postmaster alias")
|
||||
remove_moo_tag()
|
||||
else
|
||||
rspamd_logger.infox("TAG_MOO: requesting alias expansion for %s", rcpt_addr)
|
||||
rspamd_http.request({
|
||||
task=task,
|
||||
url='http://nginx:8081/aliasexp.php',
|
||||
body='',
|
||||
callback=http_callback,
|
||||
headers={Rcpt=rcpt_addr},
|
||||
})
|
||||
end
|
||||
else
|
||||
rspamd_logger.infox("TAG_MOO: invalid rcpt format")
|
||||
remove_moo_tag()
|
||||
end
|
||||
end,
|
||||
@@ -335,6 +555,7 @@ rspamd_config:register_symbol({
|
||||
rspamd_config:register_symbol({
|
||||
name = 'BCC',
|
||||
type = 'postfilter',
|
||||
flags = 'ignore_passthrough',
|
||||
callback = function(task)
|
||||
local util = require("rspamd_util")
|
||||
local rspamd_http = require "rspamd_http"
|
||||
@@ -363,11 +584,13 @@ rspamd_config:register_symbol({
|
||||
local email_content = tostring(task:get_content())
|
||||
email_content = string.gsub(email_content, "\r\n%.", "\r\n..")
|
||||
-- send mail
|
||||
local from_smtp = task:get_from('smtp')
|
||||
local from_addr = (from_smtp and from_smtp[1] and from_smtp[1].addr) or 'mailer-daemon@localhost'
|
||||
lua_smtp.sendmail({
|
||||
task = task,
|
||||
host = os.getenv("IPV4_NETWORK") .. '.253',
|
||||
port = 591,
|
||||
from = task:get_from(stp)[1].addr,
|
||||
from = from_addr,
|
||||
recipients = bcc_dest,
|
||||
helo = 'bcc',
|
||||
timeout = 20,
|
||||
@@ -397,27 +620,41 @@ rspamd_config:register_symbol({
|
||||
end
|
||||
|
||||
local action = task:get_metric_action('default')
|
||||
rspamd_logger.infox("metric action now: %s", action)
|
||||
rspamd_logger.infox("BCC: metric action: %s", action)
|
||||
|
||||
-- Check for pre-result accept (e.g., from KEEP_SPAM)
|
||||
local allow_bcc = false
|
||||
if task.has_pre_result then
|
||||
local has_pre, pre_action = task:has_pre_result()
|
||||
if has_pre and pre_action == 'accept' then
|
||||
allow_bcc = true
|
||||
rspamd_logger.infox("BCC: pre-result accept detected, will send BCC")
|
||||
end
|
||||
end
|
||||
|
||||
-- Allow BCC for mild actions or when we have pre-result accept
|
||||
if not allow_bcc and action ~= 'no action' and action ~= 'add header' and action ~= 'rewrite subject' then
|
||||
rspamd_logger.infox("BCC: skipping for action: %s", action)
|
||||
return
|
||||
end
|
||||
|
||||
local function rcpt_callback(err_message, code, body, headers)
|
||||
if err_message == nil and code == 201 and body ~= nil then
|
||||
if action == 'no action' or action == 'add header' or action == 'rewrite subject' then
|
||||
send_mail(task, body)
|
||||
end
|
||||
rspamd_logger.infox("BCC: sending BCC to %s for rcpt match", body)
|
||||
send_mail(task, body)
|
||||
end
|
||||
end
|
||||
|
||||
local function from_callback(err_message, code, body, headers)
|
||||
if err_message == nil and code == 201 and body ~= nil then
|
||||
if action == 'no action' or action == 'add header' or action == 'rewrite subject' then
|
||||
send_mail(task, body)
|
||||
end
|
||||
rspamd_logger.infox("BCC: sending BCC to %s for from match", body)
|
||||
send_mail(task, body)
|
||||
end
|
||||
end
|
||||
|
||||
if rcpt_table then
|
||||
for _,e in ipairs(rcpt_table) do
|
||||
rspamd_logger.infox(rspamd_config, "checking bcc for rcpt address %s", e)
|
||||
rspamd_logger.infox(rspamd_config, "BCC: checking bcc for rcpt address %s", e)
|
||||
rspamd_http.request({
|
||||
task=task,
|
||||
url='http://nginx:8081/bcc.php',
|
||||
@@ -430,7 +667,7 @@ rspamd_config:register_symbol({
|
||||
|
||||
if from_table then
|
||||
for _,e in ipairs(from_table) do
|
||||
rspamd_logger.infox(rspamd_config, "checking bcc for from address %s", e)
|
||||
rspamd_logger.infox(rspamd_config, "BCC: checking bcc for from address %s", e)
|
||||
rspamd_http.request({
|
||||
task=task,
|
||||
url='http://nginx:8081/bcc.php',
|
||||
@@ -441,7 +678,7 @@ rspamd_config:register_symbol({
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
-- Don't return true to avoid symbol being logged
|
||||
end,
|
||||
priority = 20
|
||||
})
|
||||
@@ -708,4 +945,4 @@ rspamd_config:register_symbol({
|
||||
return true
|
||||
end,
|
||||
priority = 1
|
||||
})
|
||||
})
|
||||
@@ -86,6 +86,12 @@
|
||||
SOGoMaximumFailedLoginInterval = 900;
|
||||
SOGoFailedLoginBlockInterval = 900;
|
||||
|
||||
// Enable SOGo URL Description for GDPR compliance, this may cause some issues with calendars and contacts. Also uncomment the encryption key below to use it.
|
||||
//SOGoURLEncryptionEnabled = NO;
|
||||
|
||||
// Set a 16 character encryption key for SOGo URL Description, change this to your own value
|
||||
//SOGoURLPathEncryptionKey = "SOGoSuperSecret0";
|
||||
|
||||
GCSChannelCollectionTimer = 60;
|
||||
GCSChannelExpireAge = 60;
|
||||
|
||||
|
||||
@@ -129,7 +129,16 @@ if (isset($_SESSION['mailcow_cc_role']) && ($_SESSION['mailcow_cc_role'] == "adm
|
||||
);
|
||||
}
|
||||
|
||||
$mta_sts = mailbox('get', 'mta_sts', $domain);
|
||||
// Check if domain is an alias domain and get target domain's MTA-STS
|
||||
$alias_domain_details = mailbox('get', 'alias_domain_details', $domain);
|
||||
$mta_sts_domain = $domain;
|
||||
|
||||
if ($alias_domain_details !== false && !empty($alias_domain_details['target_domain'])) {
|
||||
// This is an alias domain, check target domain for MTA-STS
|
||||
$mta_sts_domain = $alias_domain_details['target_domain'];
|
||||
}
|
||||
|
||||
$mta_sts = mailbox('get', 'mta_sts', $mta_sts_domain);
|
||||
if (count($mta_sts) > 0 && $mta_sts['active'] == 1) {
|
||||
if (!in_array($domain, $alias_domains)) {
|
||||
$records[] = array(
|
||||
|
||||
@@ -824,6 +824,32 @@ function verify_hash($hash, $password) {
|
||||
$hash = $components[4];
|
||||
return hash_equals(hash_pbkdf2('sha1', $password, $salt, $rounds), $hash);
|
||||
|
||||
case "PBKDF2-SHA512":
|
||||
// Handle FreeIPA-style hash: {PBKDF2-SHA512}10000$<base64_salt>$<base64_hash>
|
||||
$components = explode('$', $hash);
|
||||
if (count($components) !== 3) return false;
|
||||
|
||||
// 1st part: iteration count (integer)
|
||||
$iterations = intval($components[0]);
|
||||
if ($iterations <= 0) return false;
|
||||
|
||||
// 2nd part: salt (base64-encoded)
|
||||
$salt = $components[1];
|
||||
// 3rd part: hash (base64-encoded)
|
||||
$stored_hash_b64 = $components[2];
|
||||
|
||||
// Decode salt and hash from base64
|
||||
$salt_bin = base64_decode($salt, true);
|
||||
$hash_bin = base64_decode($stored_hash_b64, true);
|
||||
if ($salt_bin === false || $hash_bin === false) return false;
|
||||
// Get length of hash in bytes
|
||||
$hash_len = strlen($hash_bin);
|
||||
if ($hash_len === 0) return false;
|
||||
|
||||
// Calculate PBKDF2-SHA512 hash for provided password
|
||||
$test_hash = hash_pbkdf2('sha512', $password, $salt_bin, $iterations, $hash_len, true);
|
||||
return hash_equals($hash_bin, $test_hash);
|
||||
|
||||
case "PLAIN-MD4":
|
||||
return hash_equals(hash('md4', $password), $hash);
|
||||
|
||||
|
||||
@@ -49,6 +49,12 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
// Default to 1 yr
|
||||
$_data["validity"] = 8760;
|
||||
}
|
||||
if (isset($_data["permanent"]) && filter_var($_data["permanent"], FILTER_VALIDATE_BOOL)) {
|
||||
$permanent = 1;
|
||||
}
|
||||
else {
|
||||
$permanent = 0;
|
||||
}
|
||||
$domain = $_data['domain'];
|
||||
$description = $_data['description'];
|
||||
$valid_domains[] = mailbox('get', 'mailbox_details', $username)['domain'];
|
||||
@@ -65,13 +71,14 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
return false;
|
||||
}
|
||||
$validity = strtotime("+" . $_data["validity"] . " hour");
|
||||
$stmt = $pdo->prepare("INSERT INTO `spamalias` (`address`, `description`, `goto`, `validity`) VALUES
|
||||
(:address, :description, :goto, :validity)");
|
||||
$stmt = $pdo->prepare("INSERT INTO `spamalias` (`address`, `description`, `goto`, `validity`, `permanent`) VALUES
|
||||
(:address, :description, :goto, :validity, :permanent)");
|
||||
$stmt->execute(array(
|
||||
':address' => readable_random_string(rand(rand(3, 9), rand(3, 9))) . '.' . readable_random_string(rand(rand(3, 9), rand(3, 9))) . '@' . $domain,
|
||||
':description' => $description,
|
||||
':goto' => $username,
|
||||
':validity' => $validity
|
||||
':validity' => $validity,
|
||||
':permanent' => $permanent
|
||||
));
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'success',
|
||||
@@ -2104,15 +2111,23 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if (empty($_data['validity'])) {
|
||||
if (empty($_data['validity']) && empty($_data['permanent'])) {
|
||||
continue;
|
||||
}
|
||||
$validity = round((int)time() + ($_data['validity'] * 3600));
|
||||
$stmt = $pdo->prepare("UPDATE `spamalias` SET `validity` = :validity WHERE
|
||||
if (isset($_data['permanent']) && filter_var($_data['permanent'], FILTER_VALIDATE_BOOL)) {
|
||||
$permanent = 1;
|
||||
$validity = 0;
|
||||
}
|
||||
else if (isset($_data['validity'])) {
|
||||
$permanent = 0;
|
||||
$validity = round((int)time() + ($_data['validity'] * 3600));
|
||||
}
|
||||
$stmt = $pdo->prepare("UPDATE `spamalias` SET `validity` = :validity, `permanent` = :permanent WHERE
|
||||
`address` = :address");
|
||||
$stmt->execute(array(
|
||||
':address' => $address,
|
||||
':validity' => $validity
|
||||
':validity' => $validity,
|
||||
':permanent' => $permanent
|
||||
));
|
||||
$_SESSION['return'][] = array(
|
||||
'type' => 'success',
|
||||
@@ -4585,10 +4600,12 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
`description`,
|
||||
`validity`,
|
||||
`created`,
|
||||
`modified`
|
||||
`modified`,
|
||||
`permanent`
|
||||
FROM `spamalias`
|
||||
WHERE `goto` = :username
|
||||
AND `validity` >= :unixnow");
|
||||
AND (`validity` >= :unixnow
|
||||
OR `permanent` != 0)");
|
||||
$stmt->execute(array(':username' => $_data, ':unixnow' => time()));
|
||||
$tladata = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
||||
return $tladata;
|
||||
@@ -5163,7 +5180,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
|
||||
$stmt = $pdo->prepare("SELECT COALESCE(SUM(`quota`), 0) as `in_use` FROM `mailbox` WHERE (`kind` = '' OR `kind` = NULL) AND `domain` = :domain AND `username` != :username");
|
||||
$stmt->execute(array(':domain' => $row['domain'], ':username' => $_data));
|
||||
$MailboxUsage = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
$stmt = $pdo->prepare("SELECT IFNULL(COUNT(`address`), 0) AS `sa_count` FROM `spamalias` WHERE `goto` = :address AND `validity` >= :unixnow");
|
||||
$stmt = $pdo->prepare("SELECT IFNULL(COUNT(`address`), 0) AS `sa_count` FROM `spamalias` WHERE `goto` = :address AND (`validity` >= :unixnow OR `permanent` != 0)");
|
||||
$stmt->execute(array(':address' => $_data, ':unixnow' => time()));
|
||||
$SpamaliasUsage = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
$mailboxdata['max_new_quota'] = ($DomainQuota['quota'] * 1048576) - $MailboxUsage['in_use'];
|
||||
|
||||
@@ -4,7 +4,7 @@ function init_db_schema()
|
||||
try {
|
||||
global $pdo;
|
||||
|
||||
$db_version = "15102025_1115";
|
||||
$db_version = "10312025_0525";
|
||||
|
||||
$stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
|
||||
$num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
|
||||
@@ -554,7 +554,8 @@ function init_db_schema()
|
||||
"description" => "TEXT NOT NULL",
|
||||
"created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
|
||||
"modified" => "DATETIME ON UPDATE CURRENT_TIMESTAMP",
|
||||
"validity" => "INT(11)"
|
||||
"validity" => "INT(11)",
|
||||
"permanent" => "TINYINT(1) NOT NULL DEFAULT '0'"
|
||||
),
|
||||
"keys" => array(
|
||||
"primary" => array(
|
||||
|
||||
@@ -85,7 +85,7 @@ $AVAILABLE_LANGUAGES = array(
|
||||
// 'ca-es' => 'Català (Catalan)',
|
||||
'bg-bg' => 'Български (Bulgarian)',
|
||||
'cs-cz' => 'Čeština (Czech)',
|
||||
'da-dk' => 'Danish (Dansk)',
|
||||
'da-dk' => 'Dansk (Danish)',
|
||||
'de-de' => 'Deutsch (German)',
|
||||
'en-gb' => 'English',
|
||||
'es-es' => 'Español (Spanish)',
|
||||
@@ -110,6 +110,7 @@ $AVAILABLE_LANGUAGES = array(
|
||||
'sv-se' => 'Svenska (Swedish)',
|
||||
'tr-tr' => 'Türkçe (Turkish)',
|
||||
'uk-ua' => 'Українська (Ukrainian)',
|
||||
'vi-vn' => 'Tiếng Việt (Vietnamese)',
|
||||
'zh-cn' => '简体中文 (Simplified Chinese)',
|
||||
'zh-tw' => '繁體中文 (Traditional Chinese)',
|
||||
);
|
||||
|
||||
@@ -54,7 +54,16 @@ jQuery(function($){
|
||||
$.get("/inc/ajax/show_rspamd_global_filters.php");
|
||||
$("#confirm_show_rspamd_global_filters").hide();
|
||||
$("#rspamd_global_filters").removeClass("d-none");
|
||||
localStorage.setItem('rspamd_global_filters_confirmed', 'true');
|
||||
});
|
||||
|
||||
$(document).ready(function() {
|
||||
if (localStorage.getItem('rspamd_global_filters_confirmed') === 'true') {
|
||||
$("#confirm_show_rspamd_global_filters").hide();
|
||||
$("#rspamd_global_filters").removeClass("d-none");
|
||||
}
|
||||
});
|
||||
|
||||
$("#super_delete").click(function() { return confirm(lang.queue_ays); });
|
||||
|
||||
$(".refresh_table").on('click', function(e) {
|
||||
|
||||
@@ -175,6 +175,10 @@ jQuery(function($){
|
||||
'</div>';
|
||||
item.chkbox = '<input type="checkbox" class="form-check-input" data-id="tla" name="multi_select" value="' + encodeURIComponent(item.address) + '" />';
|
||||
item.address = escapeHtml(item.address);
|
||||
item.validity = {
|
||||
value: item.validity,
|
||||
permanent: item.permanent
|
||||
};
|
||||
}
|
||||
else {
|
||||
item.chkbox = '<input type="checkbox" class="form-check-input" disabled />';
|
||||
@@ -218,9 +222,21 @@ jQuery(function($){
|
||||
title: lang.alias_valid_until,
|
||||
data: 'validity',
|
||||
defaultContent: '',
|
||||
createdCell: function(td, cellData) {
|
||||
createSortableDate(td, cellData)
|
||||
}
|
||||
render: function (data, type) {
|
||||
var date = new Date(data.value ? data.value * 1000 : 0);
|
||||
switch (type) {
|
||||
case "sort":
|
||||
if (data.permanent) {
|
||||
return 0;
|
||||
}
|
||||
return date.getTime();
|
||||
default:
|
||||
if (data.permanent) {
|
||||
return lang.forever;
|
||||
}
|
||||
return date.toLocaleDateString(LOCALE, DATETIME_FORMAT);
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
title: lang.created_on,
|
||||
|
||||
@@ -1992,6 +1992,7 @@ if (isset($_GET['query'])) {
|
||||
break;
|
||||
case "cors":
|
||||
process_edit_return(cors('edit', $attr));
|
||||
break;
|
||||
case "identity-provider":
|
||||
process_edit_return(identity_provider('edit', $attr));
|
||||
break;
|
||||
|
||||
@@ -19,7 +19,16 @@
|
||||
"spam_alias": "Àlies temporals",
|
||||
"spam_score": "Puntuació de correu brossa",
|
||||
"tls_policy": "Política TLS",
|
||||
"unlimited_quota": "Quota ilimitada per bústies de correo"
|
||||
"unlimited_quota": "Quota ilimitada per bústies de correo",
|
||||
"delimiter_action": "Acció delimitadora",
|
||||
"domain_relayhost": "Canviar relayhost per un domini",
|
||||
"extend_sender_acl": "Permetre extendre l'ACL del remitent per adreces externes",
|
||||
"mailbox_relayhost": "Canvia el host de reenviament per una bústia",
|
||||
"pushover": "Pushover",
|
||||
"pw_reset": "Permetre el restabliment de la contrasenya de l'usuari mailcow",
|
||||
"ratelimit": "Límit de peticions",
|
||||
"smtp_ip_access": "Canvia hosts permesos per SMTP",
|
||||
"sogo_access": "Permetre la gestió d'accés a SOGo"
|
||||
},
|
||||
"add": {
|
||||
"activate_filter_warn": "All other filters will be deactivated, when active is checked.",
|
||||
@@ -73,7 +82,25 @@
|
||||
"validate": "Validar",
|
||||
"validation_success": "Validated successfully",
|
||||
"app_name": "Nom de l'aplicació",
|
||||
"app_password": "Afegir contrasenya a l'aplicació"
|
||||
"app_password": "Afegir contrasenya a l'aplicació",
|
||||
"app_passwd_protocols": "Protocols autoritzats per la contrasenya de l'aplicació",
|
||||
"bcc_dest_format": "La destinació c/o ha de ser una única adreça de correu vàlida.<br>Si necessiteu enviar una còpia a diverses adreces, creeu un àlies i utilitzeu-lo aquí.",
|
||||
"comment_info": "Els comentaris privats no són visibles per l'usuari, mentre que els comentaris públics apareixen com una descripció emergent a la informació de l'usuari",
|
||||
"custom_params": "Paràmetres personalitzats",
|
||||
"custom_params_hint": "Correcte: --param=xy, incorrecte: --param xy",
|
||||
"destination": "Destí",
|
||||
"disable_login": "No permetre l'inici de sessió (els missatges entrants continuen sent acceptats)",
|
||||
"domain_matches_hostname": "El domini %s coincideix amb el nom del servidor",
|
||||
"dry": "Simular la sincronització",
|
||||
"gal": "Llista d'adreces global",
|
||||
"generate": "genereu",
|
||||
"inactive": "Inactiu",
|
||||
"internal": "Intern",
|
||||
"internal_info": "Els àlies interns són només accessibles des del mateix domini o els àlies de dominis.",
|
||||
"mailbox_quota_def": "Quota per defecte de la bústia",
|
||||
"nexthop": "Següent salt",
|
||||
"private_comment": "Comentari privat",
|
||||
"public_comment": "Comentari púlbic"
|
||||
},
|
||||
"admin": {
|
||||
"access": "Accés",
|
||||
|
||||
@@ -149,7 +149,7 @@
|
||||
"arrival_time": "Čas zařazení do fronty (čas na serveru)",
|
||||
"authed_user": "Přihlášený uživatel",
|
||||
"ays": "Opravdu chcete pokračovat?",
|
||||
"ban_list_info": "Seznam blokovaných IP adres je zobrazen níže: <b>síť (zbývající čas blokování) - [akce]</b>.<br />IP adresy zařazené pro odblokování budou z aktivního seznamu odebrány během několika sekund.<br />Červeně označené položky jsou pernamentní bloky z blacklistu.",
|
||||
"ban_list_info": "Viz seznam zablokovaných IP níže: <b>síť (zbývající doba zablokování) - [akce]</b>.<br />IP adresy zařazené pro odblokování budou z aktivního seznamu odebrány během pár sekund.<br />Červeně označeny jsou položky z trvalých seznamů.",
|
||||
"change_logo": "Změnit logo",
|
||||
"logo_normal_label": "Normální",
|
||||
"logo_dark_label": "Inverzní pro tmavý režim",
|
||||
@@ -183,16 +183,16 @@
|
||||
"empty": "Žádné výsledky",
|
||||
"excludes": "Vyloučit tyto příjemce",
|
||||
"f2b_ban_time": "Doba blokování (s)",
|
||||
"f2b_blacklist": "Sítě/hostitelé na blacklistu",
|
||||
"f2b_blacklist": "Sítě či hostitelé na seznamu zákazů",
|
||||
"f2b_filter": "Regex filtre",
|
||||
"f2b_list_info": "Síť nebo hostitelé na blacklistu mají vždy větší váhu než položky na whitelistu. <b>Každá úprava seznamů trvá pár sekund.</b>",
|
||||
"f2b_list_info": "Sítě či hostitelé na seznamu zákazů mají vždy větší váhu než položky na seznamu povolení. <b>Každá úprava seznamu trvá pár sekund.</b>",
|
||||
"f2b_max_attempts": "Max. pokusů",
|
||||
"f2b_netban_ipv4": "Rozsah IPv4 podsítě k zablokování (8-32)",
|
||||
"f2b_netban_ipv6": "Rozsah IPv6 podsítě k zablokování (8-128)",
|
||||
"f2b_parameters": "Parametry automatického firewallu",
|
||||
"f2b_regex_info": "Záznamy které se berou v úvahu: SOGo, Postfix, Dovecot, PHP-FPM.",
|
||||
"f2b_retry_window": "Časový horizont pro maximum pokusů (s)",
|
||||
"f2b_whitelist": "Sítě/hostitelé na whitelistu",
|
||||
"f2b_whitelist": "Sítě či hostitelé na seznamu povolení",
|
||||
"filter_table": "Tabulka filtrů",
|
||||
"forwarding_hosts": "Předávající servery",
|
||||
"forwarding_hosts_add_hint": "Lze zadat IPv4/IPv6 adresy, sítě ve formátu CIDR, názvy serverů (budou převedeny na IP adresy) nebo názvy domén (budou převedeny na IP pomocí SPF záznamů, příp. MX záznamů).",
|
||||
@@ -304,7 +304,7 @@
|
||||
"rspamd_com_settings": "Název nastavení se vygeneruje automaticky, viz ukázky nastavení níže. Více informací viz <a href=\"https://rspamd.com/doc/configuration/settings.html#settings-structure\" target=\"_blank\">Rspamd dokumentace</a>",
|
||||
"rspamd_global_filters": "Mapa globálních filtrů",
|
||||
"rspamd_global_filters_agree": "Budu opatrný!",
|
||||
"rspamd_global_filters_info": "Mapa globálních filtrů obsahuje jiné globální black- a whitelisty.",
|
||||
"rspamd_global_filters_info": "Mapa globálních filtrů obsahuje různé seznamy povolených a zakázaných serverů",
|
||||
"rspamd_global_filters_regex": "Názvy stačí k vysvětlení. Položky musejí obsahovat jen platné regulární výrazy ve tvaru \"/vyraz/parametry\" (e.g. <code>/.+@domena\\.tld/i</code>).<br>\n Každý výraz bude podroben základní kontrole, přesto je možné Rspamd 'rozbít', nebude-li syntax zcela korektní.<br>\n Rspamd se pokusí po každé změně načíst mapu znovu. V případě potíží <a href=\"\" data-toggle=\"modal\" data-container=\"rspamd-mailcow\" data-target=\"#RestartContainer\">restartujte Rspamd</a>, aby se konfigurace načetla explicitně.",
|
||||
"rspamd_settings_map": "Nastavení Rspamd",
|
||||
"sal_level": "Úroveň 'Moo'",
|
||||
@@ -733,7 +733,7 @@
|
||||
"sogo_visible_info": "Tato volba určuje objekty, jež lze zobrazit v SOGo (sdílené nebo nesdílené aliasy, jež ukazuje alespoň na jednu schránku).",
|
||||
"spam_alias": "Vytvořit nebo změnit dočasné aliasy",
|
||||
"spam_filter": "Spam filtr",
|
||||
"spam_policy": "Přidat nebo odebrat položky whitelistu/blacklistu",
|
||||
"spam_policy": "Přidat nebo odebrat položky seznamu",
|
||||
"spam_score": "Nastavte vlastní skóre spamu",
|
||||
"subfolder2": "Synchronizace do podsložky v cílovém umístění<br><small>(prázdné = nepoužívat podsložku)</small>",
|
||||
"syncjob": "Upravit synchronizační úlohu",
|
||||
@@ -883,7 +883,7 @@
|
||||
"bcc": "BCC",
|
||||
"bcc_destination": "Cíl kopie",
|
||||
"bcc_destinations": "Cíl kopií",
|
||||
"bcc_info": "<br/>Skrytá kopie (mapa BCC) se používá pro tiché předávání kopií všech zpráv na jinou adresu. Mapa příjemců se použije, funguje-li je místní cíl jako adresát zprávy. Totéž platí pro mapy odesílatelů.<br/>\n Místní cíl se nedozví, selže-li doručení na cíl BCC.",
|
||||
"bcc_info": "Skrytá kopie (mapa BCC) se používá pro tiché předávání kopií všech zpráv na jinou adresu. Mapa příjemců se použije, funguje-li je místní cíl jako adresát zprávy. Totéž platí pro mapy odesílatelů.<br/>\n Místní cíl se nedozví, selže-li doručení na cíl BCC.",
|
||||
"bcc_local_dest": "Týká se",
|
||||
"bcc_map": "Skrytá kopie",
|
||||
"bcc_map_type": "Typ skryté kopie",
|
||||
@@ -1060,7 +1060,7 @@
|
||||
"notified": "Oznámeno",
|
||||
"qhandler_success": "Požadavek úspěšně přijat. Můžete nyní zavřít okno.",
|
||||
"qid": "Rspamd QID",
|
||||
"qinfo": "Karanténní systém uloží odmítnutou poštu do databáze (odesílatel se <em>nedozví</em>, že pošta byla doručena) jakož i pošta, která bude jako kopie doručena do složky Nevyžádaná pošta. \r\n<br>\"Naučit jako spam a smazat\" naučí zprávu jako spam přes Bayesian theorem a současně vypočítá fuzzy hashes pro odmítnutí podobných zpráv v budoucnosti. \r\n<br> Prosím, berte na vědomí, že naučení více zpráv může být - záleží na vašem systému - časově náročné . <br> Položky na černé listině jsou z karantény vyloučeny.",
|
||||
"qinfo": "Karanténa uloží do databáze odmítnutou poštu (odesílatel se <em>nedozví</em>, že pošta byla doručena) jakož i poštu, jež se jako kopie doručuje do složky Nevyžádaná pošta.\n <br>\"Naučit jako spam a smazat\" předá zprávu systému k naučení bayesiánskou analýzou jako spam a současně stanoví fuzzy hashe pro odmítání podobných zpráv v budoucnosti.\n <br> Vezměte na vědomí, že učení více zpráv může být podle výkonnosti systému zabrat více času. <br> Položky na seznamu zákazů jsou z karantény vyloučeny.",
|
||||
"qitem": "Položka v karanténě",
|
||||
"quarantine": "Karanténa",
|
||||
"quick_actions": "Akce",
|
||||
@@ -1347,12 +1347,12 @@
|
||||
"sogo_profile_reset": "Resetovat profil SOGo",
|
||||
"sogo_profile_reset_help": "Tato volba odstraní uživatelský profil SOGo a <b>nenávratně vymaže všechna data</b>.",
|
||||
"sogo_profile_reset_now": "Resetovat profil",
|
||||
"spam_aliases": "Dočasné e-mailové aliasy",
|
||||
"spam_aliases": "Spam aliasy",
|
||||
"spam_score_reset": "Obnovit výchozí nastavení serveru",
|
||||
"spamfilter": "Filtr spamu",
|
||||
"spamfilter_behavior": "Hodnocení",
|
||||
"spamfilter_bl": "Seznam zakázaných adres (blacklist)",
|
||||
"spamfilter_bl_desc": "Zakázané emailové adresy budou <b>vždy</b> klasifikovány jako spam a odmítnuty. Odmítnutá pošta <b>nebude</b> uložena do karantény. Lze použít zástupné znaky (*). Filtr se použije pouze na přímé aliasy (s jednou cílovou poštovní schránkou), s výjimkou doménových košů a samotné poštovní schránky.",
|
||||
"spamfilter_bl": "Seznam zákazů",
|
||||
"spamfilter_bl_desc": "Zakázané emailové adresy budou <b>vždy</b> klasifikovány jako spam a odmítnuty. Odmítnutá pošta <b>se neukládá</b> do karantény. Lze použít zástupné znaky (*). Filtr se použije pouze na přímé aliasy (s jednou cílovou poštovní schránkou), s výjimkou doménových košů a samotné poštovní schránky.",
|
||||
"spamfilter_default_score": "Výchozí hodnoty",
|
||||
"spamfilter_green": "Zelená: tato zpráva není spam",
|
||||
"spamfilter_hint": "První hodnota představuje \"nízké spam skóre\" a druhá \"vysoké spam skóre\".",
|
||||
@@ -1363,7 +1363,7 @@
|
||||
"spamfilter_table_empty": "Žádná data k zobrazení",
|
||||
"spamfilter_table_remove": "smazat",
|
||||
"spamfilter_table_rule": "Pravidlo",
|
||||
"spamfilter_wl": "Seznam povolených adres (whitelist)",
|
||||
"spamfilter_wl": "Seznam povolení",
|
||||
"spamfilter_wl_desc": "Povolené emailové adresy <b>nebudou nikdy klasifikovány jako spam</b>. Lze použít zástupné znaky (*). Filtr se použije pouze na přímé aliasy (s jednou cílovou mailovou schránkou), s výjimkou doménových košů a samotné mailové schránky.",
|
||||
"spamfilter_yellow": "Žlutá: tato zpráva může být spam, bude označena jako spam a přesunuta do složky nevyžádané pošty",
|
||||
"status": "Stav",
|
||||
@@ -1407,7 +1407,10 @@
|
||||
"authentication": "Autentifikace",
|
||||
"overview": "Přehled",
|
||||
"protocols": "Protokoly",
|
||||
"value": "Hodnota"
|
||||
"value": "Hodnota",
|
||||
"expire_never": "Nikdy nevyprší",
|
||||
"forever": "Navždy",
|
||||
"spam_aliases_info": "Spam alias je dočasná adresa, již lze použít k ochraně skutečných adres. <br>Případně lze nastavit také dobu platnosti, po níž je alias automaticky deaktivován, čímž se řeší případy zneužitých či odcizených adres."
|
||||
},
|
||||
"warning": {
|
||||
"cannot_delete_self": "Nelze smazat právě přihlášeného uživatele",
|
||||
|
||||
@@ -987,7 +987,7 @@
|
||||
"sogo_visible": "Alias Sichtbarkeit in SOGo",
|
||||
"sogo_visible_n": "Alias in SOGo verbergen",
|
||||
"sogo_visible_y": "Alias in SOGo anzeigen",
|
||||
"spam_aliases": "Temp. Alias",
|
||||
"spam_aliases": "Spam-Alias",
|
||||
"stats": "Statistik",
|
||||
"status": "Status",
|
||||
"sync_jobs": "Synchronisationen",
|
||||
@@ -1281,7 +1281,9 @@
|
||||
"encryption": "Verschlüsselung",
|
||||
"excludes": "Ausschlüsse",
|
||||
"expire_in": "Ungültig in",
|
||||
"expire_never": "Niemals ungültig",
|
||||
"fido2_webauthn": "FIDO2/WebAuthn",
|
||||
"forever": "Für immer",
|
||||
"force_pw_update": "Das Passwort für diesen Benutzer <b>muss</b> geändert werden, damit die Zugriffssperre auf die Groupware-Komponenten wieder freigeschaltet wird.",
|
||||
"from": "von",
|
||||
"generate": "generieren",
|
||||
@@ -1346,7 +1348,8 @@
|
||||
"sogo_profile_reset": "SOGo-Profil zurücksetzen",
|
||||
"sogo_profile_reset_help": "Das Profil wird inklusive <b>aller</b> Kalender- und Kontaktdaten <b>unwiederbringlich gelöscht</b>.",
|
||||
"sogo_profile_reset_now": "Profil jetzt zurücksetzen",
|
||||
"spam_aliases": "Temporäre E-Mail-Aliasse",
|
||||
"spam_aliases": "Spam E-Mail-Aliasse",
|
||||
"spam_aliases_info": "Ein Spam-Alias ist eine temporäre E-Mailadresse, die benutzt werden kann, um eine echte E-Mail Adressen zu schützen. <br>Optional kann eine Ablaufzeit gesetzt werden, sodass der Alias nach dem definierten Zeitraum automatisch deaktiviert wird, was missbrauchte oder geleakte Adressen effektiv entsorgt.",
|
||||
"spam_score_reset": "Auf Server-Standard zurücksetzen",
|
||||
"spamfilter": "Spamfilter",
|
||||
"spamfilter_behavior": "Bewertung",
|
||||
|
||||
@@ -1288,7 +1288,9 @@
|
||||
"encryption": "Encryption",
|
||||
"excludes": "Excludes",
|
||||
"expire_in": "Expire in",
|
||||
"expire_never": "Never Expire",
|
||||
"fido2_webauthn": "FIDO2/WebAuthn",
|
||||
"forever": "Forever",
|
||||
"force_pw_update": "You <b>must</b> set a new password to be able to access groupware related services.",
|
||||
"from": "from",
|
||||
"generate": "generate",
|
||||
@@ -1355,7 +1357,8 @@
|
||||
"sogo_profile_reset": "Reset SOGo profile",
|
||||
"sogo_profile_reset_help": "This will destroy a user's SOGo profile and <b>delete all contact and calendar data irretrievable</b>.",
|
||||
"sogo_profile_reset_now": "Reset profile now",
|
||||
"spam_aliases": "Temporary email aliases",
|
||||
"spam_aliases": "Spam email aliases",
|
||||
"spam_aliases_info": "A spam alias is a temporary email address that can be used to protect real email addresses. <br>Optionally, an expiration time can be set so that the alias is automatically deactivated after the defined period, effectively disposing of abused or leaked addresses.",
|
||||
"spam_score_reset": "Reset to server default",
|
||||
"spamfilter": "Spam filter",
|
||||
"spamfilter_behavior": "Rating",
|
||||
|
||||
@@ -1084,6 +1084,7 @@
|
||||
"aliases_send_as_all": "No verificar permisos del remitente para los siguientes dominios (y sus aliases)",
|
||||
"change_password": "Cambiar contraseña",
|
||||
"create_syncjob": "Crear nuevo trabajo de sincronización",
|
||||
"created_on": "Creado",
|
||||
"daily": "Cada día",
|
||||
"day": "Día",
|
||||
"description": "Descripción",
|
||||
@@ -1095,6 +1096,9 @@
|
||||
"edit": "Editar",
|
||||
"encryption": "Cifrado",
|
||||
"excludes": "Excluye",
|
||||
"expire_in": "Expirará en",
|
||||
"expire_never": "Nunca expirará",
|
||||
"forever": "Siempre",
|
||||
"hour": "Hora",
|
||||
"hourly": "Cada hora",
|
||||
"hours": "Horas",
|
||||
@@ -1115,7 +1119,8 @@
|
||||
"shared_aliases": "Alias compartidos",
|
||||
"shared_aliases_desc": "Los alias compartidos no se ven afectados por la configuración específica del usuario, como el filtro de correo no deseado o la política de cifrado. Los filtros de spam correspondientes solo pueden ser realizados por un administrador como una política de dominio.",
|
||||
"sogo_profile_reset": "Resetear perfil SOGo",
|
||||
"spam_aliases": "Alias de email temporales",
|
||||
"spam_aliases": "Alias de email de spam",
|
||||
"spam_aliases_info": "Un alias de spam es una dirección de correo electrónico temporal que se puede usar para proteger direcciones de correo electrónico reales. <br>Opcionalmente, se puede establecer un tiempo de expiración para que el alias se desactive automáticamente después del período definido, eliminando efectivamente las direcciones abusadas o filtradas.",
|
||||
"spamfilter": "Filtro anti-spam",
|
||||
"spamfilter_behavior": "Clasificación",
|
||||
"spamfilter_bl": "Lista negra",
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
"quarantine_notification": "Modifier la notification de quarantaine",
|
||||
"quarantine_category": "Modifier la catégorie de la notification de quarantaine",
|
||||
"ratelimit": "Limite d'envoi",
|
||||
"recipient_maps": "Cartes destinataire",
|
||||
"recipient_maps": "Cartes des destinataires",
|
||||
"smtp_ip_access": "Changer les hôtes autorisés pour SMTP",
|
||||
"sogo_access": "Autoriser la gestion des accès à SOGo",
|
||||
"sogo_profile_reset": "Réinitialiser le profil SOGo",
|
||||
@@ -109,7 +109,9 @@
|
||||
"bcc_dest_format": "La destination Cci doit être une seule adresse de courriel valide.<br>Si vous avez besoin d'envoyer une copie à plusieurs adresses, créez un alias et utilisez-le ici.",
|
||||
"tags": "Etiquettes",
|
||||
"app_passwd_protocols": "Protocoles autorisés pour le mot de passe de l'application",
|
||||
"dry": "Simuler la synchronisation"
|
||||
"dry": "Simuler la synchronisation",
|
||||
"internal": "Interne",
|
||||
"internal_info": "Les alias internes sont accessibles uniquement depuis le domaine ou les alias du domaine."
|
||||
},
|
||||
"admin": {
|
||||
"access": "Accès",
|
||||
@@ -407,7 +409,9 @@
|
||||
"iam_host": "Hôte",
|
||||
"iam_host_info": "Saisissez un ou plusieurs hôtes LDAP, séparés par des virgules.",
|
||||
"iam_import_users": "Importer des utilisateurs",
|
||||
"filter": "Filtrer"
|
||||
"filter": "Filtrer",
|
||||
"needs_restart": "nécessite un redémarrage",
|
||||
"iam": "Fournisseur d'identité"
|
||||
},
|
||||
"danger": {
|
||||
"access_denied": "Accès refusé ou données de formulaire non valides",
|
||||
@@ -441,7 +445,7 @@
|
||||
"global_filter_write_error": "Impossible d’écrire le fichier de filtre : %s",
|
||||
"global_map_invalid": "ID de carte globale %s non valide",
|
||||
"global_map_write_error": "Impossible d’écrire l’ID de la carte globale %s : %s",
|
||||
"goto_empty": "Une adresse alias doit contenir au moins une adresse 'goto'valide",
|
||||
"goto_empty": "Une adresse alias doit contenir au moins une adresse 'goto' valide",
|
||||
"goto_invalid": "Adresse Goto %s non valide",
|
||||
"ham_learn_error": "Erreur d'apprentissage Ham : %s",
|
||||
"imagick_exception": "Erreur : Exception Imagick lors de la lecture de l’image",
|
||||
@@ -548,7 +552,11 @@
|
||||
"generic_server_error": "Une erreur de serveur inattendue s'est produite. Veuillez contacter votre administrateur.",
|
||||
"authsource_in_use": "Le fournisseur d'identité ne peut pas être modifié ou supprimé car il est actuellement utilisé par un ou plusieurs utilisateurs.",
|
||||
"iam_test_connection": "Échec de la connexion",
|
||||
"required_data_missing": "La donnée requise %s est manquante"
|
||||
"required_data_missing": "La donnée requise %s est manquante",
|
||||
"max_age_invalid": "L'âge maximum %s est invalide",
|
||||
"mode_invalid": "Le mode %s est invalide",
|
||||
"mx_invalid": "L'enregistrement MX %s est invalide",
|
||||
"version_invalid": "La version %s est invalide"
|
||||
},
|
||||
"debug": {
|
||||
"chart_this_server": "Graphique (ce serveur)",
|
||||
@@ -693,7 +701,7 @@
|
||||
"spam_score": "Définir un score spam personnalisé",
|
||||
"subfolder2": "Synchronisation dans le sous-dossier sur la destination<br><small>(vide = ne pas utiliser de sous-dossier)</small>",
|
||||
"syncjob": "Modifier la tâche de synchronisation",
|
||||
"target_address": "Adresse(s) Goto<small>(séparé(s) par des virgules)</small>",
|
||||
"target_address": "Adresse(s) Goto <small>(séparé(s) par des virgules)</small>",
|
||||
"target_domain": "Domaine cible",
|
||||
"timeout1": "Délai de connexion à l’hôte distant",
|
||||
"timeout2": "Délai de connexion à l’hôte local",
|
||||
@@ -734,7 +742,20 @@
|
||||
"mailbox_rename_alias": "Créer un alias automatiquement",
|
||||
"sogo_access": "Redirection directe vers SOGo",
|
||||
"pushover": "Pushover",
|
||||
"pushover_sound": "Son"
|
||||
"pushover_sound": "Son",
|
||||
"internal": "Interne",
|
||||
"internal_info": "Les alias internes sont accessibles uniquement depuis le domaine ou les alias du domaine.",
|
||||
"mta_sts": "MTA-STS",
|
||||
"mta_sts_version": "Version",
|
||||
"mta_sts_version_info": "Défini la version du standard MTA-STS – actuellement seul <code>STSv1</code> est valide.",
|
||||
"mta_sts_mode": "Mode",
|
||||
"mta_sts_max_age": "Âge maximum",
|
||||
"mta_sts_mx": "Serveur MX",
|
||||
"mta_sts_mx_notice": "Plusieurs serveurs MX peuvent être spécifiés (séparés par des virgules).",
|
||||
"mta_sts_info": "<a href='https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol#SMTP_MTA_Strict_Transport_Security' target='_blank'>MTA-STS</a> est un standard qui oblige la délivrance des courriels entre les serveurs de courriels à utiliser TLS avec des certificats valides. <br>Il est utilisé quand <a target='_blank' href='https://en.wikipedia.org/wiki/DNS-based_Authentication_of_Named_Entities'>DANE</a> n'est pas possible à cause d'un manque ou d'un non support de DNSSEC.<br><b>Note</b> : Si le domaine du destinataire supporte DANE avec DNSSEC, DANE est <b>toujours</b> préféré – MTA-STS sert seulement en secours.",
|
||||
"mta_sts_mode_info": "Il y a trois modes parmi lesquels choisir :<ul><li><em>testing</em> – la politique est seulement surveillée, les violations n'ont pas d'impact.</li><li><em>enforce</em> – la politique est appliquée strictement, les connexions sans TLS valide sont rejetées.</li><li><em>none</em> – la politique est publiée mais non appliquée.</li></ul>",
|
||||
"mta_sts_max_age_info": "Durée en secondes pendant laquelle les serveurs de courriel peuvent mettre en cache cette politique avant de revérifier.",
|
||||
"mta_sts_mx_info": "Autoriser l'envoi uniquement aux noms d'hôtes des serveurs de courriels indiqués explicitement ; le MTA émetteur vérifie si le nom d'hôte DNS du MX correspond à la liste de la politique, et autorise la délivrance seulement avec un certificat TLS valide (protège contre le MITM)."
|
||||
},
|
||||
"footer": {
|
||||
"cancel": "Annuler",
|
||||
@@ -789,7 +810,8 @@
|
||||
"login_linkstext": "L'identifiant n'est pas correct ?",
|
||||
"login_usertext": "Se connecter en tant qu'utilisateur",
|
||||
"login_domainadmintext": "Se connecter en tant qu'administrateur du domaine",
|
||||
"login_admintext": "Se connecter en tant qu'administrateur"
|
||||
"login_admintext": "Se connecter en tant qu'administrateur",
|
||||
"email": "Adresse de courriel"
|
||||
},
|
||||
"mailbox": {
|
||||
"action": "Action",
|
||||
@@ -896,7 +918,7 @@
|
||||
"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": "La destination originale des cartes des destinataires doit être une adresse de courriel valide ou un nom de domaine.",
|
||||
"recipient_maps": "Cartes des bénéficiaires",
|
||||
"recipient_maps": "Cartes des destinataires",
|
||||
"relay_all": "Relayer tous les destinataires",
|
||||
"remove": "Supprimer",
|
||||
"resources": "Ressources",
|
||||
@@ -965,7 +987,8 @@
|
||||
"syncjob_check_log": "Vérifier le journal",
|
||||
"recipient": "Destinataire",
|
||||
"open_logs": "Afficher les journaux",
|
||||
"iam": "Fournisseur d'identité"
|
||||
"iam": "Fournisseur d'identité",
|
||||
"internal": "Interne"
|
||||
},
|
||||
"oauth2": {
|
||||
"access_denied": "Veuillez vous connecter en tant que propriétaire de la boîte de réception pour accorder l’accès via Oauth2.",
|
||||
@@ -1222,7 +1245,7 @@
|
||||
"email_and_dav": "Courriel, calendriers et contacts",
|
||||
"encryption": "Chiffrement",
|
||||
"excludes": "Exclus",
|
||||
"expire_in": "Expire dans",
|
||||
"expire_in": "Expirer dans",
|
||||
"force_pw_update": "Vous <b>devez</b> définir un nouveau mot de passe pour pouvoir accéder aux services liés aux logiciels de groupe.",
|
||||
"generate": "générer",
|
||||
"hour": "heure",
|
||||
@@ -1243,7 +1266,7 @@
|
||||
"no_last_login": "Aucune dernière information de connexion à l'interface",
|
||||
"no_record": "Pas d'enregistrement",
|
||||
"password": "Mot de passe",
|
||||
"password_now": "Mot de passe courant (confirmer les changements)",
|
||||
"password_now": "Mot de passe actuel (confirmer les changements)",
|
||||
"password_repeat": "Mot de passe (répéter)",
|
||||
"pushover_evaluate_x_prio": "Acheminement du courrier hautement prioritaire [<code>X-Priority: 1</code>]",
|
||||
"pushover_info": "Les paramètres de notification push s’appliqueront à tout le courrier propre (non spam) livré à <b>%s</b> y compris les alias (partagés, non partagés, étiquetés).",
|
||||
@@ -1350,7 +1373,12 @@
|
||||
"mailbox_general": "Général",
|
||||
"mailbox_settings": "Paramètres",
|
||||
"tfa_info": "L'authentification à deux facteurs permet de protéger votre compte. Si vous l'activez, vous aurez besoin de mots de passe d'application pour vous connecter à des applications ou des services qui ne prennent pas en charge l'authentification à deux facteurs (par exemple les clients e-mails).",
|
||||
"overview": "Vue d'ensemble"
|
||||
"overview": "Vue d'ensemble",
|
||||
"expire_never": "Ne jamais expirer",
|
||||
"forever": "Pour toujours",
|
||||
"spam_aliases_info": "Un alias de spam est une adresse de courriel temporaire qui peut être utilisée pour protéger les véritables adresses de courriel. <br> De manière optionnelle, une durée d'expiration peut être définie afin que l'alias soit automatiquement désactivé après la période définie, éliminant ainsi les adresses étant abusées ou ayant fuité.",
|
||||
"authentication": "Authentification",
|
||||
"protocols": "Protocoles"
|
||||
},
|
||||
"warning": {
|
||||
"cannot_delete_self": "Impossible de supprimer l’utilisateur connecté",
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
"weeks": "Εβδομάδες",
|
||||
"with_app_password": "με κωδικό εφαρμογής",
|
||||
"year": "χρόνος",
|
||||
"years": "χρόνια"
|
||||
"years": "χρόνια",
|
||||
"value": "Τιμή"
|
||||
},
|
||||
"warning": {
|
||||
"cannot_delete_self": "Αδυναμία διαγραφής συνδεδεμένου χρήστη",
|
||||
@@ -16,5 +17,170 @@
|
||||
"hash_not_found": "Η κατακερματισμένη τιμή (hash value) δεν βρέθηκε ή έχει είδη διαγραφεί.",
|
||||
"ip_invalid": "Παραλείφθηκε μη έγκυρη διεύθυνση IP: %s",
|
||||
"is_not_primary_alias": "Παραλείφθηκε μη πρωτεύον ψευδώνυμο %s"
|
||||
},
|
||||
"acl": {
|
||||
"alias_domains": "Προσθήκη ψευδωνύμων τομέων",
|
||||
"app_passwds": "Διαχείριση κωδικών εφαρμογής",
|
||||
"bcc_maps": "χαρτογράφηση BCC",
|
||||
"delimiter_action": "Ενέργεια οριοθέτη",
|
||||
"domain_desc": "Αλλαγή περιγραφής τομέα",
|
||||
"domain_relayhost": "Αλλαγή του διακομιστή αναμετάδοσης για ένα τομέα",
|
||||
"eas_reset": "Επαναφορά συσκευών EAS",
|
||||
"extend_sender_acl": "Να επιτρέπεται η επέκταση ACL του αποστολέα με εξωτερικές διευθύνσεις",
|
||||
"filters": "Φίλτρα",
|
||||
"login_as": "Είσοδος ως χρήστης e-mail",
|
||||
"mailbox_relayhost": "Αλλαγή διακομιστή αναμετάδοσης για ένα γραμματοκιβώτιο",
|
||||
"prohibited": "Απαγορεύεται από την ACL",
|
||||
"protocol_access": "Αλλαγή πρόσβασης πρωτοκόλλου",
|
||||
"pushover": "Pushover",
|
||||
"pw_reset": "Επιτρέψτε την επαναφορά κωδικού πρόσβασης του χρήστη",
|
||||
"quarantine": "Ενέργειες καραντίνας",
|
||||
"quarantine_attachments": "Συνημμένα καραντίνας",
|
||||
"quarantine_category": "Αλλαγή κατηγορίας ειδοποιήσεων καραντίνας",
|
||||
"quarantine_notification": "Αλλαγή ειδοποιήσεων καραντίνας",
|
||||
"ratelimit": "Όριο τιμής",
|
||||
"recipient_maps": "Χάρτες παραληπτών",
|
||||
"smtp_ip_access": "Αλλαγή επιτρεπόμενων διακομιστών SMTP",
|
||||
"sogo_access": "Επιτρέψτε τη διαχείριση της πρόσβασης στο SOGo",
|
||||
"sogo_profile_reset": "Επαναφορά του προφίλ SOGo",
|
||||
"spam_alias": "Προσωρινά ψευδώνυμα",
|
||||
"spam_policy": "Λίστα απορρίψεων/Λίστα επιτρεπόμενων",
|
||||
"spam_score": "Βαθμολογία ανεπιθύμητης αλληλογραφίας",
|
||||
"syncjobs": "Εργασίες συγχρονισμού",
|
||||
"tls_policy": "Πολιτική TLS",
|
||||
"unlimited_quota": "Απεριόριστο όριο για γραμματοκιβώτια"
|
||||
},
|
||||
"add": {
|
||||
"activate_filter_warn": "Όλα τα άλλα φίλτρα θα απενεργοποιηθούν, όταν επιλεγεί η επιλογή \"ενεργό\".",
|
||||
"active": "Ενεργό",
|
||||
"add": "Προσθήκη",
|
||||
"add_domain_only": "Προσθήκη μόνο του τομέα",
|
||||
"add_domain_restart": "Προσθήκη του τομέα και επανεκκίνηση του SOGo",
|
||||
"alias_address": "Διευθύνσεις ψευδωνύμων",
|
||||
"alias_address_info": "<small>Πλήρης διεύθυνση(εις) e-mail ή @example.com, για να λαμβάνετε ΟΛΑ τα μηνύματα ενός τομέα (χωρισμένα με κόμα). <b>μόνο τομείς του mailcow</b>.</small>",
|
||||
"alias_domain": "Ψευδώνυμο τομέα",
|
||||
"alias_domain_info": "<small>Μόνο έγκυρα ονόματα τομέα (χωρισμένα με κόμα).</small>",
|
||||
"app_name": "Όνομα εφαρμογής",
|
||||
"app_password": "Προσθήκη κωδικού εφαρμογής",
|
||||
"app_passwd_protocols": "Επιτρεπόμενα πρωτόκολλα για κωδικούς εφαρμογών",
|
||||
"automap": "Αυτόματη αντιστοίχηση φακέλων (\"Απεσταλμένα μηνύματα\", \"Απεσταλμένα\" => \"Στάλθηκαν\" κ.τ.λ.)",
|
||||
"backup_mx_options": "Επιλογές αναμετάδοσης",
|
||||
"bcc_dest_format": "Η BCC διεύθυνση πρέπει να είναι μία και έγκυρη διεύθυνση e-mail.<br>Αν θέλετε να στείλετε αντίγραφα σε πολλούς παραλήπτες, δημιουργήστε ένα ψευδόνυμο για όλους και χρησιμοποιήστε το εδώ.",
|
||||
"comment_info": "Τα προσωπικά σχόλια δεν είναι ορατά στον χρήστη. Τα δημόσια σχόλια εμφανίζονται ως tooltips.",
|
||||
"custom_params": "Προσαρμοσμένες παράμετροι",
|
||||
"custom_params_hint": "Σωστή σύνταξη: --param=xy, λάθος σύνταξη: --param xy",
|
||||
"delete1": "Διαγραφή όταν ολοκληρωθεί",
|
||||
"delete2": "Διαγραφή μηνυμάτων στον προορισμό που δεν βρίσκονται στην πηγή",
|
||||
"delete2duplicates": "Διαγραφή διπλότυπων στον προορισμό",
|
||||
"description": "Περιγραφή",
|
||||
"destination": "Προορισμός",
|
||||
"disable_login": "Απαγόρευση εισόδου (η εισερχόμενη αλληλογραφία εξακολουθεί να γίνεται δεκτή)",
|
||||
"domain": "Τομέας",
|
||||
"domain_matches_hostname": "Ο τομέας %s είναι ο ίδιος με το όνομα του διακομιστή",
|
||||
"domain_quota_m": "Συνολικό όριο τομέα (MiB)",
|
||||
"dry": "Προσομοίωση συγχρονισμού",
|
||||
"enc_method": "Μέθοδος κρυπτογράφησης",
|
||||
"exclude": "Εξαίρεση αντικειμένων (regex)",
|
||||
"full_name": "Πλήρες όνομα",
|
||||
"gal": "Κοινόχρηστη λίστα διευθύνσεων"
|
||||
},
|
||||
"danger": {
|
||||
"unknown": "Παρουσιάστηκε κάποιο άγωνστο σφάλμα",
|
||||
"unknown_tfa_method": "Άγνωστη μέθοδος TFA",
|
||||
"unlimited_quota_acl": "Το απεριόριστο όριο απαγορεύεται από την ACL",
|
||||
"username_invalid": "Το όνομα χρήστη %s δεν μπορεί να χρησιμοποιηθεί",
|
||||
"validity_missing": "Παρακαλώ ορίστε μία περίοδο εγκυρότητας",
|
||||
"value_missing": "Παρακαλώ συμπληρώστε όλα τα δεδομένα",
|
||||
"version_invalid": "Η έκδοση %s δεν είναι έγκυρη",
|
||||
"yotp_verification_failed": "Η επαλήθευση μέσω Yubico OTP απέτυχε: %s"
|
||||
},
|
||||
"datatables": {
|
||||
"collapse_all": "Σύμπτυξη όλων",
|
||||
"decimal": ".",
|
||||
"emptyTable": "Δεν υπάρχουν εγγραφές",
|
||||
"expand_all": "Επέκταση όλων",
|
||||
"info": "Εμφανίζονται _START_ εώς _END_ από _TOTAL_ εγγραφές",
|
||||
"infoEmpty": "Εμφανίζονται 0 εώς 0 από 0 εγγραφές",
|
||||
"infoFiltered": "(φιλτραρισμένες από _MAX_ συνολικές εγγραφές)",
|
||||
"thousands": ",",
|
||||
"lengthMenu": "Εμφάνιση _MENU_ εγγραφών",
|
||||
"loadingRecords": "Γίνεται φόρτωση...",
|
||||
"processing": "Παρακαλώ περιμένετε...",
|
||||
"search": "Αναζήτηση:",
|
||||
"zeroRecords": "Δε βρέθηκαν εγγραφές",
|
||||
"paginate": {
|
||||
"first": "Πρώτη",
|
||||
"last": "Τελευταία",
|
||||
"next": "Επόμενη",
|
||||
"previous": "Προηγούμενη"
|
||||
},
|
||||
"aria": {
|
||||
"sortAscending": ": ενεργοποίηση αύξουσας ταξινόμησης",
|
||||
"sortDescending": ": ενεργοποίηση φθίνουσας ταξινόμησης"
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
"architecture": "Αρχιτεκτονική",
|
||||
"chart_this_server": "Γράφημα (αυτός ο διακομιστής)",
|
||||
"containers_info": "Πληροφορίες για τον container",
|
||||
"container_running": "Εκτελείται",
|
||||
"container_disabled": "Ο container έχει σταματήσει ή απενεργοποιηθεί",
|
||||
"container_stopped": "Σταματημένος",
|
||||
"cores": "Πυρήνες",
|
||||
"current_time": "Ώρα συστήματος",
|
||||
"disk_usage": "Χρήση αποθ. χώρου",
|
||||
"docs": "Έγγραφα",
|
||||
"error_show_ip": "Δεν είναι δυνατή η επίλυση της δημόσιας IP διεύθυνσης",
|
||||
"external_logs": "Εξωτερικά αρχεία καταγραφής",
|
||||
"history_all_servers": "Ιστορικό (Όλοι οι διακομιστές)",
|
||||
"in_memory_logs": "Αρχεία καταγραφής στη μνήμη",
|
||||
"last_modified": "Τελευταία τροποποίηση",
|
||||
"log_info": "<p>mailcow <b>in-memory logs</b> are collected in Redis lists and trimmed to LOG_LINES (%d) every minute to reduce hammering.\n <br>In-memory logs are not meant to be persistent. All applications that log in-memory, also log to the Docker daemon and therefore to the default logging driver.\n <br>The in-memory log type should be used for debugging minor issues with containers.</p>\n <p><b>External logs</b> are collected via API of the given application.</p>\n <p><b>Static logs</b> are mostly activity logs, that are not logged to the Dockerd but still need to be persistent (except for API logs).</p>",
|
||||
"login_time": "Ώρα",
|
||||
"logs": "Αρχεία καταγραφής",
|
||||
"memory": "Μνήμη",
|
||||
"online_users": "Συνδεδεμένοι χρήστες",
|
||||
"restart_container": "Επανεκκίνηση",
|
||||
"service": "Υπηρεσία",
|
||||
"show_ip": "Εμφάνιση δημόσιας IP",
|
||||
"size": "Μέγεθος",
|
||||
"started_at": "Ξεκίνησε στις",
|
||||
"started_on": "Ξεκίνησε στις",
|
||||
"static_logs": "Στατικά αρχεία καταγραφής",
|
||||
"success": "Επιτυχία",
|
||||
"system_containers": "Σύστημα και Containers",
|
||||
"timezone": "Ζώνη ώρας",
|
||||
"uptime": "Χρόνος λειτουργίας",
|
||||
"update_available": "Υπάρχει διαθέσιμη ενημέρωση",
|
||||
"no_update_available": "Έχετε τη τελευταία έκδοση του συστήματος",
|
||||
"update_failed": "Δεν ήταν δυνατός ο έλεγχος για ενημερώσεις",
|
||||
"username": "Όνομα χρήστη",
|
||||
"wip": "Currently Work in Progress"
|
||||
},
|
||||
"diagnostics": {
|
||||
"cname_from_a": "Value derived from A/AAAA record. This is supported as long as the record points to the correct resource.",
|
||||
"dns_records": "Εγγραφές DNS",
|
||||
"dns_records_24hours": "Παρακαλώ σημειώστε ότι οι αλλαγές στο DNS μπορεί να χρειαστούν μέχρι 24 ώρες για να ενημερωθούν σωστά και να εμφανιστούν σε αυτή τη σελίδα. Ο σκοπός της είναι να δείτε πως μπορείτε να ρυθμίσετε σωστά τις εγγραφές DNS και να ελέγξετε αν είναι σωστές.",
|
||||
"dns_records_data": "Σωστά δεδομένα",
|
||||
"dns_records_docs": "Παρακαλώ συμβουλευτείτε επίσης <a target=\"_blank\" href=\"https://docs.mailcow.email/getstarted/prerequisite-dns\">την τεκμηρίωση</a>.",
|
||||
"dns_records_name": "Όνομα",
|
||||
"dns_records_status": "Τρέχουσα κατάσταση",
|
||||
"dns_records_type": "Τύπος",
|
||||
"optional": "Αυτή η εγγραφή είναι προαιρετική."
|
||||
},
|
||||
"edit": {
|
||||
"acl": "ACL (Δικαίωμα)",
|
||||
"active": "Ενεργό",
|
||||
"admin": "Επεξεργασία διαχειριστή",
|
||||
"advanced_settings": "Ρυθμίσεις για προχωρημένους",
|
||||
"alias": "Επεξεργασία ψευδώνυμου",
|
||||
"allow_from_smtp": "Επέτρεψε μόνο σε αυτές τις IPs να χρησιμοποιήσουν το <b>SMTP</b>",
|
||||
"allow_from_smtp_info": "Αφήστε το κενό για να επιτρέψετε όλους τους αποστολείς.<br>IPv4/IPv6 διευθύνσεις και δίκτυα.",
|
||||
"allowed_protocols": "Επιτρεπόμενα πρωτόκολλα για απ' ευθείας πρόσβαση από τους χρήστες (δεν επηρεάζει τα πρωτόκολλα κωδικών πρόσβασης εφαρμογής)",
|
||||
"app_name": "Όνομα εφαρμογής",
|
||||
"app_passwd": "Κωδικός πρόσβασης εφαρμογής",
|
||||
"app_passwd_protocols": "Επιτρέπομενα πρωτόκολλα για τον κωδικό εφαρμογής",
|
||||
"automap": "Αυτόματη αντιστοίχηση φακέλων (\"Απεσταλμένα μηνύματα\", \"Απεσταλμένα\" => \"Στάλθηκαν\" κ.τ.λ.)",
|
||||
"backup_mx_options": "Επιλογές αναμετάδοσης"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1187,6 +1187,7 @@
|
||||
"created_on": "作成日",
|
||||
"daily": "毎日",
|
||||
"day": "日",
|
||||
"description": "説明",
|
||||
"delete_ays": "削除プロセスを確認してください。",
|
||||
"direct_aliases": "直接エイリアスアドレス",
|
||||
"direct_aliases_desc": "直接エイリアスアドレスは、スパムフィルターおよびTLSポリシー設定の影響を受けます。",
|
||||
@@ -1201,7 +1202,9 @@
|
||||
"encryption": "暗号化",
|
||||
"excludes": "除外",
|
||||
"expire_in": "有効期限まで",
|
||||
"expire_never": "有効期限なし",
|
||||
"fido2_webauthn": "FIDO2/WebAuthn",
|
||||
"forever": "有効期限なし",
|
||||
"force_pw_update": "グループウェア関連サービスにアクセスするには、新しいパスワードを<b>必ず</b>設定する必要があります。",
|
||||
"from": "送信元",
|
||||
"generate": "生成",
|
||||
|
||||
@@ -185,11 +185,12 @@
|
||||
"protocol_access": "Endre protokolltilgang",
|
||||
"pushover": "Pushover",
|
||||
"quarantine": "Karantenehandlinger",
|
||||
"quarantine_attachments": "Sett vedlegg i karantene",
|
||||
"quarantine_attachments": "Se vedlegg i karantene",
|
||||
"quarantine_category": "Endre varslingskategori for karantene",
|
||||
"quarantine_notification": "Endre karantenevarslinger",
|
||||
"domain_desc": "Endre domenebeskrivelse",
|
||||
"extend_sender_acl": "Tillat utvidelse av sender-ACL fra eksterne adresser"
|
||||
"extend_sender_acl": "Tillat utvidelse av sender-ACL fra eksterne adresser",
|
||||
"pw_reset": "Tillat endring av brukerpassord"
|
||||
},
|
||||
"add": {
|
||||
"app_passwd_protocols": "Tillatte protokoller for app-passord",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -340,7 +340,8 @@
|
||||
"tls_policy": "Política de TLS",
|
||||
"quarantine_attachments": "Anexos de quarentena",
|
||||
"filters": "Filtros",
|
||||
"smtp_ip_access": "Mudar anfitriões permitidos para SMTP"
|
||||
"smtp_ip_access": "Mudar anfitriões permitidos para SMTP",
|
||||
"app_passwds": "Gerenciar senhas de aplicativos"
|
||||
},
|
||||
"warning": {
|
||||
"no_active_admin": "Não é possível desactivar o último administrador activo"
|
||||
|
||||
@@ -109,7 +109,9 @@
|
||||
"timeout2": "Тайм-аут для подключения к локальному хосту",
|
||||
"username": "Имя пользователя",
|
||||
"validate": "Проверить",
|
||||
"validation_success": "Проверка прошла успешно"
|
||||
"validation_success": "Проверка прошла успешно",
|
||||
"internal": "Внутренний",
|
||||
"internal_info": "Внутренние псевдонимы доступны только из самого домена или доменов-псевдонимов."
|
||||
},
|
||||
"admin": {
|
||||
"access": "Настройки доступа",
|
||||
@@ -550,7 +552,11 @@
|
||||
"generic_server_error": "На сервере произошла непредвиденная ошибка. Пожалуйста, свяжитесь с вашим администратором.",
|
||||
"authsource_in_use": "Поставщик идентификационных данных не может быть изменен или удален, так как в данный момент он используется одним или несколькими пользователями.",
|
||||
"iam_test_connection": "Ошибка соединения",
|
||||
"required_data_missing": "Отсутствуют необходимые данные %s"
|
||||
"required_data_missing": "Отсутствуют необходимые данные %s",
|
||||
"max_age_invalid": "Максимальный возраст %s недействителен",
|
||||
"mode_invalid": "Режим %s недействителен",
|
||||
"mx_invalid": "Запись MX %s недействительна",
|
||||
"version_invalid": "Версия %s недействительна"
|
||||
},
|
||||
"datatables": {
|
||||
"collapse_all": "Свернуть все",
|
||||
@@ -762,7 +768,20 @@
|
||||
"title": "Изменение объекта",
|
||||
"unchanged_if_empty": "Если без изменений - оставьте пустым",
|
||||
"username": "Имя пользователя",
|
||||
"validate_save": "Подтвердить и сохранить"
|
||||
"validate_save": "Подтвердить и сохранить",
|
||||
"internal": "Внутренний",
|
||||
"internal_info": "Внутренние псевдонимы доступны только из самого домена или доменов-псевдонимов.",
|
||||
"mta_sts": "MTA-STS",
|
||||
"mta_sts_info": "<a href='https://en.wikipedia.org/wiki/Simple_Mail_Transfer_Protocol#SMTP_MTA_Strict_Transport_Security' target='_blank'>MTA-STS</a> — это стандарт, который обязывает почтовые серверы использовать TLS с подлинными сертификатами для доставки электронной почты.<br>Он используется, когда <a target='_blank' href='https://ru.wikipedia.org/wiki/DANE'>DANE</a> невозможен из-за неиспользуемого или неподдерживаемого DNSSEC.<br><b>Примечание</b>: если принимающий домен поддерживает DANE с DNSSEC, <b>всегда</b> предпочитается DANE — MTA-STS действует только как резервный вариант.",
|
||||
"mta_sts_version": "Версия",
|
||||
"mta_sts_version_info": "Определяет версию стандарта MTA-STS – на данный момент существует только <code>STSv1</code>.",
|
||||
"mta_sts_mode": "Режим",
|
||||
"mta_sts_mode_info": "Есть три режима на выбор:<ul><li><em>testing</em> – политика только наблюдается, нарушения не имеют последствий.</li><li><em>enforce</em> – политика соблюдается строго, соединения без подлинного TLS отклоняются.</li><li><em>none</em> – политика опубликована, но не применяется.</li></ul>",
|
||||
"mta_sts_max_age": "Максимальный возраст",
|
||||
"mta_sts_max_age_info": "Время в секундах, в течение которого принимающие почтовые серверы могут кэшировать эту политику перед повторной загрузкой.",
|
||||
"mta_sts_mx": "Сервер MX",
|
||||
"mta_sts_mx_info": "Разрешает отправку только на явно указанные имена хостов почтовых серверов; отправляющий MTA проверяет, соответствует ли DNS-имя MX-хоста списку политик, и разрешает доставку только с подлинным TLS-сертификатом (защита от MITM).",
|
||||
"mta_sts_mx_notice": "Можно указать несколько MX-серверов (через запятую)."
|
||||
},
|
||||
"fido2": {
|
||||
"confirm": "Подтвердить",
|
||||
@@ -832,7 +851,8 @@
|
||||
"login_admintext": "Войти как администратор",
|
||||
"login_user": "Вход для пользователей",
|
||||
"login_dadmin": "Вход для администраторов домена",
|
||||
"login_admin": "Вход для администраторов"
|
||||
"login_admin": "Вход для администраторов",
|
||||
"email": "Email-адрес"
|
||||
},
|
||||
"mailbox": {
|
||||
"action": "Действия",
|
||||
@@ -1008,7 +1028,8 @@
|
||||
"waiting": "В ожидании",
|
||||
"weekly": "Раз в неделю",
|
||||
"yes": "✓",
|
||||
"iam": "Поставщик идентификационных данных"
|
||||
"iam": "Поставщик идентификационных данных",
|
||||
"internal": "Внутренний"
|
||||
},
|
||||
"oauth2": {
|
||||
"access_denied": "Пожалуйста, войдите в систему как владелец почтового аккаунта, чтобы получить доступ через OAuth2.",
|
||||
@@ -1331,7 +1352,7 @@
|
||||
"sogo_profile_reset": "Сбросить профиль SOGo",
|
||||
"sogo_profile_reset_help": "<b>Внимание:</b> это удалит настройки профиля SOGo вместе с <b>всеми контактами, календарями и фильтрами безвозвратно</b>.",
|
||||
"sogo_profile_reset_now": "Сбросить профиль сейчас",
|
||||
"spam_aliases": "Временные псевдонимы электронной почты",
|
||||
"spam_aliases": "Псевдонимы для спама",
|
||||
"spam_score_reset": "Сброс на настройки по умолчанию",
|
||||
"spamfilter": "Спам фильтр",
|
||||
"spamfilter_behavior": "Фильтрация спама",
|
||||
@@ -1387,7 +1408,10 @@
|
||||
"authentication": "Аутентификация",
|
||||
"tfa_info": "Двухфакторная аутентификация помогает защитить вашу учетную запись. Если вы включите эту функцию, вам понадобятся пароли приложений для входа в приложения или службы, которые не поддерживают двухфакторную аутентификацию (например, почтовые клиенты).",
|
||||
"protocols": "Протоколы",
|
||||
"overview": "Обзор"
|
||||
"overview": "Обзор",
|
||||
"expire_never": "Никогда не истекает",
|
||||
"forever": "Навсегда",
|
||||
"spam_aliases_info": "Псевдоним для спама — это временный адрес электронной почты, который можно использовать для защиты реальных адресов.<br>При желании можно установить срок действия, по истечении которого псевдоним будет автоматически деактивирован, что позволяет эффективно избавляться от адресов, которые были использованы не по назначению или стали доступны посторонним лицам."
|
||||
},
|
||||
"warning": {
|
||||
"cannot_delete_self": "Вы не можете удалить сами себя",
|
||||
|
||||
@@ -297,7 +297,7 @@
|
||||
"forwarding_hosts_add_hint": "Lahko vpišete IPv4/IPv6 naslove, mreže v CIDR obliki, imena gostiteljev (kateri se prevedejo v IP naslove) ali imena domen (katera se prevedejo v IP naslove glede na poizvedbo po SPF zapisih, v primeru manjkajočih zapisov pa MX zapisih).",
|
||||
"forwarding_hosts_hint": "Dohodna sporočila so brezpogojno sprejeta od katerih koli gostiteljev v tem seznamu. Ti gostitelji se ne bodo preverjali po DNSBL seznamih in ne bodo dodani v listo sivih. Prejeta neželena pošta s teh gostiteljev ni nikoli zavrnjena, opcijsko pa se lahko premakne v mapo neželene pošte. Najpogostejša uporaba za to je navedba poštnih strežnikov, iz katerih ste nastavili pravilo za posredovanje pošte na vaš mailcow strežnik.",
|
||||
"license_info": "Licenca ni zahtevana, a pomaga pri nadaljnjem razvoju. <br><a href=\"https://www.servercow.de/mailcow?lang=en#sal\" target=\"_blank\" alt=\"Naročilo SAL\">Registrirajte svoj GUID tukaj</a> ali <a href=\"https://www.servercow.de/mailcow?lang=en#support\" target=\"_blank\" alt=\"Naročilo podpore\">Kupite podporo za svojo namestitev Mailcow.</a>",
|
||||
"lookup_mx": "Cilj je regularni izraz, ki se ujema z imenom MX (<code>.*\\.google\\.com</code> za usmerjanje vse pošte, usmerjene na MX, ki se konča na google.com, prek tega skoka).",
|
||||
"lookup_mx": "Cilj je regularni izraz, ki se ujema z imenom MX (<code>.*\\.google\\.com</code> za usmerjanje vse pošte, usmerjene na MX, ki se konča na google.com, prek tega skoka)",
|
||||
"main_name": "Naziv \"mailcow UI\"",
|
||||
"merged_vars_hint": "Sive vrstice so združene iz <code>vars.(local.)inc.php</code> in jih ni mogoče spremeniti.",
|
||||
"oauth2_info": "Implementacija OAuth2 podpira vrsto odobritve »Avtorizacijska koda« in izda osvežilne žetone.<br>\nStrežnik samodejno izda tudi nove osvežilne žetone, ko je žeton za osvežitev uporabljen.<br><br>\n• Privzeti obseg je <i>profile</i>. Prek OAuth2 je mogoče overiti samo uporabnike poštnega predala. Če parameter obsega izpustite, se vrne na <i>profile</i>.<br>\n• Parameter <i>state</i> mora odjemalec poslati kot del zahteve za avtorizacijo.<br><br>\nPoti za zahteve do API-ja OAuth2: <br>\n<ul>\n<li>Končna točka avtorizacije: <code>/oauth/authorize</code></li>\n<li>Končna točka žetona: <code>/oauth/token</code></li>\n<li>Stran z viri: <code>/oauth/profile</code></li>\n</ul>\nPonovno ustvarjanje skrivnosti odjemalca ne bo poteklo obstoječih kod za avtorizacijo, vendar ne bo obnovilo žetona.<br><br>\nPreklic žetonov odjemalca bo povzročil takojšnjo prekinitev vseh aktivnih sej. Vse stranke se morajo ponovno overiti.",
|
||||
@@ -414,7 +414,7 @@
|
||||
"needs_restart": "potreben je ponovni zagon"
|
||||
},
|
||||
"danger": {
|
||||
"alias_goto_identical": "Vzdevek in ciljni naslov se ne smeta ujemati.",
|
||||
"alias_goto_identical": "Vzdevek in ciljni naslov se ne smeta ujemati",
|
||||
"aliasd_targetd_identical": "Vzdevek domene ne sme biti enak ciljni domeni: %s",
|
||||
"bcc_exists": "Za tip %s obstaja BCC preslikava %s",
|
||||
"dkim_domain_or_sel_exists": "DKIM ključ za \"%s\" obstaja in ne bo prepisan",
|
||||
@@ -651,7 +651,7 @@
|
||||
"pushover_title": "Naslov obvestila",
|
||||
"domains": "Domene",
|
||||
"extended_sender_acl_info": "Uvoziti je treba ključ domene DKIM, če je na voljo.<br>\n Ne pozabite dodati tega strežnika v ustrezni zapis SPF TXT.<br>\n Kadar koli je temu strežniku dodana domena ali vzdevek domene, ki se prekriva z zunanjim naslovom, se zunanji naslov odstrani.<br>\n Uporabite @domain.tld, da omogočite pošiljanje kot *@domain.tld.",
|
||||
"lookup_mx": "Cilj je regularni izraz, ki se ujema z imenom MX (<code>.*\\.google\\.com</code> za usmerjanje vse pošte, usmerjene na MX, ki se konča na google.com, prek tega skoka).",
|
||||
"lookup_mx": "Cilj je regularni izraz, ki se ujema z imenom MX (<code>.*\\.google\\.com</code> za usmerjanje vse pošte, usmerjene na MX, ki se konča na google.com, prek tega skoka)",
|
||||
"maxbytespersecond": "Največ bajtov na sekundo <br><small>(0 = neomejeno)</small>",
|
||||
"pushover_sender_array": "Upoštevaj samo sledeče e-poštne naslove pošiljateljev <small>(ločeni z vejico)</small>",
|
||||
"mbox_rl_info": "Ta omejitev se uporabi za prijavno ime SASL in se ujema z naslovom \"od\", ki ga uporablja prijavljeni uporabnik. Omejitev poštnega nabiralnika preglasi omejitev za celotno domeno.",
|
||||
@@ -1359,7 +1359,7 @@
|
||||
"sogo_profile_reset": "Ponastavi profil SOGo",
|
||||
"sogo_profile_reset_help": "S tem boste uničili uporabnikov profil SOGo in <b>nepovratno izbrisali vse stike in podatke koledarja</b>.",
|
||||
"sogo_profile_reset_now": "Ponastavi profil zdaj",
|
||||
"spam_aliases": "Začasni vzdevki e-pošte",
|
||||
"spam_aliases": "Vzdevki neželene e-pošte",
|
||||
"spam_score_reset": "Ponastavi na privzete nastavitve strežnika",
|
||||
"spamfilter": "Filter neželene pošte",
|
||||
"spamfilter_behavior": "Ocena",
|
||||
@@ -1407,7 +1407,10 @@
|
||||
"years": "leta",
|
||||
"waiting": "Čakanje",
|
||||
"q_all": "Vse kategorije",
|
||||
"syncjob_EX_OK": "Uspeh"
|
||||
"syncjob_EX_OK": "Uspeh",
|
||||
"expire_never": "Nikoli ne poteče",
|
||||
"forever": "Za vedno",
|
||||
"spam_aliases_info": "Vzdevek za neželeno pošto je začasni e-poštni naslov, ki ga je mogoče uporabiti za zaščito pravih e-poštnih naslovov. <br>Po želji je mogoče nastaviti čas poteka veljavnosti, tako da se vzdevek po določenem obdobju samodejno deaktivira, s čimer se učinkovito znebite zlorabljenih ali razkritih naslovov."
|
||||
},
|
||||
"warning": {
|
||||
"cannot_delete_self": "Prijavljenega uporabnika ni mogoče izbrisati",
|
||||
|
||||
697
data/web/lang/lang.vi-vn.json
Normal file
697
data/web/lang/lang.vi-vn.json
Normal file
@@ -0,0 +1,697 @@
|
||||
{
|
||||
"acl": {
|
||||
"alias_domains": "Thêm tên miền bí danh",
|
||||
"app_passwds": "Quản lý mật khẩu ứng dụng",
|
||||
"bcc_maps": "Ánh xạ BCC",
|
||||
"delimiter_action": "Hành động phân cách",
|
||||
"domain_desc": "Thay đổi mô tả tên miền",
|
||||
"domain_relayhost": "Thay đổi máy chủ chuyển tiếp cho tên miền",
|
||||
"eas_reset": "Đặt lại thiết bị EAS",
|
||||
"extend_sender_acl": "Cho phép mở rộng ACL người gửi bằng địa chỉ bên ngoài",
|
||||
"filters": "Bộ lọc",
|
||||
"login_as": "Đăng nhập với tư cách người dùng hộp thư",
|
||||
"mailbox_relayhost": "Thay đổi máy chủ chuyển tiếp cho hộp thư",
|
||||
"prohibited": "Bị cấm bởi ACL",
|
||||
"protocol_access": "Thay đổi quyền truy cập giao thức",
|
||||
"pushover": "Thông báo đẩy",
|
||||
"pw_reset": "Cho phép đặt lại mật khẩu người dùng mailcow",
|
||||
"quarantine": "Hành động cách ly",
|
||||
"quarantine_attachments": "Cách ly tệp đính kèm",
|
||||
"quarantine_category": "Thay đổi danh mục thông báo cách ly",
|
||||
"quarantine_notification": "Thay đổi thông báo cách ly",
|
||||
"ratelimit": "Giới hạn tốc độ",
|
||||
"recipient_maps": "Ánh xạ người nhận",
|
||||
"smtp_ip_access": "Thay đổi máy chủ được phép cho SMTP",
|
||||
"sogo_access": "Cho phép quản lý truy cập SOGo",
|
||||
"sogo_profile_reset": "Đặt lại hồ sơ SOGo",
|
||||
"spam_alias": "Bí danh tạm thời",
|
||||
"spam_policy": "Danh sách chặn/Danh sách cho phép",
|
||||
"spam_score": "Điểm thư rác",
|
||||
"syncjobs": "Công việc đồng bộ",
|
||||
"tls_policy": "Chính sách TLS",
|
||||
"unlimited_quota": "Hạn ngạch không giới hạn cho hộp thư"
|
||||
},
|
||||
"add": {
|
||||
"activate_filter_warn": "Tất cả các bộ lọc khác sẽ bị vô hiệu hóa khi tùy chọn kích hoạt được chọn.",
|
||||
"active": "Đang hoạt động",
|
||||
"add": "Thêm",
|
||||
"add_domain_only": "Chỉ thêm tên miền",
|
||||
"add_domain_restart": "Thêm tên miền và khởi động lại SOGo",
|
||||
"alias_address": "Địa chỉ bí danh",
|
||||
"alias_address_info": "<small>Địa chỉ email đầy đủ hoặc @example.com để bắt tất cả thư cho một tên miền (phân cách bằng dấu phẩy). <b>Chỉ áp dụng cho tên miền mailcow</b>.</small>",
|
||||
"alias_domain": "Tên miền bí danh",
|
||||
"alias_domain_info": "<small>Chỉ cho phép tên miền hợp lệ (phân cách bằng dấu phẩy).</small>",
|
||||
"app_name": "Tên ứng dụng",
|
||||
"app_password": "Thêm mật khẩu ứng dụng",
|
||||
"app_passwd_protocols": "Các giao thức được phép cho mật khẩu ứng dụng",
|
||||
"automap": "Thử tự động ánh xạ thư mục (\"Mục đã gửi\", \"Đã gửi\" => \"Đã gửi\" v.v.)",
|
||||
"backup_mx_options": "Tùy chọn chuyển tiếp",
|
||||
"bcc_dest_format": "Địa chỉ BCC phải là một địa chỉ email hợp lệ duy nhất.<br>Nếu bạn cần gửi bản sao đến nhiều địa chỉ, hãy tạo một bí danh và sử dụng nó ở đây.",
|
||||
"comment_info": "Bình luận riêng tư không hiển thị với người dùng, trong khi bình luận công khai được hiển thị dưới dạng chú thích khi di chuột qua trong tổng quan người dùng",
|
||||
"custom_params": "Tham số tùy chỉnh",
|
||||
"custom_params_hint": "Đúng: --param=xy, sai: --param xy",
|
||||
"delete1": "Xóa từ nguồn khi hoàn thành",
|
||||
"delete2": "Xóa thư ở đích không có ở nguồn",
|
||||
"delete2duplicates": "Xóa các bản sao ở đích",
|
||||
"description": "Mô tả",
|
||||
"destination": "Đích",
|
||||
"disable_login": "Không cho phép đăng nhập (vẫn nhận được thư đến)",
|
||||
"domain": "Tên miền",
|
||||
"domain_matches_hostname": "Tên miền %s khớp với tên máy chủ",
|
||||
"domain_quota_m": "Tổng hạn ngạch tên miền (MiB)",
|
||||
"dry": "Mô phỏng đồng bộ hóa",
|
||||
"enc_method": "Phương thức mã hóa",
|
||||
"exclude": "Loại trừ đối tượng (biểu thức chính quy)",
|
||||
"full_name": "Tên đầy đủ",
|
||||
"gal": "Danh sách địa chỉ toàn cục",
|
||||
"gal_info": "GAL chứa tất cả các đối tượng của một tên miền và không thể được chỉnh sửa bởi bất kỳ người dùng nào. Thông tin rảnh/bận trong SOGo sẽ bị thiếu nếu vô hiệu hóa! <b>Khởi động lại SOGo để áp dụng thay đổi.</b>",
|
||||
"generate": "Tạo",
|
||||
"goto_ham": "\"Học là <span class=\"text-success\"><b>thư bình thường</b></span>",
|
||||
"goto_null": "Loại bỏ thư một cách thầm lặng",
|
||||
"goto_spam": "Học là <span class=\"text-danger\"><b>thư rác</b></span>",
|
||||
"hostname": "Máy chủ",
|
||||
"inactive": "Không hoạt động",
|
||||
"internal": "Nội bộ",
|
||||
"internal_info": "Bí danh nội bộ chỉ có thể truy cập từ tên miền sở hữu hoặc tên miền bí danh.",
|
||||
"kind": "Loại",
|
||||
"mailbox_quota_def": "Hạn ngạch hộp thư mặc định",
|
||||
"mailbox_quota_m": "Hạn ngạch tối đa mỗi hộp thư (MiB)",
|
||||
"mailbox_username": "Tên người dùng (phần bên trái của địa chỉ email)",
|
||||
"max_aliases": "Số lượng bí danh tối đa có thể tạo",
|
||||
"max_mailboxes": "Số lượng hộp thư tối đa có thể tạo",
|
||||
"mins_interval": "Khoảng thời gian kiểm tra (phút)",
|
||||
"multiple_bookings": "Đặt chỗ nhiều lần",
|
||||
"nexthop": "Bước nhảy tiếp theo",
|
||||
"password": "Mật khẩu",
|
||||
"password_repeat": "Xác nhận mật khẩu (nhập lại)",
|
||||
"port": "Cổng",
|
||||
"post_domain_add": "Container SOGo, \\\"sogo-mailcow\\\", cần được khởi động lại sau khi thêm tên miền mới!<br><br>Ngoài ra, cấu hình DNS của tên miền cần được xem xét. Khi cấu hình DNS được phê duyệt, khởi động lại \\\"acme-mailcow\\\" để tự động tạo chứng chỉ cho tên miền mới của bạn (autoconfig.<domain>, autodiscover.<domain>).<br>Bước này là tùy chọn và sẽ được thử lại sau mỗi 24 giờ.",
|
||||
"private_comment": "Bình luận riêng tư",
|
||||
"public_comment": "Bình luận công khai",
|
||||
"quota_mb": "Hạn ngạch (MiB)",
|
||||
"relay_all": "Chuyển tiếp tất cả người nhận",
|
||||
"relay_all_info": "↪ Nếu bạn chọn <b>không</b> chuyển tiếp tất cả người nhận, bạn sẽ cần thêm một hộp thư (\"ẩn\") cho từng người nhận cần được chuyển tiếp.",
|
||||
"relay_domain": "Chuyển tiếp tên miền này",
|
||||
"relay_transport_info": "<div class=\"badge fs-6 bg-info\">Thông tin</div> Bạn có thể định nghĩa ánh xạ vận chuyển cho đích tùy chỉnh cho tên miền này. Nếu không được đặt, tra cứu MX sẽ được thực hiện.",
|
||||
"relay_unknown_only": "Chỉ chuyển tiếp hộp thư không tồn tại. Hộp thư hiện có sẽ được gửi cục bộ.",
|
||||
"relayhost_wrapped_tls_info": "Vui lòng <b>không</b> sử dụng các cổng được bọc TLS (thường được sử dụng trên cổng 465).<br>\nSử dụng bất kỳ cổng không được bọc nào và sử dụng STARTTLS. Chính sách TLS để thực thi TLS có thể được tạo trong \"Ánh xạ chính sách TLS\".",
|
||||
"select": "Vui lòng chọn...",
|
||||
"select_domain": "Vui lòng chọn tên miền trước",
|
||||
"sieve_desc": "Mô tả ngắn",
|
||||
"sieve_type": "Loại bộ lọc",
|
||||
"skipcrossduplicates": "Bỏ qua tin nhắn trùng lặp giữa các thư mục (ai đến trước được phục vụ trước)",
|
||||
"subscribeall": "Đăng ký tất cả thư mục",
|
||||
"syncjob": "Thêm công việc đồng bộ",
|
||||
"syncjob_hint": "Lưu ý rằng mật khẩu cần được lưu dưới dạng văn bản thuần!",
|
||||
"tags": "Thẻ",
|
||||
"target_address": "Địa chỉ chuyển tiếp",
|
||||
"target_address_info": "<small>Địa chỉ email đầy đủ (phân cách bằng dấu phẩy).</small>",
|
||||
"target_domain": "Tên miền đích",
|
||||
"timeout1": "Thời gian chờ kết nối đến máy chủ từ xa",
|
||||
"timeout2": "Thời gian chờ kết nối đến máy chủ cục bộ",
|
||||
"username": "Tên người dùng",
|
||||
"validate": "Xác thực",
|
||||
"validation_success": "Xác thực thành công"
|
||||
},
|
||||
"admin": {
|
||||
"access": "Truy cập",
|
||||
"action": "Hành động",
|
||||
"activate_api": "Kích hoạt API",
|
||||
"activate_send": "Kích hoạt nút gửi",
|
||||
"active": "Đang hoạt động",
|
||||
"active_rspamd_settings_map": "Ánh xạ cài đặt đang hoạt động",
|
||||
"add": "Thêm",
|
||||
"add_admin": "Thêm quản trị viên",
|
||||
"add_domain_admin": "Thêm quản trị viên tên miền",
|
||||
"add_forwarding_host": "Thêm máy chủ chuyển tiếp",
|
||||
"add_relayhost": "Thêm vận chuyển phụ thuộc người gửi",
|
||||
"add_relayhost_hint": "Vui lòng lưu ý rằng dữ liệu xác thực, nếu có, sẽ được lưu trữ dưới dạng văn bản thuần.",
|
||||
"add_row": "Thêm hàng",
|
||||
"add_settings_rule": "Thêm quy tắc cài đặt",
|
||||
"add_transport": "Thêm vận chuyển",
|
||||
"add_transports_hint": "Vui lòng lưu ý rằng dữ liệu xác thực, nếu có, sẽ được lưu trữ dưới dạng văn bản thuần.",
|
||||
"additional_rows": " hàng bổ sung đã được thêm",
|
||||
"admin": "Quản trị viên",
|
||||
"admin_details": "Chỉnh sửa chi tiết quản trị viên",
|
||||
"admin_domains": "Gán tên miền",
|
||||
"admins": "Những quản trị viên",
|
||||
"admins_ldap": "Quản trị viên LDAP",
|
||||
"admin_quicklink": "Ẩn liên kết nhanh đến trang đăng nhập quản trị",
|
||||
"advanced_settings": "Cài đặt nâng cao",
|
||||
"allowed_methods": "Phương thức cho phép kiểm soát truy cập",
|
||||
"allowed_origins": "Nguồn gốc cho phép kiểm soát truy cập",
|
||||
"api_allow_from": "Cho phép truy cập API từ các IP/ký hiệu mạng CIDR này",
|
||||
"api_info": "API đang được phát triển. Tài liệu có thể được tìm thấy tại <a href=\"/api\">/api</a>",
|
||||
"api_key": "Khóa API",
|
||||
"api_read_only": "Truy cập chỉ đọc",
|
||||
"api_read_write": "Truy cập đọc-ghi",
|
||||
"api_skip_ip_check": "Bỏ qua kiểm tra IP cho API",
|
||||
"app_hide": "Ẩn khi đăng nhập",
|
||||
"app_links": "Liên kết ứng dụng",
|
||||
"app_name": "Tên ứng dụng",
|
||||
"apps_name": "Tên \"Ứng dụng mailcow\"",
|
||||
"arrival_time": "Thời gian đến (giờ máy chủ)",
|
||||
"authed_user": "Người dùng đã xác thực",
|
||||
"ays": "Bạn có chắc chắn muốn tiếp tục không?",
|
||||
"ban_list_info": "Xem danh sách IP bị cấm bên dưới: <b>mạng (thời gian cấm còn lại) - [hành động]</b>.<br />IP trong hàng đợi bỏ cấm sẽ được xóa khỏi danh sách cấm hoạt động trong vài giây.<br />Nhãn đỏ cho biết lệnh cấm vĩnh viễn đang hoạt động bởi danh sách từ chối.",
|
||||
"change_logo": "Thay đổi logo",
|
||||
"logo_normal_label": "Bình thường",
|
||||
"logo_dark_label": "Đảo ngược cho chế độ tối",
|
||||
"configuration": "Cấu hình",
|
||||
"convert_html_to_text": "Chuyển đổi HTML thành văn bản thuần",
|
||||
"copy_to_clipboard": "Văn bản đã được sao chép vào bộ nhớ tạm!",
|
||||
"cors_settings": "Cài đặt CORS",
|
||||
"credentials_transport_warning": "<b>Cảnh báo</b>: Thêm một mục ánh xạ vận chuyển mới sẽ cập nhật thông tin xác thực cho tất cả các mục có cột bước nhảy tiếp theo khớp.",
|
||||
"customer_id": "ID Khách hàng",
|
||||
"customize": "Tùy chỉnh",
|
||||
"login_page": "Trang đăng nhập",
|
||||
"destination": "Đích đến",
|
||||
"dkim_add_key": "Thêm khóa ARC/DKIM",
|
||||
"dkim_domains_selector": "Bộ chọn",
|
||||
"dkim_domains_wo_keys": "Chọn tên miền thiếu khóa",
|
||||
"dkim_from": "Từ",
|
||||
"dkim_from_title": "Tên miền nguồn để sao chép dữ liệu",
|
||||
"dkim_key_length": "Độ dài khóa DKIM (bits)",
|
||||
"dkim_key_missing": "Thiếu khóa",
|
||||
"dkim_key_unused": "Khóa không được sử dụng",
|
||||
"dkim_key_valid": "Khóa hợp lệ",
|
||||
"dkim_keys": "Khóa ARC/DKIM",
|
||||
"dkim_overwrite_key": "Ghi đè khóa DKIM hiện có",
|
||||
"dkim_private_key": "Khóa riêng tư",
|
||||
"dkim_to": "Đến",
|
||||
"dkim_to_title": "Tên miền đích - sẽ bị ghi đè",
|
||||
"domain": "Tên miền",
|
||||
"domain_admin": "Quản trị viên tên miền",
|
||||
"domain_admins": "Các quản trị viên tên miền",
|
||||
"domainadmin_quicklink": "Ẩn liên kết nhanh đến trang đăng nhập quản trị viên tên miền",
|
||||
"domain_s": "Tên miền/s",
|
||||
"duplicate": "Nhân bản",
|
||||
"duplicate_dkim": "Nhân bản bản ghi DKIM",
|
||||
"edit": "Chỉnh sửa",
|
||||
"empty": "Không có kết quả",
|
||||
"excludes": "Loại trừ những người nhận này",
|
||||
"f2b_ban_time": "Thời gian cấm (giây)",
|
||||
"f2b_ban_time_increment": "Thời gian cấm tăng dần với mỗi lần cấm",
|
||||
"f2b_blacklist": "Mạng/máy chủ bị từ chối",
|
||||
"f2b_filter": "Bộ lọc biểu thức chính quy",
|
||||
"f2b_list_info": "Một máy chủ hoặc mạng bị từ chối sẽ luôn có quyền ưu tiên cao hơn một thực thể trong danh sách cho phép. <b>Cập nhật danh sách sẽ mất vài giây để được áp dụng.</b>",
|
||||
"f2b_manage_external": "Quản lý Fail2Ban từ bên ngoài",
|
||||
"f2b_manage_external_info": "Fail2ban sẽ vẫn duy trì danh sách cấm, nhưng sẽ không chủ động đặt quy tắc để chặn lưu lượng. Sử dụng danh sách cấm được tạo bên dưới để chặn lưu lượng từ bên ngoài.",
|
||||
"f2b_max_attempts": "Số lần thử tối đa",
|
||||
"f2b_max_ban_time": "Thời gian cấm tối đa (giây)",
|
||||
"f2b_netban_ipv4": "Kích thước mạng con IPv4 để áp dụng lệnh cấm (8-32)",
|
||||
"f2b_netban_ipv6": "Kích thước mạng con IPv6 để áp dụng lệnh cấm (8-128",
|
||||
"f2b_parameters": "Tham số Fail2ban",
|
||||
"f2b_regex_info": "Nhật ký được xem xét: SOGo, Postfix, Dovecot, PHP-FPM.",
|
||||
"f2b_retry_window": "Cửa sổ thử lại (giây) cho số lần thử tối đa",
|
||||
"f2b_whitelist": "Mạng/máy chủ được cho phép",
|
||||
"filter": "Bộ lọc",
|
||||
"filter_table": "Bảng bộ lọc",
|
||||
"force_sso_text": "Nếu nhà cung cấp OIDC bên ngoài được cấu hình, tùy chọn này sẽ ẩn biểu mẫu đăng nhập mailcow mặc định và chỉ hiển thị nút Đăng nhập một lần",
|
||||
"force_sso": "Vô hiệu hóa đăng nhập mailcow và chỉ hiển thị Đăng nhập một lần",
|
||||
"forwarding_hosts": "Máy chủ chuyển tiếp",
|
||||
"forwarding_hosts_add_hint": "Bạn có thể chỉ định địa chỉ IPv4/IPv6, mạng theo ký hiệu CIDR, tên máy chủ (sẽ được phân giải thành địa chỉ IP), hoặc tên miền (sẽ được phân giải thành địa chỉ IP bằng cách truy vấn bản ghi SPF hoặc, nếu không có, bản ghi MX).",
|
||||
"forwarding_hosts_hint": "Tin nhắn đến được chấp nhận vô điều kiện từ bất kỳ máy chủ nào được liệt kê ở đây. Các máy chủ này sau đó không bị kiểm tra với DNSBL hoặc danh sách xám. Thư rác nhận từ chúng không bao giờ bị từ chối, nhưng có thể được chuyển vào thư mục Rác. Công dụng phổ biến nhất cho việc này là chỉ định các máy chủ thư mà bạn đã thiết lập quy tắc chuyển tiếp email đến cho máy chủ mailcow của bạn.",
|
||||
"from": "Từ",
|
||||
"generate": "tạo",
|
||||
"guid": "GUID - ID phiên bản duy nhất",
|
||||
"guid_and_license": "GUID & Giấy phép",
|
||||
"hash_remove_info": "Xóa một giá trị băm giới hạn tốc độ (nếu vẫn tồn tại) sẽ đặt lại hoàn toàn bộ đếm của nó.<br\n Mỗi giá trị băm được chỉ định bằng một màu riêng biệt.",
|
||||
"help_text": "Ghi đè văn bản trợ giúp bên dưới mặt nạ đăng nhập (cho phép HTML)",
|
||||
"host": "Máy chủ",
|
||||
"html": "HTML",
|
||||
"iam": "Nhà cung cấp danh tính",
|
||||
"iam_attribute_field": "Trường thuộc tính",
|
||||
"iam_authorize_url": "Điểm cuối ủy quyền",
|
||||
"iam_auth_flow": "Luồng xác thực",
|
||||
"iam_auth_flow_info": "Ngoài Luồng mã ủy quyền (Luồng chuẩn trong Keycloak), được sử dụng cho đăng nhập một lần, mailcow còn hỗ trợ Luồng xác thực với thông tin xác thực trực tiếp. Luồng mật khẩu thư cố gắng xác thực thông tin người dùng bằng cách sử dụng API REST quản trị Keycloak. mailcow lấy mật khẩu đã băm từ thuộc tính <code>mailcow_password</code>, được ánh xạ trong Keycloak.",
|
||||
"iam_basedn": "DN cơ sở",
|
||||
"iam_client_id": "ID khách hàng",
|
||||
"iam_client_secret": "Bí mật khách hàng",
|
||||
"iam_client_scopes": "Phạm vi khách hàng",
|
||||
"iam_default_template": "Mẫu mặc định",
|
||||
"iam_default_template_description": "Nếu không có mẫu nào được gán cho người dùng, mẫu mặc định sẽ được sử dụng để tạo hộp thư, nhưng không dùng để cập nhật hộp thư.",
|
||||
"iam_description": "Cấu hình Nhà cung cấp bên ngoài cho Xác thực<br>Hộp thư của người dùng sẽ được tự động tạo khi họ đăng nhập lần đầu, với điều kiện ánh xạ thuộc tính đã được thiết lập.",
|
||||
"iam_extra_permission": "Để các cài đặt sau hoạt động, ứng dụng khách mailcow trong Keycloak cần một <code>Tài khoản dịch vụ</code> và quyền <code>xem người dùng</code>.",
|
||||
"iam_host": "Máy chủ",
|
||||
"iam_host_info": "Nhập một hoặc nhiều máy chủ LDAP, phân cách bằng dấu phẩy.",
|
||||
"iam_import_users": "Nhập người dùng",
|
||||
"iam_login_provisioning": "Tự động tạo người dùng khi đăng nhập",
|
||||
"iam_mapping": "Ánh xạ thuộc tính",
|
||||
"iam_bindpass": "Mật khẩu ràng buộc",
|
||||
"iam_periodic_full_sync": "Đồng bộ hóa đầy đủ định kỳ",
|
||||
"iam_port": "Cổng",
|
||||
"iam_realm": "Vùng",
|
||||
"iam_redirect_url": "URL chuyển hướng",
|
||||
"iam_rest_flow": "Luồng mật khẩu thư",
|
||||
"iam_server_url": "URL máy chủ",
|
||||
"iam_sso": "Đăng nhập một lần",
|
||||
"iam_sync_interval": "Khoảng thời gian đồng bộ / nhập (phút)",
|
||||
"iam_test_connection": "Kiểm tra kết nối",
|
||||
"iam_token_url": "Điểm cuối token",
|
||||
"iam_userinfo_url": "Điểm cuối thông tin người dùng",
|
||||
"iam_username_field": "Trường tên người dùng",
|
||||
"iam_binddn": "DN ràng buộc",
|
||||
"iam_use_ssl": "Sử dụng SSL",
|
||||
"iam_use_ssl_info": "Nếu bật SSL và cổng được đặt là 389, nó sẽ tự động được ghi đè để sử dụng cổng 636.",
|
||||
"iam_use_tls": "Sử dụng StartTLS",
|
||||
"iam_use_tls_info": "Nếu bật TLS, bạn phải sử dụng cổng mặc định cho máy chủ LDAP của bạn (389). Không thể sử dụng các cổng SSL.",
|
||||
"iam_version": "Phiên bản",
|
||||
"ignore_ssl_error": "Bỏ qua lỗi SSL",
|
||||
"import": "Nhập",
|
||||
"import_private_key": "Nhập khóa riêng tư",
|
||||
"in_use_by": "Đang được sử dụng bởi",
|
||||
"inactive": "Không hoạt động",
|
||||
"include_exclude": "Bao gồm/Loại trừ",
|
||||
"include_exclude_info": "Mặc định - khi không có lựa chọn - <b>tất cả hộp thư</b> được đề cập",
|
||||
"includes": "Bao gồm những người nhận này",
|
||||
"ip_check": "Kiểm tra IP",
|
||||
"ip_check_disabled": "Kiểm tra IP đã bị vô hiệu hóa. Bạn có thể bật nó trong<br> <strong>Hệ thống > Cấu hình > Tùy chọn > Tùy chỉnh</strong>",
|
||||
"ip_check_opt_in": "Chọn tham gia sử dụng dịch vụ bên thứ ba <strong>ipv4.mailcow.email</strong> và <strong>ipv6.mailcow.email</strong> để phân giải địa chỉ IP bên ngoài.",
|
||||
"is_mx_based": "Dựa trên MX",
|
||||
"last_applied": "Áp dụng lần cuối",
|
||||
"license_info": "Giấy phép không bắt buộc nhưng giúp phát triển thêm.<br><a href=\"https://www.servercow.de/mailcow?lang=en#sal\" target=\"_blank\" alt=\"Đặt hàng SAL\">Đăng ký GUID của bạn tại đây</a> hoặc <a href=\"https://www.servercow.de/mailcow?lang=en#support\" target=\"_blank\" alt=\"Đặt hàng hỗ trợ\">mua hỗ trợ cho cài đặt mailcow của bạn.</a>",
|
||||
"link": "Liên kết",
|
||||
"loading": "Vui lòng đợi...",
|
||||
"login_time": "Thời gian đăng nhập",
|
||||
"logo_info": "Hình ảnh của bạn sẽ được điều chỉnh theo chiều cao 40px cho thanh điều hướng trên cùng và chiều rộng tối đa 250px cho trang bắt đầu. Khuyến nghị sử dụng đồ họa có thể thay đổi kích thước.",
|
||||
"lookup_mx": "Đích đến là một biểu thức chính quy để so khớp với tên MX (<code>.*.google.com</code> để định tuyến tất cả thư nhắm đến MX kết thúc bằng google.com qua bước nhảy này)",
|
||||
"main_name": "Tên \"Giao diện mailcow\"",
|
||||
"merged_vars_hint": "Các hàng bị mờ đã được hợp nhất từ <code>vars.(local.)inc.php</code> và không thể sửa đổi.",
|
||||
"message": "Tin nhắn",
|
||||
"message_size": "Kích thước tin nhắn",
|
||||
"nexthop": "Bước nhảy tiếp theo",
|
||||
"needs_restart": "cần khởi động lại",
|
||||
"no_active_bans": "Không có lệnh cấm đang hoạt động",
|
||||
"no_new_rows": "Không có hàng nào khác",
|
||||
"no_record": "Không có bản ghi",
|
||||
"oauth2_apps": "Ứng dụng OAuth2",
|
||||
"oauth2_add_client": "Thêm ứng dụng khách OAuth2",
|
||||
"oauth2_client_id": "ID ứng dụng khách",
|
||||
"oauth2_client_secret": "Bí mật ứng dụng khách",
|
||||
"oauth2_info": "Triển khai OAuth2 hỗ trợ loại cấp phép \"Mã ủy quyền\" và cấp token làm mới.<br>\nMáy chủ cũng tự động cấp token làm mới mới sau khi token làm mới đã được sử dụng.<br><br>\n• Phạm vi mặc định là <i>profile</i>. Chỉ người dùng hộp thư mới có thể được xác thực qua OAuth2. Nếu tham số phạm vi bị bỏ qua, nó sẽ trở về <i>profile</i>.<br>\n• Tham số <i>state</i> phải được gửi bởi ứng dụng khách như một phần của yêu cầu ủy quyền.<br><br>\nĐường dẫn cho các yêu cầu đến API OAuth2: <br>\n<ul>\n<li>Điểm cuối ủy quyền: <code>/oauth/authorize</code></li>\n<li>Điểm cuối token: <code>/oauth/token</code></li>\n<li>Trang tài nguyên: <code>/oauth/profile</code></li>\n</ul>\nTạo lại bí mật ứng dụng khách sẽ không làm hết hạn các mã ủy quyền hiện có, nhưng chúng sẽ không thể làm mới token của họ.<br><br>\nThu hồi token ứng dụng khách sẽ gây ra việc chấm dứt ngay lập tức tất cả các phiên hoạt động. Tất cả ứng dụng khách cần xác thực lại.",
|
||||
"oauth2_redirect_uri": "URI chuyển hướng",
|
||||
"oauth2_renew_secret": "Tạo bí mật ứng dụng khách mới",
|
||||
"oauth2_revoke_tokens": "Thu hồi tất cả token ứng dụng khách",
|
||||
"optional": "tùy chọn",
|
||||
"options": "Các tùy chọn",
|
||||
"password": "Mật khẩu",
|
||||
"password_length": "Độ dài mật khẩu",
|
||||
"password_policy": "Chính sách mật khẩu",
|
||||
"password_policy_chars": "Phải chứa ít nhất một ký tự chữ cái",
|
||||
"password_policy_length": "Độ dài mật khẩu tối thiểu là %d",
|
||||
"password_policy_lowerupper": "Phải chứa ký tự viết thường và viết hoa",
|
||||
"password_policy_numbers": "Phải chứa ít nhất một số",
|
||||
"password_policy_special_chars": "Phải chứa ký tự đặc biệt",
|
||||
"password_repeat": "Xác nhận mật khẩu (nhập lại)",
|
||||
"password_reset_info": "Nếu không cung cấp email khôi phục, chức năng này không thể được sử dụng.",
|
||||
"password_reset_settings": "Cài đặt khôi phục mật khẩu",
|
||||
"password_reset_tmpl_html": "Mẫu HTML",
|
||||
"password_reset_tmpl_text": "Mẫu văn bản",
|
||||
"password_settings": "Cài đặt mật khẩu",
|
||||
"priority": "Độ ưu tiên",
|
||||
"private_key": "Khóa riêng tư",
|
||||
"quarantine": "Cách ly",
|
||||
"quarantine_bcc": "Gửi một bản sao của tất cả thông báo (BCC) đến người nhận này:<br><small>Để trống để vô hiệu hóa. <b>Thư không ký, không kiểm tra. Chỉ nên gửi nội bộ.</b></small>",
|
||||
"quarantine_exclude_domains": "Loại trừ tên miền và tên miền bí danh",
|
||||
"quarantine_max_age": "Tuổi tối đa theo ngày<br><small>Giá trị phải bằng hoặc lớn hơn 1 ngày.</small>",
|
||||
"quarantine_max_score": "Bỏ thông báo nếu điểm thư rác của một thư cao hơn giá trị này:<br><small>Mặc định là 9999.0</small>",
|
||||
"quarantine_max_size": "Kích thước tối đa tính bằng MiB (các phần tử lớn hơn sẽ bị loại bỏ):<br><small>0 <b>không</b> có nghĩa là không giới hạn.</small>",
|
||||
"quarantine_notification_html": "Mẫu email thông báo:<br><small>Để trống để khôi phục mẫu mặc định.</small>",
|
||||
"quarantine_notification_sender": "Người gửi email thông báo",
|
||||
"quarantine_notification_subject": "Chủ đề email thông báo",
|
||||
"quarantine_redirect": "<b>Chuyển hướng tất cả thông báo</b> đến người nhận này:<br><small>Để trống để vô hiệu hóa. <b>Thư không ký, không kiểm tra. Chỉ nên gửi nội bộ.</b></small>",
|
||||
"quarantine_release_format": "Định dạng của các mục được phát hành",
|
||||
"quarantine_release_format_att": "Dưới dạng tệp đính kèm",
|
||||
"quarantine_release_format_raw": "Bản gốc không sửa đổi",
|
||||
"quarantine_retention_size": "ưu giữ cho mỗi hộp thư:<br><small>0 có nghĩa là <b>không hoạt động</b>.</small>",
|
||||
"quicklink_text": "Hiển thị hoặc ẩn liên kết nhanh đến các trang đăng nhập khác dưới biểu mẫu đăng nhập",
|
||||
"quota_notification_html": "Mẫu email thông báo:<br><small>Để trống để khôi phục mẫu mặc định.</small>",
|
||||
"quota_notification_sender": "Người gửi email thông báo",
|
||||
"quota_notification_subject": "Chủ đề email thông báo",
|
||||
"quota_notifications": "Thông báo hạn ngạch",
|
||||
"quota_notifications_info": "Thông báo hạn ngạch được gửi cho người dùng một lần khi vượt quá 80% và một lần khi vượt quá 95% mức sử dụng.",
|
||||
"quota_notifications_vars": "{{percent}} bằng hạn ngạch hiện tại của người dùng<br>{{username}} là tên hộp thư",
|
||||
"queue_unban": "bỏ cấm",
|
||||
"r_active": "Hạn chế đang hoạt động",
|
||||
"r_inactive": "Hạn chế không hoạt động",
|
||||
"r_info": "Các phần tử bị mờ/vô hiệu hóa trong danh sách hạn chế đang hoạt động không được mailcow công nhận là hạn chế hợp lệ và không thể di chuyển. Các hạn chế không xác định sẽ vẫn được đặt theo thứ tự xuất hiện. <br>Bạn có thể thêm phần tử mới trong <code>inc/vars.local.inc.php</code> để có thể bật/tắt chúng.",
|
||||
"rate_name": "Tên tỷ lệ",
|
||||
"recipients": "Người nhận",
|
||||
"refresh": "Làm mới",
|
||||
"regen_api_key": "Tạo lại khóa API",
|
||||
"regex_maps": "Ánh xạ biểu thức chính quy",
|
||||
"relay_from": "Địa chỉ \"Từ:\"",
|
||||
"relay_rcpt": "Địa chỉ \"Đến:\"",
|
||||
"relay_run": "Chạy thử",
|
||||
"relayhosts": "Vận chuyển phụ thuộc người gửi",
|
||||
"relayhosts_hint": "Xác định vận chuyển phụ thuộc người gửi để có thể chọn chúng trong hộp thoại cấu hình tên miền.<br>\nDịch vụ vận chuyển luôn là \"smtp:\" và do đó sẽ thử TLS khi được cung cấp. TLS được bọc (SMTPS) không được hỗ trợ. Cài đặt chính sách TLS gửi đi riêng của người dùng được tính đến.<br>\nẢnh hưởng đến các tên miền được chọn bao gồm cả tên miền bí danh.",
|
||||
"remove": "Xóa",
|
||||
"remove_row": "Xóa hàng",
|
||||
"reset_default": "Đặt lại về mặc định",
|
||||
"reset_limit": "Xóa giá trị băm",
|
||||
"reset_password_vars": "<code>{{link}}</code> Liên kết đặt lại mật khẩu đã tạo<br><code>{{username}}</code> Tên hộp thư của người dùng yêu cầu đặt lại mật khẩu<br><code>{{username2}}</code> Tên hộp thư khôi phục<br><code>{{date}}</code> Ngày yêu cầu đặt lại mật khẩu được thực hiện<br><code>{{token_lifetime}}</code> Thời gian sống của token tính bằng phút<br><code>{{hostname}}</code> Tên máy chủ mailcow",
|
||||
"restore_template": "Để trống để khôi phục mẫu mặc định.",
|
||||
"routing": "Định tuyến",
|
||||
"rsetting_add_rule": "Thêm quy tắc",
|
||||
"rsetting_content": "Nội dung quy tắc",
|
||||
"rsetting_desc": "Mô tả ngắn",
|
||||
"rsetting_no_selection": "Vui lòng chọn một quy tắc",
|
||||
"rsetting_none": "Không có quy tắc nào",
|
||||
"rsettings_insert_preset": "Chèn mẫu ví dụ \"%s\"",
|
||||
"rsettings_preset_1": "Vô hiệu hóa tất cả trừ DKIM và giới hạn tốc độ cho người dùng đã xác thực",
|
||||
"rsettings_preset_2": "Quản trị viên bưu điện muốn thư rác",
|
||||
"rsettings_preset_3": "Chỉ cho phép người gửi cụ thể cho một hộp thư (ví dụ: sử dụng như hộp thư nội bộ)",
|
||||
"rsettings_preset_4": "Vô hiệu hóa Rspamd cho một tên miền",
|
||||
"rspamd_com_settings": "Tên cài đặt sẽ được tự động tạo, vui lòng xem các mẫu ví dụ bên dưới. Để biết thêm chi tiết, xem <a href=\"https://rspamd.com/doc/configuration/settings.html#settings-structure\" target=\"_blank\">Tài liệu Rspamd</a>",
|
||||
"rspamd_global_filters": "Ánh xạ bộ lọc toàn cục",
|
||||
"rspamd_global_filters_agree": "Tôi sẽ cẩn thận!",
|
||||
"rspamd_global_filters_info": "Ánh xạ bộ lọc toàn cục chứa các loại danh sách từ chối và cho phép toàn cục khác nhau.",
|
||||
"rspamd_global_filters_regex": "Tên của chúng giải thích mục đích sử dụng. Tất cả nội dung phải chứa biểu thức chính quy hợp lệ theo định dạng \"/mẫu/tùy chọn\" (ví dụ: <code>/.+@domain.tld/i</code>).<br>\nMặc dù kiểm tra cơ bản được thực hiện trên mỗi dòng của regex, chức năng của Rspamd có thể bị hỏng nếu nó không đọc được cú pháp chính xác.<br>\nRspamd sẽ cố gắng đọc nội dung ánh xạ khi thay đổi. Nếu bạn gặp vấn đề, <a href=\"\" data-toggle=\"modal\" data-container=\"rspamd-mailcow\" data-target=\"#RestartContainer\">khởi động lại Rspamd</a> để bắt buộc tải lại ánh xạ.<br>Các phần tử trong danh sách từ chối được loại trừ khỏi cách ly.",
|
||||
"rspamd_settings_map": "Ánh xạ cài đặt Rspamd",
|
||||
"sal_level": "Cấp độ Moo",
|
||||
"save": "Lưu thay đổi",
|
||||
"search_domain_da": "Tìm kiếm tên miền",
|
||||
"send": "Gửi",
|
||||
"sender": "Người gửi",
|
||||
"service": "Dịch vụ",
|
||||
"service_id": "ID Dịch vụ",
|
||||
"source": "Nguồn",
|
||||
"spamfilter": "Bộ lọc thư rác",
|
||||
"subject": "Chủ đề",
|
||||
"success": "Thành công",
|
||||
"sys_mails": "Thư hệ thống",
|
||||
"task": "Nhiệm vụ",
|
||||
"text": "Văn bản",
|
||||
"time": "Thời gian",
|
||||
"title": "Tiêu đề",
|
||||
"title_name": "Tiêu đề trang web \"Giao diện mailcow\"",
|
||||
"to_top": "Về đầu trang",
|
||||
"transport_dest_format": "Regex hoặc cú pháp: example.org, .example.org, *, box@example.org (nhiều giá trị có thể được phân cách bằng dấu phẩy)",
|
||||
"transport_maps": "Ánh xạ vận chuyển",
|
||||
"transport_test_rcpt_info": "• Sử dụng null@hosted.mailcow.de để kiểm tra chuyển tiếp đến đích nước ngoài.",
|
||||
"transports_hint": "• Một mục ánh xạ vận chuyển <b>ghi đè</b> một ánh xạ vận chuyển phụ thuộc người gửi</b>.<br>\n• Vận chuyển dựa trên MX được ưu tiên sử dụng.<br>\n• Cài đặt chính sách TLS gửi đi cho từng người dùng bị bỏ qua và chỉ có thể được thực thi bởi các mục ánh xạ chính sách TLS.<br>\n• Dịch vụ vận chuyển cho các vận chuyển đã định nghĩa luôn là \"smtp:\" và do đó sẽ thử TLS khi được cung cấp. TLS được bọc (SMTPS) không được hỗ trợ.<br>\n• Địa chỉ khớp với \"/localhost$/\" sẽ luôn được vận chuyển qua \"local:\", do đó đích \"*\" sẽ không áp dụng cho những địa chỉ đó.<br>\n• Để xác định thông tin xác thực cho bước nhảy tiếp theo ví dụ \"[host]:25\", Postfix <b>luôn</b> truy vấn \"host\" trước khi tìm kiếm \"[host]:25\". Hành vi này khiến không thể sử dụng \"host\" và \"[host]:25\" cùng một lúc.",
|
||||
"ui_footer": "Chân trang (cho phép HTML)",
|
||||
"ui_header_announcement": "Thông báo",
|
||||
"ui_header_announcement_active": "Đặt thông báo hoạt động",
|
||||
"ui_header_announcement_content": "Văn bản (cho phép HTML)",
|
||||
"ui_header_announcement_help": "Thông báo hiển thị cho tất cả người dùng đã đăng nhập và trên màn hình đăng nhập của giao diện người dùng.",
|
||||
"ui_header_announcement_select": "Chọn loại thông báo",
|
||||
"ui_header_announcement_type": "Loại",
|
||||
"ui_header_announcement_type_danger": "Rất quan trọng",
|
||||
"ui_header_announcement_type_info": "Thông tin",
|
||||
"ui_header_announcement_type_warning": "Quan trọng",
|
||||
"ui_texts": "Nhãn và văn bản giao diện người dùng",
|
||||
"unban_pending": "đang chờ bỏ cấm",
|
||||
"unchanged_if_empty": "Nếu không thay đổi hãy để trống",
|
||||
"upload": "Tải lên",
|
||||
"username": "Tên người dùng",
|
||||
"user_link": "Liên kết người dùng",
|
||||
"user_quicklink": "Ẩn liên kết nhanh đến trang đăng nhập người dùng",
|
||||
"validate_license_now": "Xác thực GUID với máy chủ giấy phép",
|
||||
"verify": "Xác minh",
|
||||
"yes": "✓"
|
||||
},
|
||||
"danger": {
|
||||
"access_denied": "Truy cập bị từ chối hoặc dữ liệu biểu mẫu không hợp lệ",
|
||||
"alias_domain_invalid": "Tên miền bí danh %s không hợp lệ",
|
||||
"alias_empty": "Địa chỉ bí danh không được để trống",
|
||||
"alias_goto_identical": "Địa chỉ bí danh và địa chỉ chuyển tiếp không được giống nhau",
|
||||
"alias_invalid": "Địa chỉ bí danh %s không hợp lệ",
|
||||
"aliasd_targetd_identical": "Tên miền bí danh không được giống với tên miền đích: %s",
|
||||
"aliases_in_use": "Số bí danh tối đa phải lớn hơn hoặc bằng %d",
|
||||
"app_name_empty": "Tên ứng dụng không được để trống",
|
||||
"app_passwd_id_invalid": "ID mật khẩu ứng dụng %s không hợp lệ",
|
||||
"authsource_in_use": "Nhà cung cấp danh tính không thể thay đổi hoặc xóa vì hiện đang được sử dụng bởi một hoặc nhiều người dùng.",
|
||||
"bcc_empty": "Đích BCC không được để trống",
|
||||
"bcc_exists": "Một ánh xạ BCC %s đã tồn tại cho loại %s",
|
||||
"bcc_must_be_email": "Đích BCC %s không phải là địa chỉ email hợp lệ",
|
||||
"comment_too_long": "Bình luận quá dài, cho phép tối đa 160 ký tự",
|
||||
"cors_invalid_method": "Phương thức Allow-Method được chỉ định không hợp lệ",
|
||||
"cors_invalid_origin": "Allow-Origin được chỉ định không hợp lệ",
|
||||
"defquota_empty": "Hạn ngạch mặc định cho mỗi hộp thư không được là 0.",
|
||||
"demo_mode_enabled": "Chế độ Demo đang được bật",
|
||||
"description_invalid": "Mô tả tài nguyên cho %s không hợp lệ",
|
||||
"dkim_domain_or_sel_exists": "Một khóa DKIM cho \"%s\" đã tồn tại và sẽ không bị ghi đè",
|
||||
"dkim_domain_or_sel_invalid": "Tên miền hoặc bộ chọn DKIM không hợp lệ: %s",
|
||||
"domain_cannot_match_hostname": "Tên miền không thể trùng với tên máy chủ",
|
||||
"domain_exists": "Tên miền %s đã tồn tại",
|
||||
"domain_invalid": "Tên miền trống hoặc không hợp lệ",
|
||||
"domain_not_empty": "Không thể xóa tên miền %s không trống",
|
||||
"domain_not_found": "Không tìm thấy tên miền %s",
|
||||
"domain_quota_m_in_use": "Hạn ngạch tên miền phải lớn hơn hoặc bằng %s MiB",
|
||||
"extended_sender_acl_denied": "thiếu ACL để đặt địa chỉ người gửi bên ngoài",
|
||||
"extra_acl_invalid": "Địa chỉ người gửi bên ngoài \"%s\" không hợp lệ",
|
||||
"extra_acl_invalid_domain": "Người gửi bên ngoài \"%s\" sử dụng tên miền không hợp lệ",
|
||||
"fido2_verification_failed": "Xác minh FIDO2 thất bại: %s",
|
||||
"file_open_error": "Không thể mở tệp để ghi",
|
||||
"filter_type": "Loại bộ lọc không đúng",
|
||||
"from_invalid": "Người gửi không được để trống",
|
||||
"generic_server_error": "Đã xảy ra lỗi máy chủ không mong đợi. Vui lòng liên hệ quản trị viên của bạn.",
|
||||
"global_filter_write_error": "Không thể ghi tệp bộ lọc: %s",
|
||||
"global_map_invalid": "ID ánh xạ toàn cục %s không hợp lệ",
|
||||
"global_map_write_error": "Không thể ghi ID ánh xạ toàn cục %s: %s",
|
||||
"goto_empty": "Một địa chỉ bí danh phải chứa ít nhất một địa chỉ chuyển tiếp hợp lệ",
|
||||
"goto_invalid": "Địa chỉ chuyển tiếp %s không hợp lệ",
|
||||
"ham_learn_error": "Lỗi học thư hợp lệ: %s",
|
||||
"iam_test_connection": "Kết nối thất bại",
|
||||
"imagick_exception": "Lỗi: Ngoại lệ Imagick khi đọc hình ảnh",
|
||||
"img_dimensions_exceeded": "Hình ảnh vượt quá kích thước tối đa cho phép",
|
||||
"img_invalid": "Không thể xác thực tệp hình ảnh",
|
||||
"img_size_exceeded": "Hình ảnh vượt quá kích thước tệp tối đa",
|
||||
"img_tmp_missing": "Không thể xác thực tệp hình ảnh: Không tìm thấy tệp tạm thời",
|
||||
"invalid_bcc_map_type": "Loại ánh xạ BCC không hợp lệ",
|
||||
"invalid_destination": "Định dạng đích \"%s\" không hợp lệ",
|
||||
"invalid_filter_type": "Loại bộ lọc không hợp lệ",
|
||||
"invalid_host": "Máy chủ được chỉ định không hợp lệ: %s",
|
||||
"invalid_mime_type": "Kiểu MIME không hợp lệ",
|
||||
"invalid_nexthop": "Định dạng bước nhảy tiếp theo không hợp lệ",
|
||||
"invalid_nexthop_authenticated": "Bước nhảy tiếp theo tồn tại với thông tin xác thực khác, vui lòng cập nhật thông tin xác thực hiện có cho bước nhảy này trước.",
|
||||
"invalid_recipient_map_new": "Người nhận mới được chỉ định không hợp lệ: %s",
|
||||
"invalid_recipient_map_old": "Người nhận gốc được chỉ định không hợp lệ: %s",
|
||||
"invalid_reset_token": "Token đặt lại không hợp lệ",
|
||||
"ip_list_empty": "Danh sách IP được phép không được để trống",
|
||||
"is_alias": "%s đã được biết đến như một địa chỉ bí danh",
|
||||
"is_alias_or_mailbox": "%s đã được biết đến như một bí danh, một hộp thư hoặc một địa chỉ bí danh được mở rộng từ một tên miền bí danh.",
|
||||
"is_spam_alias": "%s đã được biết đến như một địa chỉ bí danh tạm thời (địa chỉ bí danh thư rác)",
|
||||
"last_key": "Không thể xóa khóa cuối cùng, vui lòng vô hiệu hóa TFA thay thế.",
|
||||
"login_failed": "Đăng nhập không thành công",
|
||||
"mailbox_defquota_exceeds_mailbox_maxquota": "Hạn ngạch mặc định vượt quá giới hạn hạn ngạch tối đa",
|
||||
"mailbox_invalid": "Tên hộp thư không hợp lệ",
|
||||
"mailbox_quota_exceeded": "Hạn ngạch vượt quá giới hạn tên miền (tối đa %d MiB)",
|
||||
"mailbox_quota_exceeds_domain_quota": "Hạn ngạch tối đa vượt quá giới hạn hạn ngạch tên miền",
|
||||
"mailbox_quota_left_exceeded": "Không đủ dung lượng còn lại (dung lượng còn lại: %d MiB)",
|
||||
"mailboxes_in_use": "Số hộp thư tối đa phải lớn hơn hoặc bằng %d",
|
||||
"malformed_username": "Tên người dùng không đúng định dạng",
|
||||
"map_content_empty": "Nội dung ánh xạ không được để trống",
|
||||
"max_age_invalid": "Tuổi tối đa %s không hợp lệ",
|
||||
"max_alias_exceeded": "Vượt quá số bí danh tối đa",
|
||||
"max_mailbox_exceeded": "Vượt quá số hộp thư tối đa (%d trên %d)",
|
||||
"max_quota_in_use": "Hạn ngạch hộp thư phải lớn hơn hoặc bằng %d MiB",
|
||||
"maxquota_empty": "Hạn ngạch tối đa cho mỗi hộp thư không được là 0.",
|
||||
"mode_invalid": "Chế độ %s không hợp lệ",
|
||||
"mx_invalid": "Bản ghi MX %s không hợp lệ",
|
||||
"mysql_error": "Lỗi MySQL: %s",
|
||||
"network_host_invalid": "Mạng hoặc máy chủ không hợp lệ: %s",
|
||||
"next_hop_interferes": "%s xung đột với bước nhảy tiếp theo %s",
|
||||
"next_hop_interferes_any": "Một bước nhảy tiếp theo hiện có xung đột với %s",
|
||||
"nginx_reload_failed": "Tải lại Nginx thất bại: %s",
|
||||
"no_user_defined": "Không có người dùng được định nghĩa",
|
||||
"object_exists": "Đối tượng %s đã tồn tại",
|
||||
"object_is_not_numeric": "Giá trị %s không phải là số",
|
||||
"password_complexity": "Mật khẩu không đáp ứng chính sách",
|
||||
"password_empty": "Mật khẩu không được để trống",
|
||||
"password_mismatch": "Mật khẩu xác nhận không khớp",
|
||||
"password_reset_invalid_user": "Không tìm thấy hộp thư hoặc không có email khôi phục được thiết lập",
|
||||
"password_reset_na": "Tính năng khôi phục mật khẩu hiện không khả dụng. Vui lòng liên hệ quản trị viên của bạn.",
|
||||
"policy_list_from_exists": "Một bản ghi với tên đã cho tồn tại",
|
||||
"policy_list_from_invalid": "Bản ghi có định dạng không hợp lệ",
|
||||
"private_key_error": "Lỗi khóa riêng tư: %s",
|
||||
"pushover_credentials_missing": "Thiếu token hoặc khóa Pushover",
|
||||
"pushover_key": "Khóa Pushover có định dạng không đúng",
|
||||
"pushover_token": "Token Pushover có định dạng không đúng",
|
||||
"quota_not_0_not_numeric": "Hạn ngạch phải là số và >= 0",
|
||||
"recipient_map_entry_exists": "Mục ánh xạ người nhận \"%s\" đã tồn tại",
|
||||
"recovery_email_failed": "Không thể gửi email khôi phục. Vui lòng liên hệ quản trị viên của bạn.",
|
||||
"redis_error": "Lỗi Redis: %s",
|
||||
"relayhost_invalid": "Mục ánh xạ %s không hợp lệ",
|
||||
"release_send_failed": "Không thể phát hành thư: %s",
|
||||
"required_data_missing": "Thiếu dữ liệu bắt buộc %s",
|
||||
"reset_f2b_regex": "Không thể đặt lại bộ lọc biểu thức chính quy kịp thời, vui lòng thử lại hoặc đợi thêm vài giây và tải lại trang web.",
|
||||
"reset_token_limit_exceeded": "Đã vượt quá giới hạn token đặt lại. Vui lòng thử lại sau.",
|
||||
"resource_invalid": "Tên tài nguyên %s không hợp lệ",
|
||||
"rl_timeframe": "Khung thời gian giới hạn tốc độ không chính xác",
|
||||
"rspamd_ui_pw_length": "Mật khẩu giao diện Rspamd phải có ít nhất 6 ký tự",
|
||||
"script_empty": "Tập lệnh không được để trống",
|
||||
"sender_acl_invalid": "Giá trị ACL người gửi %s không hợp lệ",
|
||||
"set_acl_failed": "Không thể thiết lập ACL",
|
||||
"settings_map_invalid": "ID ánh xạ cài đặt %s không hợp lệ",
|
||||
"sieve_error": "Lỗi phân tích cú pháp Sieve: %s",
|
||||
"spam_learn_error": "Lỗi học thư rác: %s",
|
||||
"subject_empty": "Tiêu đề không được để trống",
|
||||
"target_domain_invalid": "Tên miền đích %s không hợp lệ",
|
||||
"targetd_not_found": "Không tìm thấy tên miền đích %s",
|
||||
"targetd_relay_domain": "Tên miền đích %s là tên miền chuyển tiếp",
|
||||
"template_exists": "Mẫu %s đã tồn tại",
|
||||
"template_id_invalid": "ID mẫu %s không hợp lệ",
|
||||
"template_name_invalid": "Tên mẫu không hợp lệ",
|
||||
"temp_error": "Lỗi tạm thời",
|
||||
"text_empty": "Văn bản không được để trống",
|
||||
"tfa_token_invalid": "Token xác thực hai yếu tố không hợp lệ",
|
||||
"tls_policy_map_dest_invalid": "Đích chính sách TLS không hợp lệ",
|
||||
"tls_policy_map_entry_exists": "Mục ánh xạ chính sách TLS \"%s\" đã tồn tại",
|
||||
"tls_policy_map_parameter_invalid": "Tham số chính sách không hợp lệ",
|
||||
"to_invalid": "Người nhận không được để trống",
|
||||
"totp_verification_failed": "Xác thực TOTP thất bại",
|
||||
"transport_dest_exists": "Đích vận chuyển \"%s\" đã tồn tại",
|
||||
"webauthn_verification_failed": "Xác thực WebAuthn thất bại: %s",
|
||||
"webauthn_authenticator_failed": "Không tìm thấy trình xác thực đã chọn",
|
||||
"webauthn_publickey_failed": "Không có khóa công khai nào được lưu cho trình xác thực đã chọn",
|
||||
"webauthn_username_failed": "Trình xác thực đã chọn thuộc về tài khoản khác",
|
||||
"unknown": "Đã xảy ra lỗi không xác định",
|
||||
"unknown_tfa_method": "Phương thức xác thực hai yếu tố không xác định",
|
||||
"unlimited_quota_acl": "Hạn ngạch không giới hạn bị cấm bởi ACL",
|
||||
"username_invalid": "Tên người dùng %s không thể sử dụng được",
|
||||
"validity_missing": "Vui lòng gán thời hạn hiệu lực",
|
||||
"value_missing": "Vui lòng cung cấp tất cả các giá trị",
|
||||
"version_invalid": "Phiên bản %s không hợp lệ",
|
||||
"yotp_verification_failed": "Xác thực Yubico OTP thất bại: %s"
|
||||
},
|
||||
"datatables": {
|
||||
"collapse_all": "Thu gọn tất cả",
|
||||
"emptyTable": "Không có dữ liệu trong bảng",
|
||||
"expand_all": "Mở rộng tất cả",
|
||||
"info": "Hiển thị từ _START_ đến _END_ của _TOTAL_ mục",
|
||||
"infoEmpty": "Hiển thị 0 đến 0 của 0 mục",
|
||||
"infoFiltered": "(được lọc từ tổng số _MAX_ mục)",
|
||||
"lengthMenu": "Hiển thị _MENU_ mục",
|
||||
"loadingRecords": "Đang tải...",
|
||||
"processing": "Vui lòng đợi...",
|
||||
"search": "Tìm kiếm:",
|
||||
"zeroRecords": "Không tìm thấy bản ghi phù hợp",
|
||||
"paginate": {
|
||||
"first": "Đầu",
|
||||
"last": "Cuối",
|
||||
"next": "Tiếp",
|
||||
"previous": "Trước"
|
||||
},
|
||||
"aria": {
|
||||
"sortAscending": ": kích hoạt để sắp xếp cột tăng dần",
|
||||
"sortDescending": ": kích hoạt để sắp xếp cột giảm dần"
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
"architecture": "Kiến trúc",
|
||||
"chart_this_server": "Biểu đồ (máy chủ này)",
|
||||
"containers_info": "Thông tin container",
|
||||
"container_running": "Đang chạy",
|
||||
"container_disabled": "Container đã dừng hoặc bị vô hiệu hóa",
|
||||
"container_stopped": "Đã dừng",
|
||||
"cores": "Nhân CPU",
|
||||
"current_time": "Thời gian hệ thống",
|
||||
"disk_usage": "Sử dụng ổ đĩa",
|
||||
"docs": "Tài liệu",
|
||||
"error_show_ip": "Không thể phân giải địa chỉ IP công khai",
|
||||
"external_logs": "Nhật ký ngoài",
|
||||
"history_all_servers": "Lịch sử (tất cả máy chủ)",
|
||||
"in_memory_logs": "Nhật ký trong bộ nhớ",
|
||||
"last_modified": "Sửa đổi lần cuối",
|
||||
"log_info": "<p>mailcow <b>nhật ký trong bộ nhớ</b> được thu thập trong danh sách Redis và được cắt giảm xuống LOG_LINES (%d) mỗi phút để giảm tải.\n<br>Nhật ký trong bộ nhớ không nhằm mục đích lưu trữ lâu dài. Tất cả các ứng dụng ghi nhật ký trong bộ nhớ cũng ghi vào Docker daemon và do đó vào trình điều khiển ghi nhật ký mặc định.r\n<br>Loại nhật ký trong bộ nhớ nên được sử dụng để gỡ lỗi các vấn đề nhỏ với các container.</p>\n<p><b>Nhật ký bên ngoài</b> được thu thập thông qua API của ứng dụng đã cho.</p>\n<p><b>Nhật ký tĩnh</b> chủ yếu là nhật ký hoạt động, không được ghi vào Dockerd nhưng vẫn cần được lưu trữ lâu dài (ngoại trừ nhật ký API).</p>",
|
||||
"login_time": "Thời gian",
|
||||
"logs": "Nhật ký",
|
||||
"memory": "Bộ nhớ",
|
||||
"online_users": "Người dùng trực tuyến",
|
||||
"restart_container": "Khởi động lại",
|
||||
"service": "Dịch vụ",
|
||||
"show_ip": "Hiển thị IP công khai",
|
||||
"size": "Kích thước",
|
||||
"started_at": "Bắt đầu lúc",
|
||||
"started_on": "Bắt đầu vào",
|
||||
"static_logs": "Nhật ký tĩnh",
|
||||
"success": "Thành công",
|
||||
"system_containers": "Hệ thống & Container",
|
||||
"timezone": "Múi giờ",
|
||||
"uptime": "Thời gian hoạt động",
|
||||
"update_available": "Có bản cập nhật mới",
|
||||
"no_update_available": "Hệ thống đang ở phiên bản mới nhất",
|
||||
"update_failed": "Không thể kiểm tra cập nhật",
|
||||
"username": "Tên người dùng",
|
||||
"wip": "Đang trong quá trình phát triển"
|
||||
},
|
||||
"diagnostics": {
|
||||
"cname_from_a": "Giá trị được lấy từ bản ghi A/AAAA. Điều này được hỗ trợ miễn là bản ghi trỏ đến tài nguyên chính xác.",
|
||||
"dns_records": "Bản ghi DNS",
|
||||
"dns_records_24hours": "Xin lưu ý rằng các thay đổi được thực hiện đối với DNS có thể mất tới 24 giờ để phản ánh chính xác trạng thái hiện tại của chúng trên trang này. Trang này nhằm giúp bạn dễ dàng xem cách cấu hình bản ghi DNS và kiểm tra xem tất cả bản ghi của bạn có được lưu trữ chính xác trong DNS hay không.",
|
||||
"dns_records_data": "Dữ liệu chính xác",
|
||||
"dns_records_docs": "Vui lòng tham khảo thêm <a target=\"_blank\" href=\"https://docs.mailcow.email/getstarted/prerequisite-dns\">tài liệu hướng dẫn</a>.",
|
||||
"dns_records_name": "Tên",
|
||||
"dns_records_status": "Trạng thái hiện tại",
|
||||
"dns_records_type": "Loại",
|
||||
"optional": "Bản ghi này là tùy chọn."
|
||||
},
|
||||
"edit": {
|
||||
"acl": "ACL (Quyền hạn)",
|
||||
"active": "Hoạt động",
|
||||
"admin": "Chỉnh sửa quản trị viên",
|
||||
"advanced_settings": "Cài đặt nâng cao",
|
||||
"alias": "Chỉnh sửa bí danh",
|
||||
"allow_from_smtp": "Chỉ cho phép các IP sau sử dụng <b>SMTP</b>",
|
||||
"allow_from_smtp_info": "Để trống để cho phép tất cả người gửi.<br>Địa chỉ và mạng IPv4/IPv6.",
|
||||
"allowed_protocols": "Các giao thức được phép truy cập trực tiếp của người dùng (không ảnh hưởng đến giao thức mật khẩu ứng dụng)",
|
||||
"app_name": "Tên ứng dụng",
|
||||
"app_passwd": "Mật khẩu ứng dụng",
|
||||
"app_passwd_protocols": "Các giao thức được phép cho mật khẩu ứng dụng",
|
||||
"automap": "Thử tự động ánh xạ thư mục (\"Mục đã gửi\", \"Đã gửi\" => \"Đã gửi\" v.v.)",
|
||||
"backup_mx_options": "Tùy chọn chuyển tiếp",
|
||||
"bcc_dest_format": "Đích BCC phải là một địa chỉ email hợp lệ duy nhất.<br>Nếu bạn cần gửi bản sao đến nhiều địa chỉ, hãy tạo một bí danh và sử dụng nó ở đây.",
|
||||
"client_id": "ID khách hàng",
|
||||
"client_secret": "Khóa bí mật khách hàng",
|
||||
"comment_info": "Bình luận riêng tư không hiển thị với người dùng, trong khi bình luận công khai được hiển thị dưới dạng chú thích khi di chuột qua trong tổng quan người dùng",
|
||||
"created_on": "Được tạo vào",
|
||||
"custom_attributes": "Thuộc tính tùy chỉnh",
|
||||
"delete1": "Xóa từ nguồn khi hoàn thành",
|
||||
"delete2": "Xóa thư ở đích không có ở nguồn",
|
||||
"delete2duplicates": "Xóa các bản sao ở đích",
|
||||
"delete_ays": "Vui lòng xác nhận quá trình xóa.",
|
||||
"description": "Mô tả",
|
||||
"disable_login": "Không cho phép đăng nhập (vẫn nhận được thư đến)",
|
||||
"domain": "Chỉnh sửa tên miền",
|
||||
"domain_admin": "Chỉnh sửa quản trị viên tên miền",
|
||||
"domain_footer": "Chân trang toàn tên miền",
|
||||
"domain_footer_html": "Chân trang HTML",
|
||||
"domain_footer_info": "Chân trang toàn tên miền được thêm vào tất cả các email gửi đi liên quan đến một địa chỉ trong tên miền này. <br> Các biến sau có thể được sử dụng cho chân trang:",
|
||||
"domain_footer_info_vars": {
|
||||
"auth_user": "{= auth_user =} - Tên người dùng đã xác thực được chỉ định bởi MTA",
|
||||
"from_user": "{= from_user =} - Phần người dùng của phong bì, ví dụ \"moo@mailcow.tld\" sẽ trả về \"moo\"",
|
||||
"from_name": "{= from_name =} - Tên từ phong bì, ví dụ \"Mailcow <moo@mailcow.tld>\" sẽ trả về \"Mailcow\"",
|
||||
"from_addr": "{= from_addr =} - Phần địa chỉ của phong bì",
|
||||
"from_domain": "{= from_domain =} - Phần tên miền của phong bì",
|
||||
"custom": "{= foo =} - Nếu hộp thư có thuộc tính tùy chỉnh \"foo\" với giá trị \"bar\" sẽ trả về \"bar\""
|
||||
},
|
||||
"domain_footer_plain": "Chân trang văn bản thuần",
|
||||
"domain_footer_skip_replies": "Bỏ qua chân trang trong email trả lời",
|
||||
"domain_quota": "Hạn ngạch tên miền",
|
||||
"domains": "Các tên miền",
|
||||
"dont_check_sender_acl": "Tắt kiểm tra người gửi cho tên miền %s (+ tên miền bí danh)",
|
||||
"edit_alias_domain": "Chỉnh sửa tên miền bí danh",
|
||||
"encryption": "Mã hóa",
|
||||
"exclude": "Loại trừ đối tượng (biểu thức chính quy)",
|
||||
"extended_sender_acl": "Địa chỉ người gửi bên ngoài",
|
||||
"extended_sender_acl_info": "Khóa tên miền DKIM nên được nhập vào, nếu có sẵn.<br>\n Nhớ thêm máy chủ này vào bản ghi SPF TXT tương ứng.<br>\n Khi một tên miền hoặc tên miền bí danh được thêm vào máy chủ này, mà trùng với một địa chỉ bên ngoài, địa chỉ bên ngoài sẽ bị xóa.<br>\n Sử dụng @domain.tld để cho phép gửi dưới dạng *@domain.tld.",
|
||||
"force_pw_update": "Bắt buộc cập nhật mật khẩu trong lần đăng nhập tiếp theo",
|
||||
"force_pw_update_info": "Người dùng này sẽ chỉ có thể đăng nhập vào %s. Mật khẩu ứng dụng vẫn có thể sử dụng được.",
|
||||
"footer_exclude": "Loại trừ khỏi chân trang",
|
||||
"full_name": "Tên đầy đủ",
|
||||
"gal": "Danh sách địa chỉ toàn cục",
|
||||
"gal_info": "GAL chứa tất cả các đối tượng của một tên miền và không thể được chỉnh sửa bởi bất kỳ người dùng nào. Thông tin rảnh/bận trong SOGo sẽ bị thiếu nếu vô hiệu hóa! <b>Khởi động lại SOGo để áp dụng thay đổi.</b>",
|
||||
"generate": "tạo",
|
||||
"grant_types": "Các loại cấp quyền",
|
||||
"hostname": "Tên máy chủ",
|
||||
"inactive": "Không hoạt động",
|
||||
"internal": "Nội bộ",
|
||||
"internal_info": "Bí danh nội bộ chỉ có thể truy cập từ tên miền sở hữu hoặc tên miền bí danh.",
|
||||
"kind": "Loại",
|
||||
"last_modified": "Sửa đổi lần cuối",
|
||||
"lookup_mx": "Đích là một biểu thức chính quy để khớp với tên MX (<code>.*.google.com</code> để định tuyến tất cả thư nhắm đến MX kết thúc bằng google.com qua bước nhảy này)"
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,30 @@ if (!isset($_SERVER['HTTP_HOST']) || strpos($_SERVER['HTTP_HOST'], 'mta-sts.') !
|
||||
}
|
||||
|
||||
$host = preg_replace('/:[0-9]+$/', '', $_SERVER['HTTP_HOST']);
|
||||
$domain = str_replace('mta-sts.', '', $host);
|
||||
$domain = idn_to_ascii(strtolower(str_replace('mta-sts.', '', $host)), 0, INTL_IDNA_VARIANT_UTS46);
|
||||
|
||||
// Validate domain or return 404 on error
|
||||
if ($domain === false || empty($domain)) {
|
||||
http_response_code(404);
|
||||
exit;
|
||||
}
|
||||
|
||||
// Check if domain is an alias domain and resolve to target domain
|
||||
try {
|
||||
$stmt = $pdo->prepare("SELECT `target_domain` FROM `alias_domain` WHERE `alias_domain` = :domain");
|
||||
$stmt->execute(array(':domain' => $domain));
|
||||
$alias_row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
|
||||
if ($alias_row !== false && !empty($alias_row['target_domain'])) {
|
||||
// This is an alias domain, use the target domain for MTA-STS lookup
|
||||
$domain = $alias_row['target_domain'];
|
||||
}
|
||||
} catch (PDOException $e) {
|
||||
// On database error, return 404
|
||||
http_response_code(404);
|
||||
exit;
|
||||
}
|
||||
|
||||
$mta_sts = mailbox('get', 'mta_sts', $domain);
|
||||
|
||||
if (count($mta_sts) == 0 ||
|
||||
|
||||
@@ -144,7 +144,7 @@
|
||||
|
||||
<form action="/" method="post" id="logout"><input type="hidden" name="logout"></form>
|
||||
|
||||
{% if ui_texts.ui_announcement_text and ui_texts.ui_announcement_active and not is_root_uri %}
|
||||
{% if ui_texts.ui_announcement_text and ui_texts.ui_announcement_active and not is_root_uri and mailcow_cc_username %}
|
||||
<div class="container mt-4">
|
||||
<div class="alert alert-{{ ui_texts.ui_announcement_type }}">{{ ui_texts.ui_announcement_text }}</div>
|
||||
</div>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
</div>
|
||||
<div id="collapse-tab-SpamAliases" class="card-body collapse" data-bs-parent="#user-content">
|
||||
<div class="row">
|
||||
<p>{{ lang.user.spam_aliases_info|raw }}</p>
|
||||
<div class="col-md-12 col-sm-12 col-12">
|
||||
<table id="tla_table" class="table table-striped dt-responsive w-100"></table>
|
||||
</div>
|
||||
@@ -18,12 +19,13 @@
|
||||
<a class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary" id="toggle_multi_select_all" data-id="tla" href="#"><i class="bi bi-check-all"></i> {{ lang.mailbox.toggle_all }}</a>
|
||||
<a class="btn btn-sm btn-xs-half d-block d-sm-inline btn-secondary dropdown-toggle" data-bs-toggle="dropdown" href="#">{{ lang.mailbox.quick_actions }}</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a class="dropdown-item" data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"validity":"1"}' href="#">{{ lang.user.expire_in }} 1 {{ lang.user.hour }}</a></li>
|
||||
<li><a class="dropdown-item" data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"validity":"24"}' href="#">{{ lang.user.expire_in }} 1 {{ lang.user.day }}</a></li>
|
||||
<li><a class="dropdown-item" data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"validity":"168"}' href="#">{{ lang.user.expire_in }} 1 {{ lang.user.week }}</a></li>
|
||||
<li><a class="dropdown-item" data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"validity":"744"}' href="#">{{ lang.user.expire_in }} 1 {{ lang.user.month }}</a></li>
|
||||
<li><a class="dropdown-item" data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"validity":"8760"}' href="#">{{ lang.user.expire_in }} 1 {{ lang.user.year }}</a></li>
|
||||
<li><a class="dropdown-item" data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"validity":"87600"}' href="#">{{ lang.user.expire_in }} 10 {{ lang.user.years }}</a></li>
|
||||
<li><a class="dropdown-item" data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"validity":"1","permanent":"0"}' href="#">{{ lang.user.expire_in }} 1 {{ lang.user.hour }}</a></li>
|
||||
<li><a class="dropdown-item" data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"validity":"24","permanent":"0"}' href="#">{{ lang.user.expire_in }} 1 {{ lang.user.day }}</a></li>
|
||||
<li><a class="dropdown-item" data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"validity":"168","permanent":"0"}' href="#">{{ lang.user.expire_in }} 1 {{ lang.user.week }}</a></li>
|
||||
<li><a class="dropdown-item" data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"validity":"744","permanent":"0"}' href="#">{{ lang.user.expire_in }} 1 {{ lang.user.month }}</a></li>
|
||||
<li><a class="dropdown-item" data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"validity":"8760","permanent":"0"}' href="#">{{ lang.user.expire_in }} 1 {{ lang.user.year }}</a></li>
|
||||
<li><a class="dropdown-item" data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"validity":"87600","permanent":"0"}' href="#">{{ lang.user.expire_in }} 10 {{ lang.user.years }}</a></li>
|
||||
<li><a class="dropdown-item" data-action="edit_selected" data-id="tla" data-api-url='edit/time_limited_alias' data-api-attr='{"permanent":"1"}' href="#">{{ lang.user.expire_never }}</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item" data-action="delete_selected" data-id="tla" data-api-url='delete/time_limited_alias' href="#">{{ lang.mailbox.remove }}</a></li>
|
||||
</ul>
|
||||
|
||||
@@ -84,7 +84,7 @@ services:
|
||||
- clamd
|
||||
|
||||
rspamd-mailcow:
|
||||
image: ghcr.io/mailcow/rspamd:2.4
|
||||
image: ghcr.io/mailcow/rspamd:3.14.2
|
||||
stop_grace_period: 30s
|
||||
depends_on:
|
||||
- dovecot-mailcow
|
||||
@@ -117,7 +117,7 @@ services:
|
||||
- rspamd
|
||||
|
||||
php-fpm-mailcow:
|
||||
image: ghcr.io/mailcow/phpfpm:nightly-15102025
|
||||
image: ghcr.io/mailcow/phpfpm:8.2.29
|
||||
command: "php-fpm -d date.timezone=${TZ} -d expose_php=0"
|
||||
depends_on:
|
||||
- redis-mailcow
|
||||
@@ -188,10 +188,10 @@ services:
|
||||
restart: always
|
||||
labels:
|
||||
ofelia.enabled: "true"
|
||||
ofelia.job-exec.phpfpm_keycloak_sync.schedule: "@every 1m"
|
||||
ofelia.job-exec.phpfpm_keycloak_sync.schedule: "0 * * * * *"
|
||||
ofelia.job-exec.phpfpm_keycloak_sync.no-overlap: "true"
|
||||
ofelia.job-exec.phpfpm_keycloak_sync.command: "/bin/bash -c \"php /crons/keycloak-sync.php || exit 0\""
|
||||
ofelia.job-exec.phpfpm_ldap_sync.schedule: "@every 1m"
|
||||
ofelia.job-exec.phpfpm_ldap_sync.schedule: "0 * * * * *"
|
||||
ofelia.job-exec.phpfpm_ldap_sync.no-overlap: "true"
|
||||
ofelia.job-exec.phpfpm_ldap_sync.command: "/bin/bash -c \"php /crons/ldap-sync.php || exit 0\""
|
||||
networks:
|
||||
@@ -200,7 +200,7 @@ services:
|
||||
- phpfpm
|
||||
|
||||
sogo-mailcow:
|
||||
image: ghcr.io/mailcow/sogo:1.136
|
||||
image: ghcr.io/mailcow/sogo:5.12.4
|
||||
environment:
|
||||
- DBNAME=${DBNAME}
|
||||
- DBUSER=${DBUSER}
|
||||
@@ -236,13 +236,13 @@ services:
|
||||
- sogo-userdata-backup-vol-1:/sogo_backup
|
||||
labels:
|
||||
ofelia.enabled: "true"
|
||||
ofelia.job-exec.sogo_sessions.schedule: "@every 1m"
|
||||
ofelia.job-exec.sogo_sessions.schedule: "0 * * * * *"
|
||||
ofelia.job-exec.sogo_sessions.command: "/bin/bash -c \"[[ $${MASTER} == y ]] && /usr/local/bin/gosu sogo /usr/sbin/sogo-tool -v expire-sessions $${SOGO_EXPIRE_SESSION} || exit 0\""
|
||||
ofelia.job-exec.sogo_ealarms.schedule: "@every 1m"
|
||||
ofelia.job-exec.sogo_ealarms.schedule: "0 * * * * *"
|
||||
ofelia.job-exec.sogo_ealarms.command: "/bin/bash -c \"[[ $${MASTER} == y ]] && /usr/local/bin/gosu sogo /usr/sbin/sogo-ealarms-notify -p /etc/sogo/cron.creds || exit 0\""
|
||||
ofelia.job-exec.sogo_eautoreply.schedule: "@every 5m"
|
||||
ofelia.job-exec.sogo_eautoreply.command: "/bin/bash -c \"[[ $${MASTER} == y ]] && /usr/local/bin/gosu sogo /usr/sbin/sogo-tool update-autoreply -p /etc/sogo/cron.creds || exit 0\""
|
||||
ofelia.job-exec.sogo_backup.schedule: "@every 24h"
|
||||
ofelia.job-exec.sogo_eautoreply.schedule: "0 */5 * * * *"
|
||||
ofelia.job-exec.sogo_eautoreply.command: "/bin/bash -c \"[[ $${MASTER} == y ]] && /usr/local/bin/gosu sogo /usr/sbin/sogo-tool update-autoreply -p /etc/sogo/sieve.creds || exit 0\""
|
||||
ofelia.job-exec.sogo_backup.schedule: "0 0 0 * * *"
|
||||
ofelia.job-exec.sogo_backup.command: "/bin/bash -c \"[[ $${MASTER} == y ]] && /usr/local/bin/gosu sogo /usr/sbin/sogo-tool backup /sogo_backup ALL || exit 0\""
|
||||
restart: always
|
||||
networks:
|
||||
@@ -252,7 +252,7 @@ services:
|
||||
- sogo
|
||||
|
||||
dovecot-mailcow:
|
||||
image: ghcr.io/mailcow/dovecot:nightly-11092025
|
||||
image: ghcr.io/mailcow/dovecot:2.3.21.1
|
||||
depends_on:
|
||||
- mysql-mailcow
|
||||
- netfilter-mailcow
|
||||
@@ -310,22 +310,22 @@ services:
|
||||
tty: true
|
||||
labels:
|
||||
ofelia.enabled: "true"
|
||||
ofelia.job-exec.dovecot_imapsync_runner.schedule: "@every 1m"
|
||||
ofelia.job-exec.dovecot_imapsync_runner.schedule: "0 * * * * *"
|
||||
ofelia.job-exec.dovecot_imapsync_runner.no-overlap: "true"
|
||||
ofelia.job-exec.dovecot_imapsync_runner.command: "/bin/bash -c \"[[ $${MASTER} == y ]] && /usr/local/bin/gosu nobody /usr/local/bin/imapsync_runner.pl || exit 0\""
|
||||
ofelia.job-exec.dovecot_trim_logs.schedule: "@every 1m"
|
||||
ofelia.job-exec.dovecot_trim_logs.schedule: "0 * * * * *"
|
||||
ofelia.job-exec.dovecot_trim_logs.command: "/bin/bash -c \"[[ $${MASTER} == y ]] && /usr/local/bin/gosu vmail /usr/local/bin/trim_logs.sh || exit 0\""
|
||||
ofelia.job-exec.dovecot_quarantine.schedule: "@every 20m"
|
||||
ofelia.job-exec.dovecot_quarantine.schedule: "0 */20 * * * *"
|
||||
ofelia.job-exec.dovecot_quarantine.command: "/bin/bash -c \"[[ $${MASTER} == y ]] && /usr/local/bin/gosu vmail /usr/local/bin/quarantine_notify.py || exit 0\""
|
||||
ofelia.job-exec.dovecot_clean_q_aged.schedule: "@every 24h"
|
||||
ofelia.job-exec.dovecot_clean_q_aged.schedule: "0 0 0 * * *"
|
||||
ofelia.job-exec.dovecot_clean_q_aged.command: "/bin/bash -c \"[[ $${MASTER} == y ]] && /usr/local/bin/gosu vmail /usr/local/bin/clean_q_aged.sh || exit 0\""
|
||||
ofelia.job-exec.dovecot_maildir_gc.schedule: "@every 30m"
|
||||
ofelia.job-exec.dovecot_maildir_gc.schedule: "0 */30 * * * *"
|
||||
ofelia.job-exec.dovecot_maildir_gc.command: "/bin/bash -c \"source /source_env.sh ; /usr/local/bin/gosu vmail /usr/local/bin/maildir_gc.sh\""
|
||||
ofelia.job-exec.dovecot_sarules.schedule: "@every 24h"
|
||||
ofelia.job-exec.dovecot_sarules.command: "/bin/bash -c \"/usr/local/bin/sa-rules.sh\""
|
||||
ofelia.job-exec.dovecot_fts.schedule: "@every 24h"
|
||||
ofelia.job-exec.dovecot_fts.schedule: "0 0 0 * * *"
|
||||
ofelia.job-exec.dovecot_fts.command: "/bin/bash -c \"/usr/local/bin/gosu vmail /usr/local/bin/optimize-fts.sh\""
|
||||
ofelia.job-exec.dovecot_repl_health.schedule: "@every 5m"
|
||||
ofelia.job-exec.dovecot_repl_health.schedule: "0 */5 * * * *"
|
||||
ofelia.job-exec.dovecot_repl_health.command: "/bin/bash -c \"/usr/local/bin/gosu vmail /usr/local/bin/repl_health.sh\""
|
||||
ulimits:
|
||||
nproc: 65535
|
||||
@@ -339,7 +339,7 @@ services:
|
||||
- dovecot
|
||||
|
||||
postfix-mailcow:
|
||||
image: ghcr.io/mailcow/postfix:1.81
|
||||
image: ghcr.io/mailcow/postfix:3.7.11
|
||||
depends_on:
|
||||
mysql-mailcow:
|
||||
condition: service_started
|
||||
@@ -382,7 +382,7 @@ services:
|
||||
- postfix
|
||||
|
||||
postfix-tlspol-mailcow:
|
||||
image: ghcr.io/mailcow/postfix-tlspol:1.0
|
||||
image: ghcr.io/mailcow/postfix-tlspol:1.8.22
|
||||
depends_on:
|
||||
unbound-mailcow:
|
||||
condition: service_healthy
|
||||
@@ -466,7 +466,7 @@ services:
|
||||
condition: service_started
|
||||
unbound-mailcow:
|
||||
condition: service_healthy
|
||||
image: ghcr.io/mailcow/acme:nightly-11092025
|
||||
image: ghcr.io/mailcow/acme:1.95
|
||||
dns:
|
||||
- ${IPV4_NETWORK:-172.22.1}.254
|
||||
environment:
|
||||
|
||||
@@ -91,6 +91,44 @@ if grep --help 2>&1 | head -n 1 | grep -q -i "busybox"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Add image prefetch function
|
||||
function prefetch_image() {
|
||||
echo "Checking Docker image: ${DEBIAN_DOCKER_IMAGE}"
|
||||
|
||||
# Get local image digest if it exists
|
||||
local local_digest=$(docker image inspect ${DEBIAN_DOCKER_IMAGE} --format='{{index .RepoDigests 0}}' 2>/dev/null | cut -d'@' -f2)
|
||||
|
||||
# Get remote image digest without pulling
|
||||
local remote_digest=$(docker manifest inspect ${DEBIAN_DOCKER_IMAGE} 2>/dev/null | grep -oP '"digest":\s*"\K[^"]+' | head -1)
|
||||
|
||||
if [[ -z "${remote_digest}" ]]; then
|
||||
echo "Warning: Unable to check remote image"
|
||||
if [[ -n "${local_digest}" ]]; then
|
||||
echo "Using cached version"
|
||||
echo
|
||||
return 0
|
||||
else
|
||||
echo "Error: Image ${DEBIAN_DOCKER_IMAGE} not found locally or remotely"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "${local_digest}" != "${remote_digest}" ]]; then
|
||||
echo "Image update available, pulling ${DEBIAN_DOCKER_IMAGE}"
|
||||
if docker pull ${DEBIAN_DOCKER_IMAGE} 2>/dev/null; then
|
||||
echo "Successfully pulled ${DEBIAN_DOCKER_IMAGE}"
|
||||
else
|
||||
echo "Error: Failed to pull ${DEBIAN_DOCKER_IMAGE}"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "Image is up to date (${remote_digest:0:12}...)"
|
||||
fi
|
||||
echo
|
||||
}
|
||||
|
||||
# Prefetch the image early in the script
|
||||
prefetch_image
|
||||
|
||||
function backup() {
|
||||
DATE=$(date +"%Y-%m-%d-%H-%M-%S")
|
||||
@@ -110,32 +148,32 @@ function backup() {
|
||||
docker run --name mailcow-backup --rm \
|
||||
-v ${BACKUP_LOCATION}/mailcow-${DATE}:/backup:z \
|
||||
-v $(docker volume ls -qf name=^${CMPS_PRJ}_vmail-vol-1$):/vmail:ro,z \
|
||||
${DEBIAN_DOCKER_IMAGE} /bin/tar --warning='no-file-ignored' --use-compress-program="pigz --rsyncable -p ${THREADS}" -Pcvpf /backup/backup_vmail.tar.gz /vmail
|
||||
${DEBIAN_DOCKER_IMAGE} /bin/tar --warning='no-file-ignored' --use-compress-program="zstd --rsyncable -T${THREADS}" -Pcvpf /backup/backup_vmail.tar.zst /vmail
|
||||
;;&
|
||||
crypt|all)
|
||||
docker run --name mailcow-backup --rm \
|
||||
-v ${BACKUP_LOCATION}/mailcow-${DATE}:/backup:z \
|
||||
-v $(docker volume ls -qf name=^${CMPS_PRJ}_crypt-vol-1$):/crypt:ro,z \
|
||||
${DEBIAN_DOCKER_IMAGE} /bin/tar --warning='no-file-ignored' --use-compress-program="pigz --rsyncable -p ${THREADS}" -Pcvpf /backup/backup_crypt.tar.gz /crypt
|
||||
${DEBIAN_DOCKER_IMAGE} /bin/tar --warning='no-file-ignored' --use-compress-program="zstd --rsyncable -T${THREADS}" -Pcvpf /backup/backup_crypt.tar.zst /crypt
|
||||
;;&
|
||||
redis|all)
|
||||
docker exec $(docker ps -qf name=redis-mailcow) redis-cli -a ${REDISPASS} --no-auth-warning save
|
||||
docker run --name mailcow-backup --rm \
|
||||
-v ${BACKUP_LOCATION}/mailcow-${DATE}:/backup:z \
|
||||
-v $(docker volume ls -qf name=^${CMPS_PRJ}_redis-vol-1$):/redis:ro,z \
|
||||
${DEBIAN_DOCKER_IMAGE} /bin/tar --warning='no-file-ignored' --use-compress-program="pigz --rsyncable -p ${THREADS}" -Pcvpf /backup/backup_redis.tar.gz /redis
|
||||
${DEBIAN_DOCKER_IMAGE} /bin/tar --warning='no-file-ignored' --use-compress-program="zstd --rsyncable -T${THREADS}" -Pcvpf /backup/backup_redis.tar.zst /redis
|
||||
;;&
|
||||
rspamd|all)
|
||||
docker run --name mailcow-backup --rm \
|
||||
-v ${BACKUP_LOCATION}/mailcow-${DATE}:/backup:z \
|
||||
-v $(docker volume ls -qf name=^${CMPS_PRJ}_rspamd-vol-1$):/rspamd:ro,z \
|
||||
${DEBIAN_DOCKER_IMAGE} /bin/tar --warning='no-file-ignored' --use-compress-program="pigz --rsyncable -p ${THREADS}" -Pcvpf /backup/backup_rspamd.tar.gz /rspamd
|
||||
${DEBIAN_DOCKER_IMAGE} /bin/tar --warning='no-file-ignored' --use-compress-program="zstd --rsyncable -T${THREADS}" -Pcvpf /backup/backup_rspamd.tar.zst /rspamd
|
||||
;;&
|
||||
postfix|all)
|
||||
docker run --name mailcow-backup --rm \
|
||||
-v ${BACKUP_LOCATION}/mailcow-${DATE}:/backup:z \
|
||||
-v $(docker volume ls -qf name=^${CMPS_PRJ}_postfix-vol-1$):/postfix:ro,z \
|
||||
${DEBIAN_DOCKER_IMAGE} /bin/tar --warning='no-file-ignored' --use-compress-program="pigz --rsyncable -p ${THREADS}" -Pcvpf /backup/backup_postfix.tar.gz /postfix
|
||||
${DEBIAN_DOCKER_IMAGE} /bin/tar --warning='no-file-ignored' --use-compress-program="zstd --rsyncable -T${THREADS}" -Pcvpf /backup/backup_postfix.tar.zst /postfix
|
||||
;;&
|
||||
mysql|all)
|
||||
SQLIMAGE=$(grep -iEo '(mysql|mariadb)\:.+' ${COMPOSE_FILE})
|
||||
@@ -154,7 +192,7 @@ function backup() {
|
||||
${SQLIMAGE} /bin/sh -c "mariabackup --host mysql --user root --password ${DBROOT} --backup --rsync --target-dir=/backup_mariadb ; \
|
||||
mariabackup --prepare --target-dir=/backup_mariadb ; \
|
||||
chown -R 999:999 /backup_mariadb ; \
|
||||
/bin/tar --warning='no-file-ignored' --use-compress-program='gzip --rsyncable' -Pcvpf /backup/backup_mariadb.tar.gz /backup_mariadb ;"
|
||||
/bin/tar --warning='no-file-ignored' --use-compress-program='zstd --rsyncable' -Pcvpf /backup/backup_mariadb.tar.zst /backup_mariadb ;"
|
||||
fi
|
||||
;;&
|
||||
--delete-days)
|
||||
@@ -170,6 +208,19 @@ function backup() {
|
||||
done
|
||||
}
|
||||
|
||||
function get_archive_info() {
|
||||
local backup_name="$1"
|
||||
local location="$2"
|
||||
|
||||
if [[ -f "${location}/${backup_name}.tar.zst" ]]; then
|
||||
echo "${backup_name}.tar.zst|zstd -d -T${THREADS}"
|
||||
elif [[ -f "${location}/${backup_name}.tar.gz" ]]; then
|
||||
echo "${backup_name}.tar.gz|pigz -d -p ${THREADS}"
|
||||
else
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
function restore() {
|
||||
for bin in docker; do
|
||||
if [[ -z $(which ${bin}) ]]; then
|
||||
@@ -199,10 +250,17 @@ function restore() {
|
||||
case "$1" in
|
||||
vmail)
|
||||
docker stop $(docker ps -qf name=dovecot-mailcow)
|
||||
docker run -i --name mailcow-backup --rm \
|
||||
-v ${RESTORE_LOCATION}:/backup:z \
|
||||
-v $(docker volume ls -qf name=^${CMPS_PRJ}_vmail-vol-1$):/vmail:z \
|
||||
${DEBIAN_DOCKER_IMAGE} /bin/tar --use-compress-program="pigz -d -p ${THREADS}" -Pxvf /backup/backup_vmail.tar.gz
|
||||
ARCHIVE_INFO=$(get_archive_info "backup_vmail" "${RESTORE_LOCATION}")
|
||||
if [[ -z "${ARCHIVE_INFO}" ]]; then
|
||||
echo -e "\e[31mError: No backup file found for vmail (searched for .tar.zst and .tar.gz)\e[0m"
|
||||
else
|
||||
ARCHIVE_FILE=$(echo "${ARCHIVE_INFO}" | cut -d'|' -f1)
|
||||
DECOMPRESS_PROG=$(echo "${ARCHIVE_INFO}" | cut -d'|' -f2)
|
||||
docker run -i --name mailcow-backup --rm \
|
||||
-v ${RESTORE_LOCATION}:/backup:z \
|
||||
-v $(docker volume ls -qf name=^${CMPS_PRJ}_vmail-vol-1$):/vmail:z \
|
||||
${DEBIAN_DOCKER_IMAGE} /bin/tar --use-compress-program="${DECOMPRESS_PROG}" -Pxvf /backup/${ARCHIVE_FILE}
|
||||
fi
|
||||
docker start $(docker ps -aqf name=dovecot-mailcow)
|
||||
echo
|
||||
echo "In most cases it is not required to run a full resync, you can run the command printed below at any time after testing wether the restore process broke a mailbox:"
|
||||
@@ -218,31 +276,50 @@ function restore() {
|
||||
;;
|
||||
redis)
|
||||
docker stop $(docker ps -qf name=redis-mailcow)
|
||||
docker run -i --name mailcow-backup --rm \
|
||||
-v ${RESTORE_LOCATION}:/backup:z \
|
||||
-v $(docker volume ls -qf name=^${CMPS_PRJ}_redis-vol-1$):/redis:z \
|
||||
${DEBIAN_DOCKER_IMAGE} /bin/tar --use-compress-program="pigz -d -p ${THREADS}" -Pxvf /backup/backup_redis.tar.gz
|
||||
ARCHIVE_INFO=$(get_archive_info "backup_redis" "${RESTORE_LOCATION}")
|
||||
if [[ -z "${ARCHIVE_INFO}" ]]; then
|
||||
echo -e "\e[31mError: No backup file found for redis (searched for .tar.zst and .tar.gz)\e[0m"
|
||||
else
|
||||
ARCHIVE_FILE=$(echo "${ARCHIVE_INFO}" | cut -d'|' -f1)
|
||||
DECOMPRESS_PROG=$(echo "${ARCHIVE_INFO}" | cut -d'|' -f2)
|
||||
docker run -i --name mailcow-backup --rm \
|
||||
-v ${RESTORE_LOCATION}:/backup:z \
|
||||
-v $(docker volume ls -qf name=^${CMPS_PRJ}_redis-vol-1$):/redis:z \
|
||||
${DEBIAN_DOCKER_IMAGE} /bin/tar --use-compress-program="${DECOMPRESS_PROG}" -Pxvf /backup/${ARCHIVE_FILE}
|
||||
fi
|
||||
docker start $(docker ps -aqf name=redis-mailcow)
|
||||
;;
|
||||
crypt)
|
||||
docker stop $(docker ps -qf name=dovecot-mailcow)
|
||||
docker run -i --name mailcow-backup --rm \
|
||||
-v ${RESTORE_LOCATION}:/backup:z \
|
||||
-v $(docker volume ls -qf name=^${CMPS_PRJ}_crypt-vol-1$):/crypt:z \
|
||||
${DEBIAN_DOCKER_IMAGE} /bin/tar --use-compress-program="pigz -d -p ${THREADS}" -Pxvf /backup/backup_crypt.tar.gz
|
||||
ARCHIVE_INFO=$(get_archive_info "backup_crypt" "${RESTORE_LOCATION}")
|
||||
if [[ -z "${ARCHIVE_INFO}" ]]; then
|
||||
echo -e "\e[31mError: No backup file found for crypt (searched for .tar.zst and .tar.gz)\e[0m"
|
||||
else
|
||||
ARCHIVE_FILE=$(echo "${ARCHIVE_INFO}" | cut -d'|' -f1)
|
||||
DECOMPRESS_PROG=$(echo "${ARCHIVE_INFO}" | cut -d'|' -f2)
|
||||
docker run -i --name mailcow-backup --rm \
|
||||
-v ${RESTORE_LOCATION}:/backup:z \
|
||||
-v $(docker volume ls -qf name=^${CMPS_PRJ}_crypt-vol-1$):/crypt:z \
|
||||
${DEBIAN_DOCKER_IMAGE} /bin/tar --use-compress-program="${DECOMPRESS_PROG}" -Pxvf /backup/${ARCHIVE_FILE}
|
||||
fi
|
||||
docker start $(docker ps -aqf name=dovecot-mailcow)
|
||||
;;
|
||||
rspamd)
|
||||
if [[ $(find "${RESTORE_LOCATION}" \( -name '*x86*' -o -name '*aarch*' \) -exec basename {} \; | sed 's/^\.//' | sed 's/^\.//') == "" ]]; then
|
||||
ARCHIVE_INFO=$(get_archive_info "backup_rspamd" "${RESTORE_LOCATION}")
|
||||
if [[ -z "${ARCHIVE_INFO}" ]]; then
|
||||
echo -e "\e[31mError: No backup file found for rspamd (searched for .tar.zst and .tar.gz)\e[0m"
|
||||
elif [[ $(find "${RESTORE_LOCATION}" \( -name '*x86*' -o -name '*aarch*' \) -exec basename {} \; | sed 's/^\.//' | sed 's/^\.//') == "" ]]; then
|
||||
echo -e "\e[33mCould not find a architecture signature of the loaded backup... Maybe the backup was done before the multiarch update?"
|
||||
sleep 2
|
||||
echo -e "Continuing anyhow. If rspamd is crashing upon boot try remove the rspamd volume with docker volume rm ${CMPS_PRJ}_rspamd-vol-1 after you've stopped the stack.\e[0m"
|
||||
sleep 2
|
||||
docker stop $(docker ps -qf name=rspamd-mailcow)
|
||||
ARCHIVE_FILE=$(echo "${ARCHIVE_INFO}" | cut -d'|' -f1)
|
||||
DECOMPRESS_PROG=$(echo "${ARCHIVE_INFO}" | cut -d'|' -f2)
|
||||
docker run -i --name mailcow-backup --rm \
|
||||
-v ${RESTORE_LOCATION}:/backup:z \
|
||||
-v $(docker volume ls -qf name=^${CMPS_PRJ}_rspamd-vol-1$):/rspamd:z \
|
||||
${DEBIAN_DOCKER_IMAGE} /bin/tar --use-compress-program="pigz -d -p ${THREADS}" -Pxvf /backup/backup_rspamd.tar.gz
|
||||
${DEBIAN_DOCKER_IMAGE} /bin/tar --use-compress-program="${DECOMPRESS_PROG}" -Pxvf /backup/${ARCHIVE_FILE}
|
||||
docker start $(docker ps -aqf name=rspamd-mailcow)
|
||||
elif [[ $ARCH != $(find "${RESTORE_LOCATION}" \( -name '*x86*' -o -name '*aarch*' \) -exec basename {} \; | sed 's/^\.//' | sed 's/^\.//') ]]; then
|
||||
echo -e "\e[31mThe Architecture of the backed up mailcow OS is different then your restoring mailcow OS..."
|
||||
@@ -250,19 +327,28 @@ function restore() {
|
||||
echo -e "Skipping rspamd due to compatibility issues!\e[0m"
|
||||
else
|
||||
docker stop $(docker ps -qf name=rspamd-mailcow)
|
||||
ARCHIVE_FILE=$(echo "${ARCHIVE_INFO}" | cut -d'|' -f1)
|
||||
DECOMPRESS_PROG=$(echo "${ARCHIVE_INFO}" | cut -d'|' -f2)
|
||||
docker run -i --name mailcow-backup --rm \
|
||||
-v ${RESTORE_LOCATION}:/backup:z \
|
||||
-v $(docker volume ls -qf name=^${CMPS_PRJ}_rspamd-vol-1$):/rspamd:z \
|
||||
${DEBIAN_DOCKER_IMAGE} /bin/tar --use-compress-program="pigz -d -p ${THREADS}" -Pxvf /backup/backup_rspamd.tar.gz
|
||||
${DEBIAN_DOCKER_IMAGE} /bin/tar --use-compress-program="${DECOMPRESS_PROG}" -Pxvf /backup/${ARCHIVE_FILE}
|
||||
docker start $(docker ps -aqf name=rspamd-mailcow)
|
||||
fi
|
||||
;;
|
||||
postfix)
|
||||
docker stop $(docker ps -qf name=postfix-mailcow)
|
||||
docker run -i --name mailcow-backup --rm \
|
||||
-v ${RESTORE_LOCATION}:/backup:z \
|
||||
-v $(docker volume ls -qf name=^${CMPS_PRJ}_postfix-vol-1$):/postfix:z \
|
||||
${DEBIAN_DOCKER_IMAGE} /bin/tar --use-compress-program="pigz -d -p ${THREADS}" -Pxvf /backup/backup_postfix.tar.gz
|
||||
ARCHIVE_INFO=$(get_archive_info "backup_postfix" "${RESTORE_LOCATION}")
|
||||
if [[ -z "${ARCHIVE_INFO}" ]]; then
|
||||
echo -e "\e[31mError: No backup file found for postfix (searched for .tar.zst and .tar.gz)\e[0m"
|
||||
else
|
||||
ARCHIVE_FILE=$(echo "${ARCHIVE_INFO}" | cut -d'|' -f1)
|
||||
DECOMPRESS_PROG=$(echo "${ARCHIVE_INFO}" | cut -d'|' -f2)
|
||||
docker run -i --name mailcow-backup --rm \
|
||||
-v ${RESTORE_LOCATION}:/backup:z \
|
||||
-v $(docker volume ls -qf name=^${CMPS_PRJ}_postfix-vol-1$):/postfix:z \
|
||||
${DEBIAN_DOCKER_IMAGE} /bin/tar --use-compress-program="${DECOMPRESS_PROG}" -Pxvf /backup/${ARCHIVE_FILE}
|
||||
fi
|
||||
docker start $(docker ps -aqf name=postfix-mailcow)
|
||||
;;
|
||||
mysql|mariadb)
|
||||
@@ -305,14 +391,19 @@ function restore() {
|
||||
echo Restoring... && \
|
||||
gunzip < backup/backup_mysql.gz | mysql -uroot && \
|
||||
mysql -uroot -e SHUTDOWN;"
|
||||
elif [[ -f "${RESTORE_LOCATION}/backup_mariadb.tar.gz" ]]; then
|
||||
docker run --name mailcow-backup --rm \
|
||||
-v $(docker volume ls -qf name=^${CMPS_PRJ}_mysql-vol-1$):/backup_mariadb/:rw,z \
|
||||
--entrypoint= \
|
||||
-v ${RESTORE_LOCATION}:/backup:z \
|
||||
${SQLIMAGE} /bin/bash -c "shopt -s dotglob ; \
|
||||
/bin/rm -rf /backup_mariadb/* ; \
|
||||
/bin/tar -Pxvzf /backup/backup_mariadb.tar.gz"
|
||||
else
|
||||
ARCHIVE_INFO=$(get_archive_info "backup_mariadb" "${RESTORE_LOCATION}")
|
||||
if [[ -n "${ARCHIVE_INFO}" ]]; then
|
||||
ARCHIVE_FILE=$(echo "${ARCHIVE_INFO}" | cut -d'|' -f1)
|
||||
DECOMPRESS_PROG=$(echo "${ARCHIVE_INFO}" | cut -d'|' -f2)
|
||||
docker run --name mailcow-backup --rm \
|
||||
-v $(docker volume ls -qf name=^${CMPS_PRJ}_mysql-vol-1$):/backup_mariadb/:rw,z \
|
||||
--entrypoint= \
|
||||
-v ${RESTORE_LOCATION}:/backup:z \
|
||||
${SQLIMAGE} /bin/bash -c "shopt -s dotglob ; \
|
||||
/bin/rm -rf /backup_mariadb/* ; \
|
||||
/bin/tar --use-compress-program='${DECOMPRESS_PROG}' -Pxvf /backup/${ARCHIVE_FILE}"
|
||||
fi
|
||||
fi
|
||||
echo "Modifying mailcow.conf..."
|
||||
source ${RESTORE_LOCATION}/mailcow.conf
|
||||
@@ -363,8 +454,8 @@ elif [[ ${1} == "restore" ]]; then
|
||||
fi
|
||||
|
||||
echo "[ 0 ] - all"
|
||||
# find all files in folder with *.gz extension, print their base names, remove backup_, remove .tar (if present), remove .gz
|
||||
FILE_SELECTION[0]=$(find "${FOLDER_SELECTION[${input_sel}]}" -maxdepth 1 \( -type d -o -type f \) \( -name '*.gz' -o -name 'mysql' \) -printf '%f\n' | sed 's/backup_*//' | sed 's/\.[^.]*$//' | sed 's/\.[^.]*$//')
|
||||
# find all files in folder with *.zst or *.gz extension, print their base names, remove backup_, remove .tar (if present), remove .zst/.gz
|
||||
FILE_SELECTION[0]=$(find "${FOLDER_SELECTION[${input_sel}]}" -maxdepth 1 \( -type d -o -type f \) \( -name '*.zst' -o -name '*.gz' -o -name 'mysql' \) -printf '%f\n' | sed 's/backup_*//' | sed 's/\.[^.]*$//' | sed 's/\.[^.]*$//' | sort -u)
|
||||
for file in $(ls -f "${FOLDER_SELECTION[${input_sel}]}"); do
|
||||
if [[ ${file} =~ vmail ]]; then
|
||||
echo "[ ${i} ] - Mail directory (/var/vmail)"
|
||||
|
||||
301
helper-scripts/dev_tests/test_backup_and_restore.sh
Executable file
301
helper-scripts/dev_tests/test_backup_and_restore.sh
Executable file
@@ -0,0 +1,301 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Test script for backup_and_restore.sh
|
||||
# Tests backward compatibility with .tar.gz and new .tar.zst format
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
BACKUP_IMAGE="${BACKUP_IMAGE:-ghcr.io/mailcow/backup:latest}"
|
||||
TEST_DIR="/tmp/mailcow_backup_test_$$"
|
||||
THREADS=2
|
||||
|
||||
echo "=== Mailcow Backup & Restore Test Suite ==="
|
||||
echo "Test directory: ${TEST_DIR}"
|
||||
echo "Backup image: ${BACKUP_IMAGE}"
|
||||
echo ""
|
||||
|
||||
# Cleanup function
|
||||
cleanup() {
|
||||
echo "Cleaning up test files..."
|
||||
rm -rf "${TEST_DIR}"
|
||||
docker rmi mailcow-backup-test 2>/dev/null || true
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
# Create test directory structure
|
||||
mkdir -p "${TEST_DIR}"/{test_data,backup_zst,backup_gz,restore_zst,restore_gz,backup_large_zst,backup_large_gz}
|
||||
echo "Test data for mailcow backup compatibility test" > "${TEST_DIR}/test_data/test.txt"
|
||||
echo "Additional file to verify complete restore" > "${TEST_DIR}/test_data/test2.txt"
|
||||
|
||||
# Build test backup image with zstd support
|
||||
echo "=== Building backup image with zstd support ==="
|
||||
docker build -t mailcow-backup-test "${SCRIPT_DIR}/../data/Dockerfiles/backup/" || {
|
||||
echo "ERROR: Failed to build backup image"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Test 1: Create .tar.zst backup
|
||||
echo ""
|
||||
echo "=== Test 1: Creating .tar.zst backup ==="
|
||||
docker run --rm \
|
||||
-w /data \
|
||||
-v "${TEST_DIR}/test_data:/data:ro" \
|
||||
-v "${TEST_DIR}/backup_zst:/backup" \
|
||||
mailcow-backup-test \
|
||||
/bin/tar --use-compress-program="zstd --rsyncable -T${THREADS}" \
|
||||
-cvpf /backup/backup_test.tar.zst . \
|
||||
> /dev/null
|
||||
echo "✓ .tar.zst backup created: $(ls -lh ${TEST_DIR}/backup_zst/backup_test.tar.zst | awk '{print $5}')"
|
||||
|
||||
# Test 2: Create .tar.gz backup
|
||||
echo ""
|
||||
echo "=== Test 2: Creating .tar.gz backup (legacy) ==="
|
||||
docker run --rm \
|
||||
-w /data \
|
||||
-v "${TEST_DIR}/test_data:/data:ro" \
|
||||
-v "${TEST_DIR}/backup_gz:/backup" \
|
||||
mailcow-backup-test \
|
||||
/bin/tar --use-compress-program="pigz --rsyncable -p ${THREADS}" \
|
||||
-cvpf /backup/backup_test.tar.gz . \
|
||||
> /dev/null
|
||||
echo "✓ .tar.gz backup created: $(ls -lh ${TEST_DIR}/backup_gz/backup_test.tar.gz | awk '{print $5}')"
|
||||
|
||||
# Test 3: Test get_archive_info function
|
||||
echo ""
|
||||
echo "=== Test 3: Testing get_archive_info function ==="
|
||||
|
||||
# Extract and test the function directly
|
||||
get_archive_info() {
|
||||
local backup_name="$1"
|
||||
local location="$2"
|
||||
|
||||
if [[ -f "${location}/${backup_name}.tar.zst" ]]; then
|
||||
echo "${backup_name}.tar.zst|zstd -d -T${THREADS}"
|
||||
elif [[ -f "${location}/${backup_name}.tar.gz" ]]; then
|
||||
echo "${backup_name}.tar.gz|pigz -d -p ${THREADS}"
|
||||
else
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
# Test with .tar.zst
|
||||
result=$(get_archive_info "backup_test" "${TEST_DIR}/backup_zst")
|
||||
if [[ "${result}" =~ "zstd" ]]; then
|
||||
echo "✓ Correctly detects .tar.zst and returns zstd decompressor"
|
||||
else
|
||||
echo "✗ Failed to detect .tar.zst"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test with .tar.gz
|
||||
result=$(get_archive_info "backup_test" "${TEST_DIR}/backup_gz")
|
||||
if [[ "${result}" =~ "pigz" ]]; then
|
||||
echo "✓ Correctly detects .tar.gz and returns pigz decompressor"
|
||||
else
|
||||
echo "✗ Failed to detect .tar.gz"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test with no file
|
||||
result=$(get_archive_info "backup_test" "${TEST_DIR}")
|
||||
if [[ -z "${result}" ]]; then
|
||||
echo "✓ Correctly returns empty when no backup file found"
|
||||
else
|
||||
echo "✗ Should return empty but got: ${result}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test 4: Restore from .tar.zst
|
||||
echo ""
|
||||
echo "=== Test 4: Restoring from .tar.zst ==="
|
||||
docker run --rm \
|
||||
-w /restore \
|
||||
-v "${TEST_DIR}/backup_zst:/backup:ro" \
|
||||
-v "${TEST_DIR}/restore_zst:/restore" \
|
||||
mailcow-backup-test \
|
||||
/bin/tar --use-compress-program="zstd -d -T${THREADS}" -xvpf /backup/backup_test.tar.zst \
|
||||
> /dev/null 2>&1
|
||||
|
||||
if [[ -f "${TEST_DIR}/restore_zst/test.txt" ]] && \
|
||||
[[ -f "${TEST_DIR}/restore_zst/test2.txt" ]]; then
|
||||
echo "✓ Successfully restored from .tar.zst"
|
||||
else
|
||||
echo "✗ Failed to restore from .tar.zst"
|
||||
ls -la "${TEST_DIR}/restore_zst/" || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test 5: Restore from .tar.gz
|
||||
echo ""
|
||||
echo "=== Test 5: Restoring from .tar.gz (backward compatibility) ==="
|
||||
docker run --rm \
|
||||
-w /restore \
|
||||
-v "${TEST_DIR}/backup_gz:/backup:ro" \
|
||||
-v "${TEST_DIR}/restore_gz:/restore" \
|
||||
mailcow-backup-test \
|
||||
/bin/tar --use-compress-program="pigz -d -p ${THREADS}" -xvpf /backup/backup_test.tar.gz \
|
||||
> /dev/null 2>&1
|
||||
|
||||
if [[ -f "${TEST_DIR}/restore_gz/test.txt" ]] && \
|
||||
[[ -f "${TEST_DIR}/restore_gz/test2.txt" ]]; then
|
||||
echo "✓ Successfully restored from .tar.gz (backward compatible)"
|
||||
else
|
||||
echo "✗ Failed to restore from .tar.gz"
|
||||
ls -la "${TEST_DIR}/restore_gz/" || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test 6: Verify content integrity
|
||||
echo ""
|
||||
echo "=== Test 6: Verifying content integrity ==="
|
||||
original_content=$(cat "${TEST_DIR}/test_data/test.txt")
|
||||
zst_content=$(cat "${TEST_DIR}/restore_zst/test.txt")
|
||||
gz_content=$(cat "${TEST_DIR}/restore_gz/test.txt")
|
||||
|
||||
if [[ "${original_content}" == "${zst_content}" ]] && \
|
||||
[[ "${original_content}" == "${gz_content}" ]]; then
|
||||
echo "✓ Content integrity verified for both formats"
|
||||
else
|
||||
echo "✗ Content mismatch detected"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test 7: Compare compression ratios
|
||||
echo ""
|
||||
echo "=== Test 7: Compression comparison ==="
|
||||
zst_size=$(stat -f%z "${TEST_DIR}/backup_zst/backup_test.tar.zst" 2>/dev/null || stat -c%s "${TEST_DIR}/backup_zst/backup_test.tar.zst")
|
||||
gz_size=$(stat -f%z "${TEST_DIR}/backup_gz/backup_test.tar.gz" 2>/dev/null || stat -c%s "${TEST_DIR}/backup_gz/backup_test.tar.gz")
|
||||
improvement=$(echo "scale=2; (${gz_size} - ${zst_size}) * 100 / ${gz_size}" | bc)
|
||||
|
||||
echo " Small files - .tar.gz size: ${gz_size} bytes"
|
||||
echo " Small files - .tar.zst size: ${zst_size} bytes"
|
||||
echo " Small files - Improvement: ${improvement}% smaller with zstd"
|
||||
|
||||
# Test 8: Error handling - missing backup file
|
||||
echo ""
|
||||
echo "=== Test 8: Error handling - Missing backup file ==="
|
||||
result=$(get_archive_info "nonexistent_backup" "${TEST_DIR}/backup_zst")
|
||||
if [[ -z "${result}" ]]; then
|
||||
echo "✓ Correctly handles missing backup files"
|
||||
else
|
||||
echo "✗ Should return empty for missing files"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test 9: Error handling - Empty directory
|
||||
echo ""
|
||||
echo "=== Test 9: Error handling - Empty directory ==="
|
||||
mkdir -p "${TEST_DIR}/empty_dir"
|
||||
result=$(get_archive_info "backup_test" "${TEST_DIR}/empty_dir")
|
||||
if [[ -z "${result}" ]]; then
|
||||
echo "✓ Correctly handles empty directories"
|
||||
else
|
||||
echo "✗ Should return empty for empty directories"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test 10: Priority test - .tar.zst preferred over .tar.gz
|
||||
echo ""
|
||||
echo "=== Test 10: Format priority - .tar.zst preferred ==="
|
||||
mkdir -p "${TEST_DIR}/both_formats"
|
||||
touch "${TEST_DIR}/both_formats/backup_test.tar.gz"
|
||||
touch "${TEST_DIR}/both_formats/backup_test.tar.zst"
|
||||
result=$(get_archive_info "backup_test" "${TEST_DIR}/both_formats")
|
||||
if [[ "${result}" =~ "zstd" ]]; then
|
||||
echo "✓ Correctly prefers .tar.zst when both formats exist"
|
||||
else
|
||||
echo "✗ Should prefer .tar.zst over .tar.gz"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Test 11: Large file compression test
|
||||
echo ""
|
||||
echo "=== Test 11: Large file compression test ==="
|
||||
mkdir -p "${TEST_DIR}/large_data"
|
||||
# Create ~10MB of compressible data (log-like content)
|
||||
for i in {1..50000}; do
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] INFO: Processing email message $i from user@example.com to recipient@domain.com" >> "${TEST_DIR}/large_data/maillog.txt"
|
||||
echo "[$(date '+%Y-%m-%d %H:%M:%S')] DEBUG: SMTP connection established from 192.168.1.$((i % 255))" >> "${TEST_DIR}/large_data/maillog.txt"
|
||||
done 2>/dev/null
|
||||
|
||||
# Get size (portable: works on Linux and macOS)
|
||||
if du --version 2>/dev/null | grep -q GNU; then
|
||||
original_size=$(du -sb "${TEST_DIR}/large_data" | cut -f1)
|
||||
else
|
||||
# macOS
|
||||
original_size=$(find "${TEST_DIR}/large_data" -type f -exec stat -f%z {} \; | awk '{sum+=$1} END {print sum}')
|
||||
fi
|
||||
echo " Original data size: $(echo "scale=2; ${original_size} / 1024 / 1024" | bc) MB"
|
||||
|
||||
# Backup with zstd
|
||||
docker run --rm \
|
||||
-w /data \
|
||||
-v "${TEST_DIR}/large_data:/data:ro" \
|
||||
-v "${TEST_DIR}/backup_large_zst:/backup" \
|
||||
mailcow-backup-test \
|
||||
/bin/tar --use-compress-program="zstd --rsyncable -T${THREADS}" \
|
||||
-cvpf /backup/backup_large.tar.zst . \
|
||||
> /dev/null 2>&1
|
||||
|
||||
# Backup with pigz
|
||||
docker run --rm \
|
||||
-w /data \
|
||||
-v "${TEST_DIR}/large_data:/data:ro" \
|
||||
-v "${TEST_DIR}/backup_large_gz:/backup" \
|
||||
mailcow-backup-test \
|
||||
/bin/tar --use-compress-program="pigz --rsyncable -p ${THREADS}" \
|
||||
-cvpf /backup/backup_large.tar.gz . \
|
||||
> /dev/null 2>&1
|
||||
|
||||
zst_large_size=$(stat -f%z "${TEST_DIR}/backup_large_zst/backup_large.tar.zst" 2>/dev/null || stat -c%s "${TEST_DIR}/backup_large_zst/backup_large.tar.zst" 2>/dev/null || echo "0")
|
||||
gz_large_size=$(stat -f%z "${TEST_DIR}/backup_large_gz/backup_large.tar.gz" 2>/dev/null || stat -c%s "${TEST_DIR}/backup_large_gz/backup_large.tar.gz" 2>/dev/null || echo "0")
|
||||
|
||||
if [[ ${zst_large_size} -gt 0 ]] && [[ ${gz_large_size} -gt 0 ]]; then
|
||||
large_improvement=$(echo "scale=2; (${gz_large_size} - ${zst_large_size}) * 100 / ${gz_large_size}" | bc)
|
||||
|
||||
echo " .tar.gz compressed: $(echo "scale=2; ${gz_large_size} / 1024 / 1024" | bc) MB"
|
||||
echo " .tar.zst compressed: $(echo "scale=2; ${zst_large_size} / 1024 / 1024" | bc) MB"
|
||||
echo " Improvement: ${large_improvement}% smaller with zstd"
|
||||
else
|
||||
echo " ✗ Failed to get file sizes"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ $(echo "${large_improvement} > 0" | bc) -eq 1 ]]; then
|
||||
echo "✓ zstd provides better compression on realistic data"
|
||||
else
|
||||
echo "⚠ zstd compression similar or worse than gzip (unusual but not critical)"
|
||||
fi
|
||||
|
||||
# Test 12: Thread scaling test
|
||||
echo ""
|
||||
echo "=== Test 12: Multi-threading verification ==="
|
||||
# This test verifies that different thread counts work (not measuring speed difference)
|
||||
for thread_count in 1 4; do
|
||||
THREADS=${thread_count}
|
||||
result=$(get_archive_info "backup_test" "${TEST_DIR}/backup_zst")
|
||||
if [[ "${result}" =~ "-T${thread_count}" ]]; then
|
||||
echo "✓ Thread count ${thread_count} correctly configured"
|
||||
else
|
||||
echo "✗ Thread count not properly applied"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "=== All tests passed! ==="
|
||||
echo ""
|
||||
echo "Summary:"
|
||||
echo " ✓ zstd compression working"
|
||||
echo " ✓ pigz compression working (legacy)"
|
||||
echo " ✓ zstd decompression working"
|
||||
echo " ✓ pigz decompression working (backward compatible)"
|
||||
echo " ✓ Archive detection working"
|
||||
echo " ✓ Content integrity verified"
|
||||
echo " ✓ Format priority correct (.tar.zst preferred)"
|
||||
echo " ✓ Error handling for missing files"
|
||||
echo " ✓ Error handling for empty directories"
|
||||
echo " ✓ Multi-threading configuration verified"
|
||||
echo " ✓ Large file compression: ${large_improvement}% improvement"
|
||||
echo " ✓ Small file compression: ${improvement}% improvement"
|
||||
@@ -25,6 +25,6 @@ services:
|
||||
- /var/run/mysqld/mysqld.sock:/var/run/mysqld/mysqld.sock
|
||||
|
||||
mysql-mailcow:
|
||||
image: alpine:3.20
|
||||
image: alpine:3.23
|
||||
command: /bin/true
|
||||
restart: "no"
|
||||
|
||||
47
update.sh
47
update.sh
@@ -20,11 +20,28 @@ fi
|
||||
BRANCH="$(cd "${SCRIPT_DIR}" && git rev-parse --abbrev-ref HEAD)"
|
||||
|
||||
MODULE_DIR="${SCRIPT_DIR}/_modules"
|
||||
# Calculate hash before fetch
|
||||
if [[ -d "${MODULE_DIR}" && -n "$(ls -A "${MODULE_DIR}" 2>/dev/null)" ]]; then
|
||||
MODULES_HASH_BEFORE=$(find "${MODULE_DIR}" -type f -exec sha256sum {} \; 2>/dev/null | sort | sha256sum | awk '{print $1}')
|
||||
else
|
||||
MODULES_HASH_BEFORE="EMPTY"
|
||||
fi
|
||||
|
||||
echo -e "\e[33mFetching latest _modules from origin/${BRANCH}…\e[0m"
|
||||
git fetch origin "${BRANCH}"
|
||||
git checkout "origin/${BRANCH}" -- _modules
|
||||
|
||||
if [[ ! -d "${MODULE_DIR}" || -z "$(ls -A "${MODULE_DIR}")" ]]; then
|
||||
echo -e "\e[33m_modules is missing or empty – fetching all Modules from origin/${BRANCH}…\e[0m"
|
||||
git fetch origin "${BRANCH}"
|
||||
git checkout "origin/${BRANCH}" -- _modules
|
||||
echo -e "\e[33mDone. Please restart the script...\e[0m"
|
||||
echo -e "\e[31mError: _modules is still missing or empty after fetch!\e[0m"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# Calculate hash after fetch
|
||||
MODULES_HASH_AFTER=$(find "${MODULE_DIR}" -type f -exec sha256sum {} \; 2>/dev/null | sort | sha256sum | awk '{print $1}')
|
||||
|
||||
# Check if modules changed
|
||||
if [[ "${MODULES_HASH_BEFORE}" != "${MODULES_HASH_AFTER}" ]]; then
|
||||
echo -e "\e[33m_modules have been updated. Please restart the update script.\e[0m"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
@@ -320,28 +337,6 @@ if [ ! "$DEV" ]; then
|
||||
chmod +x update.sh
|
||||
EXIT_COUNT+=1
|
||||
fi
|
||||
|
||||
MODULE_DIR="$(dirname "$0")/_modules"
|
||||
echo -e "\e[32mChecking for updates in _modules...\e[0m"
|
||||
if [ ! -d "${MODULE_DIR}" ] || [ -z "$(ls -A "${MODULE_DIR}")" ]; then
|
||||
echo -e "\e[33m_modules missing or empty — fetching from origin...\e[0m"
|
||||
git checkout "origin/${BRANCH}" -- _modules
|
||||
else
|
||||
OLD_SUM="$(find "${MODULE_DIR}" -type f -exec sha1sum {} \; | sort | sha1sum)"
|
||||
git fetch origin
|
||||
git checkout "origin/${BRANCH}" -- _modules
|
||||
NEW_SUM="$(find "${MODULE_DIR}" -type f -exec sha1sum {} \; | sort | sha1sum)"
|
||||
|
||||
if [[ "${OLD_SUM}" != "${NEW_SUM}" ]]; then
|
||||
EXIT_COUNT+=1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ${EXIT_COUNT} -ge 1 ]; then
|
||||
echo "Changes for the update Script, please run this script again, exiting!"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
if [ ! "$FORCE" ]; then
|
||||
|
||||
Reference in New Issue
Block a user