The shell script starts with the following comment:<p><pre><code> # All the code is wrapped in a main function that gets called at the
# bottom of the file, so that a truncated partial download doesn't end
# up executing half a script.</code></pre>
Another trick I've also seen is to enclose the whole script in a code block<p><pre><code> {
code
}
</code></pre>
That way, if the file is not fully loaded, the block will not end and the script will not parse
A simple (not perfect) approach could be to have a comment containing a "unique" string on the last line and grep for it as the first check to ensure that the entire script has downloaded.<p><pre><code> #!/usr/bin/env bash
set -u
grep -wq '^# asfewdq42d3@asd$' $0
[ $? -ne 0 ] \
&& echo "script is not complete - re-download" \
&& exit 1
echo "script is complete"
# asfewdq42d3@asd</code></pre>
I've seen scripts (self-extracting archives for Linux, for example) that <i>checksum themselves</i> either by some trickery, or just ignoring the first line after the shebang (which itself is the computed checksum of the rest of the file).
This probably means you can edit the script while it's running without it falling over confusingly. Might cargo cult this pattern - I'm very prone to editing a build.sh while it runs.
<p><pre><code> #!/bin/bash
SHA512="485fe3502978ad95e99f865756fd64c729820d15884fc735039b76de1b5459d32f8fadd050b66daf80d929d1082ad8729620925fb434bb09455304a639c9bc87"
# This line and everything later gets SHA512'ed and put in the above line.
# To generate the sha512 simply: tail -n +3 [SCRIPTNAME].sh | sha512sum
check_sha512() {
# Compute the SHA512 hash of the script excluding the first two lines
local current_sha=$(tail -n +3 "$0" | sha512sum | awk '{print $1}')
# Compare the computed SHA512 hash with the predefined one
if [[ "$current_sha" != "$SHA512" ]]; then
echo "Error: SHA512 hash does not match!"
exit 1
fi
}
# Call the function to perform the hash check
check_sha512
# Rest of your script starts here
echo "Script execution continues..."
</code></pre>
The idea is simple: if the first line get's mangled (#!/bin/bash) the script probably won't execute at all.
If the second line gets mangled than obviously the SHA512 comparison won't work (variable name or value).<p>Finally if the rest of the script gets mangled or truncated it won't SDHA512 the same and it'll cause the function to exit.<p>For bonus points you can add a check if first line of script is exactly "#!/bin/bash" as well.
A serious question for any Linux-heads here, no insult intended.<p>How is it possible that there are ELEVEN different possible package managers that need to be supported by an installation script like this?<p>I can understand that some divergences in philosophical or concrete requirements could lead to two, three, or four opinionated varieties, but ELEVEN?<p>Does that mean that if I want to write an app that runs on Linux I should also be seeking to support 11 package managers? Or is there something unique about tailscale that would necessitate it?<p>edit: Thank you for the responses so far, but noone has yet answered the core question: WHY are there eleven of them?
I mean, could you put the main function at the <i>top</i> of the script, so that it calls later definitions?<p>The problem is that the script could be truncated in such a way that it executes successfully. It defines a bunch of functions and then quits.<p>If you're not checking for the success or failure of the download, you're probably not checking for the success or failure of the script; something is just going to assume the script worked.
If only there was a way to transactionally run shell scripts such that if they don't complete fully, the changes are automatically reverted.<p>Edit: cue the HN responses to use nix, and other solutions
...isn't that a common pattern? I'm pretty sure most install scripts I download and run already do that, though I don't run those very often.
Don't pipe curl/wget a script to a shell without reading what you've downloaded. This should be common sense. Do `wget $url; most install.sh` and <i>only if you're satisfied with what you read</i>, execute `sh install.sh`.