1

I'm writing a shell script which will run in the background to control Syncthing and manage a UFW firewall in relation to Syncthing.

Here it is in simplified form:

#!/bin/bash

sync () {
    # Open the ports which Syncthing communicates on.
    sudo ufw allow syncthing &> /dev/null
    # Start Syncthing, and block script execution here until Syncthing is closed.
    syncthing &> /dev/null
    # Close the ports which Syncthing communicates on once it is closed.
    sudo ufw delete allow syncthing &> /dev/null
}

# Get sudo before the sync function is backgrounded.
sudo -v
# Run the sync function in the background.
sync &

This script works as intended when the terminal it is run from remains open.

If the terminal it is run from is closed while Syncthing is running though, then ports in the firewall are not closed when Syncthing is closed.

Is there a way to get this script to run properly -- closing the ports in the firewall after Syncthing is closed -- when the terminal it is started from is closed before Syncthing is closed?

Here is a script which you can use to experiment with this behaviour. It doesn't require Syncthing to be installed, and it outputs to syslog:

#!/bin/bash

test_function () {
    echo '-- Opening port' | logger -t TEST
    sudo ufw allow 80 | logger -t TEST
    echo '-- Close the terminal you started this script from in the next 10 seconds' | logger -t TEST
    sleep 10
    echo '-- Closing port' | logger -t TEST
    sudo ufw delete allow 80 | logger -t TEST
}

sudo -v
test_function &
countermeasure
  • 442
  • 4
  • 10

2 Answers2

1

I guess the sudo cache you create with sudo -v is tied to the terminal session, and goes away immediately when you log out.

The simple workaround then is to run the entire command using sudo.

#!/bin/sh

sync () {
    ufw allow syncthing
    su "$SUDO_USER" -c syncthing
    ufw delete allow syncthing
}

test "$SUDO_USER" && test -w / || {
  echo "${0##*/}: run this script using sudo" >&2
  exit 126
}

sync >/dev/null 2>&1 &

I also refactored the redirections to the caller; this should hopefully make it easier to change it to write diagnostics from ufw and syncthing to a log file, for example.

The su command runs syncthing as the invoking user; I am unfamiliar with its functionality, so this may be insufficient if it requires a login session or access to your desktop environment e.g. to display a GUI (and overkill if you don't mind running it as root).

Only the redirections were using Bash syntax, so replacing those with portable sh syntax allowed me to change the shebang to #!/bin/sh; on many systems, that should allow this script to consume significantly less resources.

Alternatively, update your sudoers privileges to allow you to run these specific ufw commands passwordless. If you have /etc/sudoers.d you can create a new file there to grant yourself these permissions.

you ALL=(root) NOPASSWD: /usr/bin/ufw allow syncthing
you ALL=(root) NOPASSWD: /usr/bin/ufw delete allow syncthing

(where obviously you'd replace you with your actual account name, and perhaps check the path to ufw which I simply guessed).

tripleee
  • 175,061
  • 34
  • 275
  • 318
  • It's important to me that I can call my script without sudo, because the sync() function will eventually be included in a larger script which I don't want to ever have to call with sudo. If I remove the test from your script, and replace the last line in it with `sudo bash -c "$(declare -f sync); sync &> /dev/null &"`, I think that gets me what I want. – countermeasure Jul 24 '22 at 08:09
  • You can't use `&>` in a `sh` script; I generally find it clearer to avoid this particular Bashism (the fact that it was loaned from Csh would already convince many people that it's a bad idea). – tripleee Jul 24 '22 at 08:15
  • Requiring interactive `sudo` would seem to make the script _less_ useful for integrating into a larger script. – tripleee Jul 24 '22 at 08:17
  • One thing that I've noticed is that now when Syncthing opens its web GUI, it's using Chromium, and not my user's (and the root user's) default of Firefox. I'm guessing that because we're running the sync() function with sudo and fiddling with users inside it, we're losing some settings, but I'm not sure which ones or where from. How would I put that right? – countermeasure Jul 24 '22 at 08:22
  • If the process requires a GUI, you really need to run it within your desktop session for these things to work right. Maybe hook it into your desktop logout actions? – tripleee Jul 24 '22 at 09:45
  • Perhaps see also https://stackoverflow.com/a/68933388/874188 – tripleee Jul 27 '22 at 06:19
  • This is what was causing the Syncthing GUI to open in Chromium and not Firefox: When I do `$ sudo su`, and then `# su $SUDO_USER -c "xdg-settings get default-web-browser"`, then the output is `chromium.desktop`. – countermeasure Jul 28 '22 at 06:15
  • Thanks for your help. Passwordless sudo solved my problem. I wrote a separate answer which makes it clear for future visitors that this was the solution I went with, but all credit for that approach goes to you @tripleee :) – countermeasure Aug 18 '22 at 03:21
0

I used one of triplee's suggestions to handle this problem by enabling passwordless sudo for the ufw commands I wanted to run.

To do that I created a file called ufw in /etc/sudoers.d/ with this content:

%sudo ALL=(root) NOPASSWD:/usr/sbin/ufw allow syncthing
%sudo ALL=(root) NOPASSWD:/usr/sbin/ufw delete allow syncthing

Then with passwordless sudo in place, my script became:

#!/bin/bash

sync () {
    sudo ufw allow syncthing
    syncthing
    sudo ufw delete allow syncthing
}

sync &> /dev/null &

Now even when the terminal that it is run from is closed while syncthing is running, the ufw delete command still fires when syncthing is closed.

countermeasure
  • 442
  • 4
  • 10