浏览代码

Merge remote-tracking branch 'origin/staging' into nightly

FreddleSpl0it 2 周之前
父节点
当前提交
ac90ecaf4f

+ 1 - 1
.github/workflows/pr_to_nightly.yml

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

+ 2 - 1
_modules/scripts/core.sh

@@ -124,7 +124,7 @@ prefetch_images() {
 }
 
 docker_garbage() {
-  SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+  SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )/../.." && pwd )"
   IMGS_TO_DELETE=()
 
   declare -A IMAGES_INFO
@@ -185,6 +185,7 @@ detect_major_update() {
     MAJOR_VERSIONS=(
       "2025-02"
       "2025-03"
+      "2025-08"
     )
 
     current_version=""

+ 2 - 2
_modules/scripts/ipv6_controller.sh

@@ -41,7 +41,7 @@ docker_daemon_edit(){
     ! _has_kv ipv6 true       && MISSING+=("ipv6: true")
     ! grep -Eq '"fixed-cidr-v6"\s*:\s*".+"' "$DOCKER_DAEMON_CONFIG" \
                               && MISSING+=('fixed-cidr-v6: "fd00:dead:beef:c0::/80"')
-    if [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -ge 27 ]]; then
+    if [[ -n "$DOCKER_MAJOR" && "$DOCKER_MAJOR" -le 27 ]]; then
       _has_kv ipv6 true && ! _has_kv ip6tables true && MISSING+=("ip6tables: true")
       ! _has_kv experimental true                 && MISSING+=("experimental: true")
     fi
@@ -105,7 +105,7 @@ EOF
 }
 EOF
       fi
-      echo "${GREEN}Created $DOCKER_DAEMON_CONFIG with IPv6 settings.${NC}"
+      echo -e "${GREEN}Created $DOCKER_DAEMON_CONFIG with IPv6 settings.${NC}"
       echo "Restarting Docker..."
       (command -v systemctl &>/dev/null && systemctl restart docker) || service docker restart
       echo "Docker restarted."

+ 4 - 13
_modules/scripts/new_options.sh

@@ -49,7 +49,6 @@ adapt_new_options() {
   "DOVECOT_MASTER_PASS"
   "MAILCOW_PASS_SCHEME"
   "ADDITIONAL_SERVER_NAMES"
-  "ACME_CONTACT"
   "WATCHDOG_VERBOSE"
   "WEBAUTHN_ONLY_TRUSTED_VENDORS"
   "SPAMHAUS_DQS_KEY"
@@ -95,7 +94,7 @@ adapt_new_options() {
             echo '# Max log lines per service to keep in Redis logs' >> mailcow.conf
             echo "LOG_LINES=9999" >> mailcow.conf
             ;;
-        
+
         IPV4_NETWORK)
             echo '# Internal IPv4 /24 subnet, format n.n.n. (expands to n.n.n.0/24)' >> mailcow.conf
             echo "IPV4_NETWORK=172.22.1" >> mailcow.conf
@@ -238,14 +237,6 @@ adapt_new_options() {
             echo '# Comma separated list without spaces! Example: ADDITIONAL_SERVER_NAMES=a.b.c,d.e.f' >> mailcow.conf
             echo 'ADDITIONAL_SERVER_NAMES=' >> mailcow.conf
             ;;
-        ACME_CONTACT)
-            echo '# Lets Encrypt registration contact information' >> mailcow.conf
-            echo '# Optional: Leave empty for none' >> mailcow.conf
-            echo '# This value is only used on first order!' >> mailcow.conf
-            echo '# Setting it at a later point will require the following steps:' >> mailcow.conf
-            echo '# https://docs.mailcow.email/troubleshooting/debug-reset_tls/' >> mailcow.conf
-            echo 'ACME_CONTACT=' >> mailcow.conf
-            ;;
         WEBAUTHN_ONLY_TRUSTED_VENDORS)
             echo "# WebAuthn device manufacturer verification" >> mailcow.conf
             echo '# After setting WEBAUTHN_ONLY_TRUSTED_VENDORS=y only devices from trusted manufacturers are allowed' >> mailcow.conf
@@ -285,7 +276,7 @@ adapt_new_options() {
             echo '# A COMPLETE DOCKER STACK REBUILD (compose down && compose up -d) IS NEEDED TO APPLY THIS.' >> mailcow.conf
             echo ENABLE_IPV6=${IPV6_BOOL} >> mailcow.conf
             ;;
-    
+
         SKIP_CLAMD)
             echo '# Skip ClamAV (clamd-mailcow) anti-virus (Rspamd will auto-detect a missing ClamAV container) - y/n' >> mailcow.conf
             echo 'SKIP_CLAMD=n' >> mailcow.conf
@@ -295,11 +286,11 @@ adapt_new_options() {
             echo '# Skip Olefy (olefy-mailcow) anti-virus for Office documents (Rspamd will auto-detect a missing Olefy container) - y/n' >> mailcow.conf
             echo 'SKIP_OLEFY=n' >> mailcow.conf
             ;;
-        
+
         REDISPASS)
             echo "REDISPASS=$(LC_ALL=C </dev/urandom tr -dc A-Za-z0-9 2>/dev/null | head -c 28)" >> mailcow.conf
             ;;
-                  
+
         *)
             echo "${option}=" >> mailcow.conf
             ;;

+ 1 - 1
data/Dockerfiles/dovecot/Dockerfile

@@ -3,7 +3,7 @@ FROM alpine:3.21
 LABEL maintainer="The Infrastructure Company GmbH <info@servercow.de>"
 
 # renovate: datasource=github-releases depName=tianon/gosu versioning=semver-coerced extractVersion=^(?<version>.*)$
-ARG GOSU_VERSION=1.16
+ARG GOSU_VERSION=1.17
 
 ENV LANG=C.UTF-8
 ENV LC_ALL=C.UTF-8

+ 1 - 1
data/Dockerfiles/dovecot/quarantine_notify.py

@@ -76,7 +76,7 @@ try:
 
   def notify_rcpt(rcpt, msg_count, quarantine_acl, category):
     if category == "add_header": category = "add header"
-    meta_query = query_mysql('SELECT SHA2(CONCAT(id, qid), 256) AS qhash, id, subject, score, sender, created, action FROM quarantine WHERE notified = 0 AND rcpt = "%s" AND score < %f AND (action = "%s" OR "all" = "%s")' % (rcpt, max_score, category, category))
+    meta_query = query_mysql('SELECT `qhash`, id, subject, score, sender, created, action FROM quarantine WHERE notified = 0 AND rcpt = "%s" AND score < %f AND (action = "%s" OR "all" = "%s")' % (rcpt, max_score, category, category))
     print("%s: %d of %d messages qualify for notification" % (rcpt, len(meta_query), msg_count))
     if len(meta_query) == 0:
       return

+ 3 - 3
data/Dockerfiles/phpfpm/Dockerfile

@@ -3,15 +3,15 @@ FROM php:8.2-fpm-alpine3.21
 LABEL maintainer = "The Infrastructure Company GmbH <info@servercow.de>"
 
 # renovate: datasource=github-tags depName=krakjoe/apcu versioning=semver-coerced extractVersion=^v(?<version>.*)$
-ARG APCU_PECL_VERSION=5.1.24
+ARG APCU_PECL_VERSION=5.1.26
 # renovate: datasource=github-tags depName=Imagick/imagick versioning=semver-coerced extractVersion=(?<version>.*)$
 ARG IMAGICK_PECL_VERSION=3.8.0
 # renovate: datasource=github-tags depName=php/pecl-mail-mailparse versioning=semver-coerced extractVersion=^v(?<version>.*)$
 ARG MAILPARSE_PECL_VERSION=3.1.8
 # renovate: datasource=github-tags depName=php-memcached-dev/php-memcached versioning=semver-coerced extractVersion=^v(?<version>.*)$
-ARG MEMCACHED_PECL_VERSION=3.2.0
+ARG MEMCACHED_PECL_VERSION=3.3.0
 # renovate: datasource=github-tags depName=phpredis/phpredis versioning=semver-coerced extractVersion=(?<version>.*)$
-ARG REDIS_PECL_VERSION=6.1.0
+ARG REDIS_PECL_VERSION=6.2.0
 # renovate: datasource=github-tags depName=composer/composer versioning=semver-coerced extractVersion=(?<version>.*)$
 ARG COMPOSER_VERSION=2.8.6
 

+ 1 - 1
data/Dockerfiles/rspamd/Dockerfile

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

+ 2 - 2
data/conf/rspamd/lua/rspamd.local.lua

@@ -102,7 +102,7 @@ rspamd_config:register_symbol({
       local rcpt_split = rspamd_str_split(rcpt['addr'], '@')
       if #rcpt_split == 2 then
         if rcpt_split[1] == 'postmaster' then
-          task:set_pre_result('accept', 'whitelisting postmaster smtp rcpt')
+          task:set_pre_result('accept', 'whitelisting postmaster smtp rcpt', 'postmaster')
           return
         end
       end
@@ -167,7 +167,7 @@ rspamd_config:register_symbol({
         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")
-            task:set_pre_result('accept', 'ip matched with forward hosts')
+            task:set_pre_result('accept', 'ip matched with forward hosts', 'keep_spam')
           end
         end
       end

+ 3 - 0
data/conf/rspamd/meta_exporter/pipe.php

@@ -236,6 +236,9 @@ foreach ($rcpt_final_mailboxes as $rcpt_final) {
       ':action' => $action,
       ':fuzzy_hashes' => $fuzzy
     ));
+    $lastId = $pdo->lastInsertId();
+    $stmt_update = $pdo->prepare("UPDATE `quarantine` SET `qhash` = SHA2(CONCAT(`id`, `qid`), 256) WHERE `id` = :id");
+    $stmt_update->execute(array(':id' => $lastId));
     $stmt = $pdo->prepare('DELETE FROM `quarantine` WHERE `rcpt` = :rcpt AND `id` NOT IN (
       SELECT `id`
       FROM (

+ 1 - 0
data/web/edit.php

@@ -125,6 +125,7 @@ if (isset($_SESSION['mailcow_cc_role'])) {
           'mailbox' => $mailbox,
           'rl' => $rl,
           'pushover_data' => $pushover_data,
+          'get_tagging_options' => mailbox('get', 'delimiter_action', $mailbox),
           'quarantine_notification' => $quarantine_notification,
           'quarantine_category' => $quarantine_category,
           'get_tls_policy' => $get_tls_policy,

二进制
data/web/favicon.png


+ 17 - 0
data/web/inc/functions.mailbox.inc.php

@@ -1223,6 +1223,14 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
           $stmt->execute(array(
             ':username' => $username
           ));
+          // save delimiter_action
+          if (isset($_data['tagged_mail_handler'])) {
+            mailbox('edit', 'delimiter_action', array(
+              'username' => $username,
+              'tagged_mail_handler' => $_data['tagged_mail_handler']
+            ));
+          }
+
           // save tags
           foreach($tags as $index => $tag){
             if (empty($tag)) continue;
@@ -1613,6 +1621,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
           $attr = array();
           $attr["quota"]                       = isset($_data['quota']) ? intval($_data['quota']) * 1048576 : 0;
           $attr['tags']                        = (isset($_data['tags'])) ? $_data['tags'] : array();
+          $attr["tagged_mail_handler"]         = (!empty($_data['tagged_mail_handler'])) ? $_data['tagged_mail_handler'] : strval($MAILBOX_DEFAULT_ATTRIBUTES['tagged_mail_handler']);
           $attr["quarantine_notification"]     = (!empty($_data['quarantine_notification'])) ? $_data['quarantine_notification'] : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_notification']);
           $attr["quarantine_category"]         = (!empty($_data['quarantine_category'])) ? $_data['quarantine_category'] : strval($MAILBOX_DEFAULT_ATTRIBUTES['quarantine_category']);
           $attr["rl_frame"]                    = (!empty($_data['rl_frame'])) ? $_data['rl_frame'] : "s";
@@ -3259,6 +3268,13 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
               );
               return false;
             }
+            // save delimiter_action
+            if (isset($_data['tagged_mail_handler'])) {
+              mailbox('edit', 'delimiter_action', array(
+                'username' => $username,
+                'tagged_mail_handler' => $_data['tagged_mail_handler']
+              ));
+            }
             // save tags
             foreach($tags as $index => $tag){
               if (empty($tag)) continue;
@@ -3604,6 +3620,7 @@ function mailbox($_action, $_type, $_data = null, $_extra = null) {
             $attr = array();
             $attr["quota"]                       = isset($_data['quota']) ? intval($_data['quota']) * 1048576 : 0;
             $attr['tags']                        = (isset($_data['tags'])) ? $_data['tags'] : $is_now['tags'];
+            $attr["tagged_mail_handler"]         = (!empty($_data['tagged_mail_handler'])) ? $_data['tagged_mail_handler'] : $is_now['tagged_mail_handler'];
             $attr["quarantine_notification"]     = (!empty($_data['quarantine_notification'])) ? $_data['quarantine_notification'] : $is_now['quarantine_notification'];
             $attr["quarantine_category"]         = (!empty($_data['quarantine_category'])) ? $_data['quarantine_category'] : $is_now['quarantine_category'];
             $attr["rl_frame"]                    = (!empty($_data['rl_frame'])) ? $_data['rl_frame'] : $is_now['rl_frame'];

+ 3 - 3
data/web/inc/functions.quarantine.inc.php

@@ -22,7 +22,7 @@ function quarantine($_action, $_data = null) {
         return false;
       }
       $stmt = $pdo->prepare('SELECT `id` FROM `quarantine` LEFT OUTER JOIN `user_acl` ON `user_acl`.`username` = `rcpt`
-        WHERE SHA2(CONCAT(`id`, `qid`), 256) = :hash
+        WHERE `qhash` = :hash
           AND user_acl.quarantine = 1
           AND rcpt IN (SELECT username FROM mailbox)');
       $stmt->execute(array(':hash' => $hash));
@@ -65,7 +65,7 @@ function quarantine($_action, $_data = null) {
         return false;
       }
       $stmt = $pdo->prepare('SELECT `id` FROM `quarantine` LEFT OUTER JOIN `user_acl` ON `user_acl`.`username` = `rcpt`
-        WHERE SHA2(CONCAT(`id`, `qid`), 256) = :hash
+        WHERE `qhash` = :hash
           AND `user_acl`.`quarantine` = 1
           AND `username` IN (SELECT `username` FROM `mailbox`)');
       $stmt->execute(array(':hash' => $hash));
@@ -833,7 +833,7 @@ function quarantine($_action, $_data = null) {
         )));
         return false;
       }
-      $stmt = $pdo->prepare('SELECT * FROM `quarantine` WHERE SHA2(CONCAT(`id`, `qid`), 256) = :hash');
+      $stmt = $pdo->prepare('SELECT * FROM `quarantine` WHERE `qhash` = :hash');
       $stmt->execute(array(':hash' => $hash));
       return $stmt->fetch(PDO::FETCH_ASSOC);
     break;

+ 9 - 1
data/web/inc/init_db.inc.php

@@ -4,7 +4,7 @@ function init_db_schema()
   try {
     global $pdo;
 
-    $db_version = "27012025_1555";
+    $db_version = "06082025_1611";
 
     $stmt = $pdo->query("SHOW TABLES LIKE 'versions'");
     $num_results = count($stmt->fetchAll(PDO::FETCH_ASSOC));
@@ -345,10 +345,14 @@ function init_db_schema()
           "notified" => "TINYINT(1) NOT NULL DEFAULT '0'",
           "created" => "DATETIME(0) NOT NULL DEFAULT NOW(0)",
           "user" => "VARCHAR(255) NOT NULL DEFAULT 'unknown'",
+          "qhash" => "VARCHAR(64)",
         ),
         "keys" => array(
           "primary" => array(
             "" => array("id")
+          ),
+          "key" => array(
+            "qhash" => array("qhash")
           )
         ),
         "attr" => "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC"
@@ -1482,6 +1486,10 @@ function init_db_schema()
         'msg' => 'db_init_complete'
       );
     }
+
+    // fill quarantine.qhash
+    $pdo->query("UPDATE `quarantine` SET `qhash` = SHA2(CONCAT(`id`, `qid`), 256) WHERE ISNULL(`qhash`)");
+
   } catch (PDOException $e) {
     if (php_sapi_name() == "cli") {
       echo "DB initialization failed: " . print_r($e, true) . PHP_EOL;

+ 77 - 71
data/web/inc/vars.inc.php

@@ -186,6 +186,12 @@ $MAILBOX_DEFAULT_ATTRIBUTES['force_pw_update'] = false;
 // Enable SOGo access - Users will be redirected to SOGo after login (set to false to disable redirect by default)
 $MAILBOX_DEFAULT_ATTRIBUTES['sogo_access'] = true;
 
+// How to handle tagged emails
+// none      - No special handling
+// subfolder - Create subfolder under INBOX (e.g. "INBOX/Facebook")
+// subject   - Add tag to subject (e.g. "[Facebook] Subject")
+$MAILBOX_DEFAULT_ATTRIBUTES['tagged_mail_handler'] = "none";
+
 // Send notification when quarantine is not empty (never, hourly, daily, weekly)
 $MAILBOX_DEFAULT_ATTRIBUTES['quarantine_notification'] = 'hourly';
 
@@ -257,57 +263,57 @@ $RSPAMD_MAPS = array(
 
 $IMAPSYNC_OPTIONS = array(
   'whitelist' => array(
-    'abort',     
-    'authmd51',        
-    'authmd52',           
+    'abort',
+    'authmd51',
+    'authmd52',
     'authmech1',
     'authmech2',
-    'authuser1', 
-    'authuser2', 
-    'debug',   
-    'debugcontent', 
-    'debugcrossduplicates', 
-    'debugflags',    
-    'debugfolders',            
-    'debugimap',    
-    'debugimap1',     
-    'debugimap2',   
-    'debugmemory',       
-    'debugssl',              
+    'authuser1',
+    'authuser2',
+    'debug',
+    'debugcontent',
+    'debugcrossduplicates',
+    'debugflags',
+    'debugfolders',
+    'debugimap',
+    'debugimap1',
+    'debugimap2',
+    'debugmemory',
+    'debugssl',
     'delete1emptyfolders',
-    'delete2folders',    
-    'disarmreadreceipts', 
+    'delete2folders',
+    'disarmreadreceipts',
     'domain1',
     'domain2',
-    'domino1',   
-    'domino2',  
+    'domino1',
+    'domino2',
     'dry',
     'errorsmax',
-    'exchange1',   
-    'exchange2',   
+    'exchange1',
+    'exchange2',
     'exitwhenover',
     'expunge1',
-    'f1f2',  
-    'filterbuggyflags',  
+    'f1f2',
+    'filterbuggyflags',
     'folder',
     'folderfirst',
     'folderlast',
     'folderrec',
-    'gmail1',     
-    'gmail2',    
-    'idatefromheader',   
+    'gmail1',
+    'gmail2',
+    'idatefromheader',
     'include',
     'inet4',
     'inet6',
-    'justconnect',  
-    'justfolders',  
-    'justfoldersizes',  
-    'justlogin',   
-    'keepalive1',   
-    'keepalive2',   
+    'justconnect',
+    'justfolders',
+    'justfoldersizes',
+    'justlogin',
+    'keepalive1',
+    'keepalive2',
     'log',
     'logdir',
-    'logfile',        
+    'logfile',
     'maxbytesafter',
     'maxlinelength',
     'maxmessagespersecond',
@@ -315,62 +321,62 @@ $IMAPSYNC_OPTIONS = array(
     'maxsleep',
     'minage',
     'minsize',
-    'noabletosearch', 
-    'noabletosearch1',  
-    'noabletosearch2',   
-    'noexpunge1',        
-    'noexpunge2',   
+    'noabletosearch',
+    'noabletosearch1',
+    'noabletosearch2',
+    'noexpunge1',
+    'noexpunge2',
     'nofoldersizesatend',
-    'noid',       
-    'nolog',  
-    'nomixfolders',          
-    'noresyncflags',   
-    'nossl1',   
-    'nossl2',            
-    'nosyncacls',      
-    'notls1', 
-    'notls2',              
-    'nouidexpunge2',   
-    'nousecache',      
+    'noid',
+    'nolog',
+    'nomixfolders',
+    'noresyncflags',
+    'nossl1',
+    'nossl2',
+    'nosyncacls',
+    'notls1',
+    'notls2',
+    'nouidexpunge2',
+    'nousecache',
     'oauthaccesstoken1',
     'oauthaccesstoken2',
     'oauthdirect1',
     'oauthdirect2',
-    'office1',    
-    'office2',      
-    'pidfile', 
-    'pidfilelocking', 
+    'office1',
+    'office2',
+    'pidfile',
+    'pidfilelocking',
     'prefix1',
     'prefix2',
-    'proxyauth1',  
-    'proxyauth2',         
-    'resyncflags',     
-    'resynclabels',     
-    'search', 
+    'proxyauth1',
+    'proxyauth2',
+    'resyncflags',
+    'resynclabels',
+    'search',
     'search1',
-    'search2', 
+    'search2',
     'sep1',
     'sep2',
     'showpasswords',
     'skipemptyfolders',
-    'ssl2',            
+    'ssl2',
     'sslargs1',
-    'sslargs2', 
+    'sslargs2',
     'subfolder1',
-    'subscribe',   
+    'subscribe',
     'subscribed',
     'syncacls',
     'syncduplicates',
     'syncinternaldates',
-    'synclabels', 
-    'tests',     
-    'testslive',       
-    'testslive6',     
-    'tls2',             
-    'truncmess',  
-    'usecache', 
-    'useheader',  
-    'useuid'    
+    'synclabels',
+    'tests',
+    'testslive',
+    'testslive6',
+    'tls2',
+    'truncmess',
+    'usecache',
+    'useheader',
+    'useuid'
   ),
   'blacklist' => array(
     'skipmess',

+ 18 - 0
data/web/js/site/mailbox.js

@@ -269,6 +269,24 @@ $(document).ready(function() {
   function setMailboxTemplateData(template){
     $("#addInputQuota").val(template.quota / 1048576);
 
+    if (template.tagged_mail_handler === "subfolder"){
+      $('#tagged_mail_handler_subfolder').prop('checked', true);
+      $('#tagged_mail_handler_subject').prop('checked', false);
+      $('#tagged_mail_handler_none').prop('checked', false);
+    } else if(template.tagged_mail_handler === "subject"){
+      $('#tagged_mail_handler_subfolder').prop('checked', false);
+      $('#tagged_mail_handler_subject').prop('checked', true);
+      $('#tagged_mail_handler_none').prop('checked', false);
+    } else if(template.tagged_mail_handler === "none"){
+      $('#tagged_mail_handler_subfolder').prop('checked', false);
+      $('#tagged_mail_handler_subject').prop('checked', false);
+      $('#tagged_mail_handler_none').prop('checked', true);
+    } else {
+      $('#tagged_mail_handler_subfolder').prop('checked', false);
+      $('#tagged_mail_handler_subject').prop('checked', false);
+      $('#tagged_mail_handler_none').prop('checked', true);
+    }
+
     if (template.quarantine_notification === "never"){
       $('#quarantine_notification_never').prop('checked', true);
       $('#quarantine_notification_hourly').prop('checked', false);

+ 17 - 0
data/web/templates/edit/mailbox-templates.twig

@@ -36,6 +36,23 @@
         <small class="text-muted">0 = ∞</small>
       </div>
     </div>
+    <div class="row mb-2">
+      <label class="control-label col-sm-2">{{ lang.user.tag_handling }}</label>
+      <div class="col-sm-10">
+        <div class="btn-group">
+          <input type="radio" class="btn-check" name="tagged_mail_handler" id="tagged_mail_handler_subfolder" autocomplete="off" value="subfolder" {% if template.attributes.tagged_mail_handler == 'subfolder' %}checked{% endif %}>
+          <label class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-light" for="tagged_mail_handler_subfolder">{{ lang.user.tag_in_subfolder }}</label>
+
+          <input type="radio" class="btn-check" name="tagged_mail_handler" id="tagged_mail_handler_subject" autocomplete="off" value="subject" {% if template.attributes.tagged_mail_handler == 'subject' %}checked{% endif %}>
+          <label class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-light" for="tagged_mail_handler_subject">{{ lang.user.tag_in_subject }}</label>
+
+          <input type="radio" class="btn-check" name="tagged_mail_handler" id="tagged_mail_handler_none" autocomplete="off" value="none" {% if template.attributes.tagged_mail_handler == 'none' %}checked{% endif %}>
+          <label class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-light" for="tagged_mail_handler_none">{{ lang.user.tag_in_none }}</label>
+        </div>
+        <p class="text-muted"><small>{{ lang.user.tag_help_explain|raw }}</small></p>
+        <p class="text-muted"><small>{{ lang.user.tag_help_example|raw }}</small></p>
+      </div>
+    </div>
     <div class="row mb-2">
       <label class="control-label col-sm-2">{{ lang.user.quarantine_notification }}</label>
       <div class="col-sm-10">

+ 29 - 2
data/web/templates/edit/mailbox.twig

@@ -117,7 +117,7 @@
                   <div class="row mb-2">
                     <label class="control-label col-sm-2" for="relayhost">{{ lang.edit.relayhost }}</label>
                     <div class="col-sm-10">
-                      <select data-acl="{{ acl.mailbox_relayhost }}" data-live-search="true" id="relayhost" name="relayhost" class="form-control mb-4">
+                      <select data-acl="{{ acl.mailbox_relayhost }}" data-live-search="true" id="relayhost" name="relayhost" class="form-control">
                         {% for rlyhost in rlyhosts %}
                           <option
                             style="{% if rlyhost.active != '1' %}background: #ff4136; color: #fff{% endif %}"
@@ -131,7 +131,34 @@
                         </option>
                       </select>
                       <p class="d-block d-sm-none" style="margin: 0;padding: 0">&nbsp;</p>
-                      <small class="text-muted d-block">{{ lang.edit.mailbox_relayhost_info }}</small>
+                      <small class="text-muted d-block mb-4">{{ lang.edit.mailbox_relayhost_info }}</small>
+                    </div>
+                  </div>
+                  <div class="row mb-2">
+                    <label class="control-label col-sm-2">{{ lang.user.tag_handling }}</label>
+                    <div class="col-sm-10">
+                      <div class="btn-group" data-acl="{{ acl.delimiter_action }}">
+                        <button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline{% if get_tagging_options == 'subfolder' %} btn-dark{% else %} btn-light{% endif %}"
+                        data-action="edit_selected"
+                        data-item="{{ mailbox }}"
+                        data-id="delimiter_action"
+                        data-api-url='edit/delimiter_action'
+                        data-api-attr='{"tagged_mail_handler":"subfolder"}'>{{ lang.user.tag_in_subfolder }}</button>
+                        <button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline{% if get_tagging_options == 'subject' %} btn-dark{% else %} btn-light{% endif %}"
+                        data-action="edit_selected"
+                        data-item="{{ mailbox }}"
+                        data-id="delimiter_action"
+                        data-api-url='edit/delimiter_action'
+                        data-api-attr='{"tagged_mail_handler":"subject"}'>{{ lang.user.tag_in_subject }}</button>
+                        <button type="button" class="btn btn-sm btn-xs-third d-block d-sm-inline{% if get_tagging_options == 'none' %} btn-dark{% else %} btn-light{% endif %}"
+                        data-action="edit_selected"
+                        data-item="{{ mailbox }}"
+                        data-id="delimiter_action"
+                        data-api-url='edit/delimiter_action'
+                        data-api-attr='{"tagged_mail_handler":"none"}'>{{ lang.user.tag_in_none }}</button>
+                      </div>
+                      <p class="text-muted"><small>{{ lang.user.tag_help_explain|raw }}</small></p>
+                      <p class="text-muted"><small>{{ lang.user.tag_help_example|raw }}</small></p>
                     </div>
                   </div>
                   <div class="row mb-2">

+ 34 - 0
data/web/templates/modals/mailbox.twig

@@ -76,6 +76,23 @@
               <div class="badge fs-5 bg-warning addInputQuotaExhausted" style="display:none;">{{ lang.warning.quota_exceeded_scope }}</div>
             </div>
           </div>
+          <div class="row mb-2">
+            <label class="control-label col-sm-2 text-sm-end text-sm-end">{{ lang.user.tag_handling }}</label>
+            <div class="col-sm-10">
+              <div class="btn-group">
+                <input type="radio" class="btn-check" name="tagged_mail_handler" id="tagged_mail_handler_subfolder" autocomplete="off" value="subfolder">
+                <label class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-light" for="tagged_mail_handler_subfolder">{{ lang.user.tag_in_subfolder }}</label>
+
+                <input type="radio" class="btn-check" name="tagged_mail_handler" id="tagged_mail_handler_subject" autocomplete="off" value="subject">
+                <label class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-light" for="tagged_mail_handler_subject">{{ lang.user.tag_in_subject }}</label>
+
+                <input type="radio" class="btn-check" name="tagged_mail_handler" id="tagged_mail_handler_none" autocomplete="off" value="none">
+                <label class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-light" for="tagged_mail_handler_none">{{ lang.user.tag_in_none }}</label>
+              </div>
+              <p class="text-muted"><small>{{ lang.user.tag_help_explain|raw }}</small></p>
+              <p class="text-muted"><small>{{ lang.user.tag_help_example|raw }}</small></p>
+            </div>
+          </div>
           <div class="row mb-2">
             <label class="control-label col-sm-2 text-sm-end text-sm-end">{{ lang.user.quarantine_notification }}</label>
             <div class="col-sm-10">
@@ -246,6 +263,23 @@
               <small class="text-muted">0 = ∞</small>
             </div>
           </div>
+          <div class="row mb-2">
+            <label class="control-label col-sm-2 text-sm-end text-sm-end">{{ lang.user.tag_handling }}</label>
+            <div class="col-sm-10">
+              <div class="btn-group">
+                <input type="radio" class="btn-check" name="tagged_mail_handler" id="template_tagged_mail_handler_subfolder" autocomplete="off" value="subfolder">
+                <label class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-light" for="template_tagged_mail_handler_subfolder">{{ lang.user.tag_in_subfolder }}</label>
+
+                <input type="radio" class="btn-check" name="tagged_mail_handler" id="template_tagged_mail_handler_subject" autocomplete="off" value="subject">
+                <label class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-light" for="template_tagged_mail_handler_subject">{{ lang.user.tag_in_subject }}</label>
+
+                <input type="radio" class="btn-check" name="tagged_mail_handler" id="template_tagged_mail_handler_none" autocomplete="off" value="none">
+                <label class="btn btn-sm btn-xs-quart d-block d-sm-inline btn-light" for="template_tagged_mail_handler_none">{{ lang.user.tag_in_none }}</label>
+              </div>
+              <p class="text-muted"><small>{{ lang.user.tag_help_explain|raw }}</small></p>
+              <p class="text-muted"><small>{{ lang.user.tag_help_example|raw }}</small></p>
+            </div>
+          </div>
           <div class="row mb-2">
             <label class="control-label col-sm-2 text-sm-end text-sm-end">{{ lang.user.quarantine_notification }}</label>
             <div class="col-sm-10">

+ 1 - 1
docker-compose.yml

@@ -84,7 +84,7 @@ services:
             - clamd
 
     rspamd-mailcow:
-      image: ghcr.io/mailcow/rspamd:2.2
+      image: ghcr.io/mailcow/rspamd:2.3
       stop_grace_period: 30s
       depends_on:
         - dovecot-mailcow

+ 1 - 2
update.sh

@@ -383,7 +383,6 @@ configure_ipv6
 [[ -f data/conf/nginx/ZZZ-ejabberd.conf ]] && rm data/conf/nginx/ZZZ-ejabberd.conf
 migrate_config_options
 adapt_new_options
-remove_obsolete_options
 
 if [ ! "$DEV" ]; then
   DEFAULT_REPO="https://github.com/mailcow/mailcow-dockerized"
@@ -434,7 +433,7 @@ if [ ! "$DEV" ]; then
     echo "Run $COMPOSE_COMMAND up -d to restart your stack without updates or try again after fixing the mentioned errors."
     exit 1
   fi
-elif [ "$DEV" ]; then
+else
   echo -e "\e[33mDEVELOPER MODE: Not creating a git diff and commiting it to prevent development stuff within a backup diff...\e[0m"
 fi