Skip to content

Delete user without prompt but keep mailbox data#4565

Merged
georglauterbach merged 1 commit intodocker-mailserver:masterfrom
dano19:master
Sep 8, 2025
Merged

Delete user without prompt but keep mailbox data#4565
georglauterbach merged 1 commit intodocker-mailserver:masterfrom
dano19:master

Conversation

@dano19
Copy link
Copy Markdown
Contributor

@dano19 dano19 commented Sep 3, 2025

Description

Added option to allow to delete email but do not delete email data without prompt using "-n", default and "-y" remains as is.
Its required for UI I made and I want to allow admin to delete email without deleting data.
https://gitlab.com/div-solutions/docker-mailserver-ui

Type of change

  • Improvement (non-breaking change that does improve existing functionality)
  • This change requires a documentation update

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation (README.md or the documentation under docs/)
  • If necessary, I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • I have added information about changes made in this PR to CHANGELOG.md

@polarathene
Copy link
Copy Markdown
Member

This doesn't seem to be necessary?

I think it would be better to avoid the issue entirely by ensuring you run commands non-interactively, without needing to patch support in per command as-needed 😅


You want the default logic of this command, but your concern is the interactive prompt when you call the command non-interactively?:

$ setup email del [email protected]
Do you want to delete the mailbox data as well (removing all mails)? [y/N]

This script handles the prompt by reading from stdin, so you can just pipe in the n input into the commands stdin and it'll be processed:

echo 'n' | setup email del [email protected]
2025-09-04 00:30:46+00:00 INFO  delmailuser: The mailbox data will not be deleted.
2025-09-04 00:30:46+00:00 INFO  delmailuser: '[email protected]' and associated data (aliases, quotas) deleted

Or from the docker host when calling into the container you can wrap that like this:

docker exec dms bash -c 'echo "n" | setup email del [email protected]'

Since I omitted -it from the docker command, the echo workaround isn't actually necessary either, but you will find the equivalent for docker compose exec does need it unless you add the required opt-out flags when using that to get non-interactive shell.

Sending a command from outside of the container via the docker host

This is for a quick reference, if you're interested in the details to better understand this advice, I've covered that in the next section.

$ docker run --rm \
  --env ACCOUNT_PROVISIONER=FILE \
  --volume ./postfix-accounts.cf:/tmp/docker-mailserver/postfix-accounts.cf:ro \
  ghcr.io/docker-mailserver/docker-mailserver:15.1.0 \
    setup email del [email protected]

2025-09-04 01:06:43+00:00 INFO  delmailuser: The mailbox data will not be deleted.
sed: cannot rename /tmp/docker-mailserver/sedauD0Xw: Device or resource busy
2025-09-04 01:06:43+00:00 ERROR delmailuser: '[email protected]' could not be deleted
2025-09-04 01:06:43+00:00 ERROR delmailuser: Aborting

docker compose equivalent (for both run and exec) are opt-out instead of opt-in for interactive terminal via --no-TTY --interactive=false:

services:
  dms:
    image: ghcr.io/docker-mailserver/docker-mailserver:15.1.0
    volumes:
      - ./postfix-accounts.cf:/tmp/docker-mailserver/postfix-accounts.cf:ro
# Only `--no-TTY` => Prompt is hidden but otherwise remains interactive
# Only `--interactive=false` => Prompt appears and hangs as no input can actually be received
# Both opt-out flags => No prompt shown and no stdin to receive input, proper non-interactive shell

$ docker compose run --rm --no-TTY --interactive=false \
  --env ACCOUNT_PROVISIONER=FILE \
  dms \
    setup email del [email protected]

2025-09-04 01:06:43+00:00 INFO  delmailuser: The mailbox data will not be deleted.
sed: cannot rename /tmp/docker-mailserver/sedauD0Xw: Device or resource busy
2025-09-04 01:06:43+00:00 ERROR delmailuser: '[email protected]' could not be deleted
2025-09-04 01:06:43+00:00 ERROR delmailuser: Aborting

docker compose exec vs docker exec default flags

When executing a command in a container from the host however, you'd typically use one of these two CLI commands with the flags -it to get an interactive tty. The two commands however differ in defaults for --interactive and --tty.

To better demonstrate this I've provided a bash script below and reproduction commands with output to compare/try. This is a difference worth being aware about when it comes to calling interactive commands in containers :)

Relevance of flags to the CLI prompt logic

Using -i will enable interactive prompting, but -t is required to actually see that prompt (due to how the read command used for the input + prompt logic works). When both are disabled, only then will the prompt (via read command) fallback to the implicit default.

  • Without --tty, the -p of the read command will be ignored so no prompt text appears.. while anything from our logs via echo continues to output_).
  • While only --tty without --interactive, the prompt is output but your containers stdin is not connected into the container, so anything you input appears unresponsive/ignored.
  • With --interactive only, you'd instead trigger that same without --tty condition hiding the prompt, but as stdin was kept open through the pipe, read is still waiting on input to continue. While normally in a piped command this would make sense such as echo 'hello' | read -p 'Showing this would be redundant', when it's an interactive pipe you'd want that TTY to see the prompt text 😅

Reference - Reproduction Example

You can run the following bash script mounted into a container (eg: -v ./detect.sh:/usr/local/bin/detect:ro) with both docker and docker compose using either subcommand exec or run:

#!/bin/bash

# Detected output:
# https://stackoverflow.com/questions/911168/how-can-i-detect-if-my-shell-script-is-running-through-a-pipe/30811869#30811869
function detect_output() {
  [[ -t 1 ]] && echo 'STDOUT is attached to TTY'
  [[ -p /dev/stdout ]] && echo 'STDOUT is attached to a pipe'
  [[ ! -t 1 && ! -p /dev/stdout ]] && echo 'STDOUT is attached to a redirection'
}

# Detect input (stdin vs `bash -i`):
# https://stackoverflow.com/questions/65265637/what-does-tty-s-have-to-do-with-detecting-whether-a-shell-is-interactive/65265888#65265888
function detect_input() {
  # Can input actually be received?:
  if [[ -t 0 ]]; then
    echo 'STDIN is attached to TTY'
  elif [[ -p /dev/stdin ]]; then
    echo 'STDIN is attached to a pipe'
  else
    echo 'Missing STDIN, cannot receive input?'
  fi

  # Bash (only relevant if the script was run with `bash -i` flag):
  case $- in *i*) echo "Shell is INTERACTIVE";; *) echo "Shell is NON-INTERACTIVE";; esac
}

detect_output
detect_input

docker exec

The defaults for the Docker CLI run / exec commands require opt-in to both --interactive / -i + --tty / -t:

$ docker run --rm -d -v ./detect.sh:/usr/local/bin/detect:ro --name shell-test fedora:42 sleep 60 \
  && echo -e '\nNeither:' && docker exec shell-test detect \
  && echo -e '\nBoth:' && docker exec -it shell-test detect \
  && echo -e '\nOnly -i:' && docker exec -i shell-test detect \
  && echo -e '\nOnly -t:' && docker exec -t shell-test detect

Neither:
STDOUT is attached to a pipe
Missing STDIN, cannot receive input?
Shell is NON-INTERACTIVE

Both:
STDOUT is attached to TTY
STDIN is attached to TTY
Shell is NON-INTERACTIVE

Only -i:
STDOUT is attached to a pipe
STDIN is attached to a pipe
Shell is NON-INTERACTIVE

Only -t:
STDOUT is attached to TTY
STDIN is attached to TTY
Shell is NON-INTERACTIVE

So read -p in this scenario only becomes non-interactive in the "Neither" variant.

STDIN via pipe or TTY present will make it handled interactively.

docker compose exec

Docker Compose equivalent commands exec + run unlike the regular Docker CLI equivalent commands differ in interactive defaults (presumably for legacy reasons).

As such you need to opt-out with docker compose run with flags --no-TTY --interactive=false / -T -i=false, but docker compose exec lacks the ability to control interactive like docker exec, only the pseudo-TTY flag is supported (which can also be configured to a different default with the tty key in compose.yaml](https://docs.docker.com/reference/compose-file/services/#tty)).

UPDATE: Despite missing from the official documentation docker compose exec page, the -i / --interactive flag is actually implemented for docker compose exec as can be seen in the below output.

Equivalent Docker Compose exec example (click to view)

Try this basic compose.yaml like this with the earlier detect.sh bash script file and you'll find the following commands and output.

services:
  shell-test:
    image: fedora:42
    command: sleep 5
    volumes:
      - ./detect.sh:/usr/local/bin/detect:ro
# Every result is the same since neither `-i` or `-t` is opt-in, but instead opt-out:
$ docker compose up -d \
  && echo -e '\nNeither:' && docker compose exec shell-test detect \
  && echo -e '\nBoth:' && docker compose exec -it shell-test detect \
  && echo -e '\nOnly -i:' && docker compose exec -i shell-test detect \
  && echo -e '\nOnly -t:' && docker compose exec -t shell-test detect \
  && docker compose down

Neither:
STDOUT is attached to TTY
STDIN is attached to TTY
Shell is NON-INTERACTIVE

Both:
STDOUT is attached to TTY
STDIN is attached to TTY
Shell is NON-INTERACTIVE

Only -i:
STDOUT is attached to TTY
STDIN is attached to TTY
Shell is NON-INTERACTIVE

Only -t:
STDOUT is attached to TTY
STDIN is attached to TTY
Shell is NON-INTERACTIVE
# Actual equivalent to `docker exec` results via opt-out flags `-T` + `-i=false`:
$ docker compose up -d \
  && echo -e '\nNeither enabled:' && docker compose exec -T -i=false shell-test detect \
  && echo -e '\nBoth (defaults):' && docker compose exec shell-test detect \
  && echo -e '\nOnly STDIN:' && docker compose exec -T shell-test detect \
  && echo -e '\nOnly TTY:' && docker compose exec -i=false shell-test detect \
  && docker compose down

Neither enabled:
STDOUT is attached to a pipe
Missing STDIN, cannot receive input?
Shell is NON-INTERACTIVE

Both (defaults):
STDOUT is attached to TTY
STDIN is attached to TTY
Shell is NON-INTERACTIVE

Only STDIN:
STDOUT is attached to a pipe
STDIN is attached to a pipe
Shell is NON-INTERACTIVE

Only TTY:
STDOUT is attached to TTY
STDIN is attached to TTY
Shell is NON-INTERACTIVE

@polarathene
Copy link
Copy Markdown
Member

Oh, I just saw https://gitlab.com/div-solutions/docker-mailserver-ui is referring to k8s.

Kubernetes is rather slow at resolving issues with landing capabilities that Docker has, so if it lacks the equivalent ability to run a command non-interactively perhaps the support in DMS is necessary? @cfis or @georglauterbach can probably chime in on that?

We could technically infer lack of stdin via [[ ! -t 0 && ! -p /dev/stdin ]]? Could be a helper method for implicit opt-out of interactive prompts?

I'll wait on feedback from other maintainers.

@cfis
Copy link
Copy Markdown

cfis commented Sep 4, 2025

I believe you want:

kubectl exec <pod-name> -- <command>

Add -it for an interactive console.

@dano19
Copy link
Copy Markdown
Contributor Author

dano19 commented Sep 4, 2025

Thanks for your reply @polarathene.

I can use 'echo "n" | setup email del [email protected]'

My process of thinking was, because my code to execute setup though k8s client in .net looks like this:

await Config.ClusterClient
    .WebSocketNamespacedPodExecAsync(Config.MailServerPod.Metadata.Name, Config.MailServerPodNamespace, new List<string>()
        {
            "sh", "-c", $"/usr/local/bin/setup {command}"
        },
    Config.MailServerPod.Spec.Containers[0].Name)

so if there is -y, why not -n, doesn't impact feature in any way except adding new parameter for promptless deletion 😄

If this is too much of an issue, I will just modify my execution function to insert the echo n.

Thanks

@polarathene
Copy link
Copy Markdown
Member

I'm not against the change, I was just curious if you could run the command non-interactively without needing that support whenever a command has an interactive prompt.

I'll leave the PR up to @casperklein or @georglauterbach to decide if we should include flags for skipping prompts to use defaults. It's probably still a valid use-case, you're just the first user I've seen bring it up :)

@georglauterbach
Copy link
Copy Markdown
Member

I am fine with this change. @casperklein what do you make out of this?

@casperklein
Copy link
Copy Markdown
Member

echo 'n' | setup email del [email protected] works, but an extra parameter is the cleaner solution.

@georglauterbach georglauterbach added area/scripts kind/improvement Improve an existing feature, configuration file or the documentation orchestrator/kubernetes labels Sep 8, 2025
@georglauterbach georglauterbach added this to the v16.0.0 milestone Sep 8, 2025
@georglauterbach georglauterbach merged commit e78801b into docker-mailserver:master Sep 8, 2025
12 of 13 checks passed
@github-project-automation github-project-automation Bot moved this from Accepted to Done in DMS Features & Tasks Sep 8, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/scripts kind/improvement Improve an existing feature, configuration file or the documentation orchestrator/kubernetes

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

5 participants