Pārlūkot izejas kodu

fix: restart dovecot during remote deploy

AI-Co-Authored-By: Codex
chendeben 1 mēnesi atpakaļ
vecāks
revīzija
f82fccd7d6
2 mainītis faili ar 35 papildinājumiem un 5 dzēšanām
  1. 3 1
      scripts/deploy-remote.sh
  2. 32 4
      test/deploy-remote-script.test.js

+ 3 - 1
scripts/deploy-remote.sh

@@ -253,12 +253,14 @@ if [[ "${run_maildir_migration}" == "1" ]]; then
   mail_services_stopped_for_migration=1
   docker compose stop app dovecot </dev/null
   docker compose run --rm --no-deps -T app </dev/null node scripts/migrate-sqlite-maildir.js
+  maildir_cutover_committed=1
   mail_services_stopped_for_migration=0
 else
   echo "Skipping Maildir migration check; set MAILHUB_DEPLOY_RUN_MAILDIR_MIGRATION=1 to run it."
 fi
-maildir_cutover_committed=1
 docker compose up -d </dev/null
+wait_for_compose_health app postfix
+docker compose restart dovecot </dev/null
 wait_for_compose_health app postfix dovecot
 verify_mail_runtime
 MAILHUB_CERT_RESTART=1 ./scripts/sync-tls-certificate.sh </dev/null

+ 32 - 4
test/deploy-remote-script.test.js

@@ -48,9 +48,10 @@ test('checks the Dovecot IMAPS authentication path through Lua passdb', () => {
   assert.match(scriptSource, /mailhub-healthcheck@invalid\.invalid/);
   assert.match(scriptSource, /temporary authentication failure\|unavailable/);
   assert.match(scriptSource, /Dovecot IMAPS authentication path check failed\./);
+  assert.match(scriptSource, /wait_for_compose_health app postfix\s+docker compose restart dovecot <\/dev\/null/);
 });
 
-test('waits for app and postfix health before and after certificate synchronization', { skip: !canRun }, (t) => {
+test('waits for app and postfix before restarting Dovecot and certificate synchronization', { skip: !canRun }, (t) => {
   const fixture = createFixture(t);
   const result = runDeploy(fixture);
 
@@ -62,6 +63,8 @@ test('waits for app and postfix health before and after certificate synchronizat
       'sync:0',
       'health:app-container',
       'health:postfix-container',
+      'health:app-container',
+      'health:postfix-container',
       'health:dovecot-container',
       'sync:1',
       'health:app-container',
@@ -93,10 +96,12 @@ test('skips the Maildir migration maintenance window by default', { skip: !canRu
   const offlineSync = events.indexOf('sync:0');
   const pull = events.indexOf('pull:dovecot');
   const up = events.indexOf('up');
+  const dovecotRestart = events.indexOf('compose-restart:dovecot');
   assert.equal(events.includes('stop:app,dovecot'), false, events.join('\n'));
   assert.equal(events.includes('migrate'), false, events.join('\n'));
   assert.ok(offlineSync >= 0 && offlineSync < up, events.join('\n'));
   assert.ok(pull >= 0 && pull < up, events.join('\n'));
+  assert.ok(up >= 0 && up < dovecotRestart, events.join('\n'));
 });
 
 test('runs the opt-in Maildir migration without consuming the SSH heredoc stdin', { skip: !canRun }, (t) => {
@@ -125,13 +130,32 @@ test('reports the previous revision when deployment fails', { skip: !canRun }, (
     result.stderr,
     /Deployment failed\. Previous revision was previous-revision; inspect the running containers before recovery\./
   );
-  assert.match(result.stderr, /Maildir cutover is already committed; legacy mail services will not be restarted/);
+  assert.doesNotMatch(result.stderr, /Maildir cutover is already committed/);
   const events = readEvents(fixture.logFile);
   assert.equal(events.filter((event) => event === 'sync:0').length, 1);
   assert.equal(events.filter((event) => event === 'sync:1').length, 1);
-  assert.equal(events.filter((event) => event === 'health:app-container').length, 1);
-  assert.equal(events.filter((event) => event === 'health:postfix-container').length, 1);
+  assert.equal(events.filter((event) => event === 'health:app-container').length, 2);
+  assert.equal(events.filter((event) => event === 'health:postfix-container').length, 2);
   assert.equal(events.filter((event) => event === 'health:dovecot-container').length, 1);
+  assert.equal(events.includes('stop:app,dovecot'), false, events.join('\n'));
+  assert.equal(events.at(-1), 'final:ps', events.join('\n'));
+});
+
+test('does not apply Maildir cutover protection to default runtime probe failures', { skip: !canRun }, (t) => {
+  const fixture = createFixture(t);
+  const result = runDeploy(fixture, { runtimeStatus: '29' });
+
+  assert.equal(result.status, 1);
+  assert.match(
+    result.stderr,
+    /Deployment failed\. Previous revision was previous-revision; inspect the running containers before recovery\./
+  );
+  assert.doesNotMatch(result.stderr, /Maildir cutover is already committed/);
+  const events = readEvents(fixture.logFile);
+  assert.equal(events.includes('migrate'), false, events.join('\n'));
+  assert.equal(events.includes('stop:app,dovecot'), false, events.join('\n'));
+  assert.ok(events.includes('compose-restart:dovecot'), events.join('\n'));
+  assert.equal(events.at(-1), 'final:ps', events.join('\n'));
 });
 
 test('stops the app for migration and restarts the previous container if migration fails', { skip: !canRun }, (t) => {
@@ -223,6 +247,10 @@ if [ "$1" = "compose" ] && [ "$2" = "up" ]; then
   printf '%s\\n' 'up' >> "$MAILHUB_DEPLOY_TEST_LOG"
   exit 0
 fi
+if [ "$1" = "compose" ] && [ "$2" = "restart" ]; then
+  printf 'compose-restart:%s\\n' "$3" >> "$MAILHUB_DEPLOY_TEST_LOG"
+  exit 0
+fi
 if [ "$1" = "compose" ] && [ "$2" = "exec" ]; then
   cat >/dev/null
   service=""