Ubuntu mute sound when laptop lid closed

You can mute the sound before suspend by creating a systemd service.

  • First, make sure that your script is executable:

    chmod u+x /path/to/your/script.sh
    
  • Then, create a systemd service that runs your script, by running:

    sudo nano /etc/systemd/system/mute_before_sleep.service
    
  • In the nano window enter the following:

    [Unit]
    Description=Mute sound before suspend
    Before=suspend.target
    
    [Service]
    ExecStart=/path/to/your/script.sh
    User=<your_username>
    Environment=DISPLAY=:0
    
    [Install]
    WantedBy=suspend.target
    

    In ExecStart= enter the path to your script and in User= enter your username.

  • Save and close nano by pressing Ctrl+O and then Ctrl+X.

  • Finally, enable the service:

    sudo systemctl enable mute_before_sleep.service
    

TL;DR: excution environment is different, hence amixer command returns an error

The script seems to be called when suspending as new lines are being appended to the "test" file

Yes, it runs, but I've tested your script and the amixer command returns an error (reviewed with sudo journalctl -b -u systemd-suspend.service):

Jan 04 18:46:49 administrator-PC systemd-sleep[12969]: ALSA lib control.c:1373:(snd_ctl_open_noupdate) Invalid CTL pulse
Jan 04 18:46:49 administrator-PC systemd-sleep[12969]: amixer: Mixer attach pulse error: No such file or directory

The issue appears to be the -D pulse part. But simpler command amixer -q set Master mute works. After resume my laptop has the volume muted, error no longer appears.

twice - which might indicate that the script also runs on resume - can someone confirm this assumption?

Yes, the script runs twice - once on suspend and once on resume; the scripts run with two positional parameters per systemd-sleep(8) and Arch Wiki article. The full script I've used is listed below (note, the parts which call echo and date aren't particularly relevant and were for debugging only; they may be safely removed from the script):

#!/bin/sh

# this will go into journalctl log
echo SUSPEND SCRIPT STARTED
case $1/$2 in
  pre/*) amixer -q set Master mute; { echo Suspending; date >> /suspend_log.txt;}  ;;  
  post/*) { echo "Waking up from $2..."; date;} >> /suspend_log.txt ;;
  *) echo foobar;;
esac

As for the reason why the original amixer command works when you run it manually, likely stems from the fact that when systemd runs the script, there is no DISPLAY variable present in environment. By contrast when you use a GUI terminal emulator the variable is present, hence the original command works. The original command can be adapted to use the environment variable via

DISPLAY=:0 amixer -q -D pulse sset Master mute

but I'd still recommend using amixer -q set Master mute as environment-independent command.