Repository: certbot/certbot

Issue:
ValueError: I/O operation on closed file in "certonly" mode
## My operating system is (include version):

CentOS Linux release 7.8.2003 (Core)

## I installed Certbot with (snap, OS package manager, pip, certbot-auto, etc):

Installed using snap.

## I ran this command and it produced this output:

certbot certonly --manual --quiet --force-renewal --preferred-challenge dns --manual-auth-hook ${CHALLENGE_SCRIPT} -d ${DOMAIN},*.${DOMAIN}

## Certbot's behavior differed from what I expected because:

It actually issues the certificate, but it gives an I/O error (see below) just before quitting, which kind of breaks our automation.

## Here is a Certbot log showing the issue (if available):

    Error in atexit._run_exitfuncs:
    Traceback (most recent call last):
      File "/var/lib/snapd/snap/certbot/1343/lib/python3.8/site-packages/certbot/util.py", line 625, in _atexit_call
        func(*args, **kwargs)
      File "/var/lib/snapd/snap/certbot/1343/lib/python3.8/site-packages/certbot/display/util.py", line 72, in notification
        obj.get_display().notification(message, pause=pause, wrap=wrap,
      File "/var/lib/snapd/snap/certbot/1343/lib/python3.8/site-packages/certbot/_internal/display/obj.py", line 421, in notification
        self.outfile.write(
    ValueError: I/O operation on closed file.

## Here is the relevant nginx server block or Apache virtualhost for the domain I am configuring:

We are using manual mode with certonly.


Discussion:
> which kind of breaks our automation

How exactly are you running Certbot? This looks similar to another issue where Certbot was being launched into the background (`&`) and then the parent process was being killed before Certbot was, resulting in Certbot trying to write to a closed stream. If this is the case, redirect Certbot's output to a file and use `--non-interactive`, `--quiet` (if appropriate).

Otherwise, I think we need some more detail about how how to trigger this issue.
Hi, it doesn't run on background but it _does_ run scripted.

I'll give `--non-interactive` a try and let you know.
Not related to your issue, but please be careful with `--force-renewal`: it's almost never appropriate and can lead to excessively issued certificates with running into rate limits.
I was using it just to verify the issue. We’re not using that option in production. 

I can confirm that the issue still happens with `--non-interactive`. It’s harmless because it triggers just before exit. 
Could you perhaps share (a shortened version of) your script calling certbot which still produces this error so we can reproduce it?
I'm seeing a similar problem. Normally I run certbot out of cron, but have seen this running the same script from a shell too, just now. Tried `--non-interactive` and got:
```

NEXT STEPS:
Error in atexit._run_exitfuncs:
Traceback (most recent call last):
  File "/usr/local/lib/python3.7/site-packages/certbot/util.py", line 624, in _atexit_call
    func(*args, **kwargs)
  File "/usr/local/lib/python3.7/site-packages/certbot/display/util.py", line 515, in notification
    .format(line=os.linesep, frame=SIDE_FRAME, msg=message)
ValueError: I/O operation on closed file.
```

Script is a bash script on FreeBSD:
```
certbot --quiet certonly -n \
        --non-interactive \
        --force-renewal \
        --agree-tos \
        --email <my-email-address> \
        --webroot \
        --webroot-path "$webroot" -d <subdomain1> \
        --webroot-path "$webroot" -d <subdomain2> \
        --webroot-path "$webroot" -d <subdomain3> \
        --webroot-path "$webroot" -d <subdomain4> \
        --webroot-path "$webroot" -d <subdomain5> \
        --webroot-path "$webrootp" -d <subdomain6> \
        --max-log-backups 60 \
        --deploy-hook "$0 deploy-hook" \
        2>&1
```

Thanks for the example command @pirtoo, that's really helpful. 

It looks like the most basic way to reproduce the issue is by trying to issue a certificate with `--quiet`.

We'll get this fixed.

Resolving patch:
diff --git a/certbot/certbot/_internal/main.py b/certbot/certbot/_internal/main.py
--- a/certbot/certbot/_internal/main.py
+++ b/certbot/certbot/_internal/main.py
@@ -66,9 +66,12 @@ def _suggest_donation_if_appropriate(config):
     :rtype: None
 
     """
+    # don't prompt for donation if:
+    # - renewing
+    # - using the staging server (--staging or --dry-run)
+    # - running with --quiet (display fd won't be available during atexit calls #8995)
     assert config.verb != "renew"
-    if config.staging:
-        # --dry-run implies --staging
+    if config.staging or config.quiet:
         return
     util.atexit_register(
         display_util.notification,


Test patch: