Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In bash i'm building an update script, how to update the updater script

I am building a little script to update application files on a raspberry pi.

It will do the following:

  1. Download a zip file of the application files
  2. Unzip them
  3. Copy each one to the right place and make it executable etc as needed.

The problem i'm having is that one of the files is updatescript.sh.

I've read that it is dangerous to update / change a bash script while it is executing. See Edit shell script while it's running

Is there a good way to achieve what I'm trying to do?

like image 284
user230910 Avatar asked Oct 21 '25 19:10

user230910


1 Answers

What you've read is badly overblown.

It's completely safe to overwrite a shell script in-place by mving a different file over it. When you do this, the old file handle is still valid, referring to the original unmodified file contents. What you can't safely do is edit the existing file in-place.

So, the below is fine (and is what all your OS-vendor update tools like RPM do in effect):

#!/usr/bin/env bash
tempfile=$(mktemp "$BASH_SOURCE".XXXXXX)
if curl https://example.com/whatever >"$tempfile" &&
   curl https://example.com/whatever.sig >"$tempfile.sig" &&
   gpgv "$tempfile.sig" "$tempfile"; then
  chown --reference="$BASH_SOURCE" -- "$tempfile"
  chmod --reference="$BASH_SOURCE" -- "$tempfile"
  sync # force your filesystem to fully flush file contents to disk
  mv -- "$tempfile" "$BASH_SOURCE" && rm -f -- "$tempfile.sig"
else
  rm -f -- "$tempfile" "$tempfile.sig"
  exit 1
fi

...whereas this is risky:

curl https://example.com/whatever >/usr/local/bin/whatever

So do the first, thing, not the second one: When downloading a new version of your script, write that to a different file, and only rename it over the original when the download succeeded. That's what you want to do anyhow to ensure atomicity.

(There are also some demonstrations of code-signing validation practices above because, well, you need them when building an updater. You wouldn't be trying to distribute code via an automated download without verifying a signature, right? Because that's how one simple breakin to your web server results in every single one of your customers being 0wned. The above expects the public side of your code-signing keys to be in ~/.gnupg/trustedkeys.gpg, but you can put trustedkeys.gpg in any directory and point to it with the environment variable GNUPGHOME).


Even if you don't write your update code safely, the risk is still trivial to mitigate. If you move the body of your script into a function, such that it has to be completely read before any part of it can be executed, then there's no part of the file that isn't already read at the time when execution begins.

#!/usr/bin/env bash
main() {
  echo "Logic all goes here"
}; { main "$@"; exit; }

Because { main "$@"; exit; } is part of a compound command, the parser reads the exit before it starts executing the main, so it's guaranteed that no further source-file content will be read after main exits, even if some future bash release didn't handle input line-by-line in the first place.

like image 109
Charles Duffy Avatar answered Oct 23 '25 22:10

Charles Duffy



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!