Contents:
- Manage the Unmanaged
- Use Cases
- Usage
- Dependencies
- Using a Checksum
- Using git
- Using a Run-Command (rc) File
- Conditional Execution
- Conditional Execution, Full Example
- Event Hooks
- Trust
- The qwerty.sh Web Service
- White Label
- Why "qwerty.sh"?
It is a solved (or solvable) problem to download, verify, and unpack files working with the tools provided with a programming language. Everything else is undefined. Use qwerty.sh to download, verify, and unpack: unversioned files, specific versions of development tools, and project data.
More broadly, download any file and make it part of a project without checking it into the repository. Add individual files from other git repos. Outside of git, determine the file's checksum and have qwerty.sh verify it. The qwerty.sh commands get checked into the project repo; the resulting files do not.
Know that you have a bad download at download time and not run time, especially to save from troubleshooting hidden errors or running untrusted code.
Use qwerty.sh when bootstrapping builds and development environments, for:
- Repeatable single-command downloads in build workflows.
- Trusted copy/paste developer tool instructions (replacing
curl ... | sh
). - An easy-to-type command to bootstrap a development environment.
On a Unix shell:
Q=https://raw.githubusercontent.com/rduplain/qwerty.sh/v0.8/qwerty.sh
alias qwerty.sh="curl --proto '=https' --tlsv1.2 -sSf $Q | Q=$Q sh -s -"
Explained:
- Set the
$Q
environment variable to a reliably hostedqwerty.sh
script. - Use
--proto '=https' --tlsv1.2
to configurecurl
to only accept HTTPS; this mitigates the risk ofcurl
being redirected to a non-encrypted URL. - Use
-sS
to silencecurl
(-s
) but show an error (-S
). - Use
-f
to fail silently on server errors, to let qwerty.sh control output. - Reference the previously set
$Q
, twice:- The first is simple variable substitution, give the URL to
curl
. - The second is to pass
$Q
to theqwerty.sh
program, in order forqwerty.sh
to provide useful help messaging. Instead ofexport Q
, settingQ=$Q
allows thesh -s -
process to haveQ
in its environment withoutexport
ing/cluttering the environment of other processes (especially becauseQ
is highly contextual to qwerty.sh).
- The first is simple variable substitution, give the URL to
- Pipe to
sh
to runqwerty.sh
locally. - Invoking
sh -s -
allows for all additional command-line arguments to be passed toqwerty.sh
(instead of tosh
).
The --help
flag shows full usage.
See full examples below.
curl
, to fetch the qwerty.sh script and have it download target files.sh
, which is a given on all Unix-like systems, to run the qwerty.sh script.openssl
, which is widely available with curl, to verify checksums.git
, a version control system, to verify file integrity.
Arguments to qwerty.sh indicate whether to use a checksum or use git to verify
files, and qwerty.sh requires openssl
or git
accordingly. For HTTPS, curl
must support TLSv1.2 (2008), which it commonly does.
The qwerty.sh project provides fully portable shell using commands commonly found on Unix platforms (SUSv4), and makes every effort to provide a simple, clear error message in the event that a dependency is missing.
Download a shell script, verify it, execute it (without keeping it):
qwerty.sh \
--sha256=87d9aaac491de41f2e19d7bc8b3af20a54645920c499bbf868cd62aa4a77f4c7 \
http://hello.qwerty.sh | sh
Download a program, verify it, keep it, make it executable (then execute it):
qwerty.sh \
--sha256=87d9aaac491de41f2e19d7bc8b3af20a54645920c499bbf868cd62aa4a77f4c7 \
--output=hello --chmod=a+x \
http://hello.qwerty.sh && ./hello
Download an archive, verify it, unpack it (without keeping the archive itself):
qwerty.sh \
--sha256=70c98b2d0640b2b73c9d8adb4df63bcb62bad34b788fe46d1634b6cf87dc99a4 \
http://download.redis.io/releases/redis-5.0.0.tar.gz | \
tar -xvzf -
Download a shell script, verify it, execute it (without keeping it):
qwerty.sh \
-o - https://github.com/rduplain/qwerty.sh.git web/hello/hello.sh | sh
Download a program, verify it, keep it, make it executable (then execute it):
qwerty.sh \
--chmod=a+x \
https://github.com/rduplain/qwerty.sh.git \
web/hello/hello.sh:hello && ./hello
Download an entire repository (without retaining .git metadata):
qwerty.sh https://github.com/rduplain/qwerty.sh.git
qwerty.sh --output=OUTPUT_DIRECTORY https://github.com/rduplain/qwerty.sh.git
Download a specific revision of a file (-o -
writes to stdout):
qwerty.sh \
-b v0.6.3 \
-o - https://github.com/rduplain/qwerty.sh.git qwerty.sh | head
Download a specific revision of a file which is not tagged or is not at the
HEAD of a branch (and note that use of --ref
is more download intensive):
qwerty.sh \
--ref dea68e7 \
-o - https://github.com/rduplain/qwerty.sh.git qwerty.sh | head
Download multiple files, verify them, keep them, make them executable:
qwerty.sh \
--chmod=a+x \
https://github.com/rduplain/qwerty.sh.git \
qwerty.sh web/hello/hello.sh:hello.sh
Download multiple files, verify them, write one file to stdout while making the others executable:
qwerty.sh \
--chmod=a+x \
https://github.com/rduplain/qwerty.sh.git \
LICENSE:- web/hello/hello.sh:hello.sh
Download multiple files, verify them, and write them to stdout:
qwerty.sh \
-o - \
https://github.com/rduplain/qwerty.sh.git \
README.md web/README.md | less
Run qwerty.sh in batch-mode by providing a run-command (rc) file. This approach is useful in order to have a project download, verify, and unpack multiple files from multiple sources without tracking anything but the rc file in version control.
An example .qwertyrc
:
# https://qwerty.sh/
# With checksum, download shell script, verify it, keep it, make it executable.
--sha256=87d9aaac491de41f2e19d7bc8b3af20a54645920c499bbf868cd62aa4a77f4c7 \
--output=hello-from-checksum --chmod=a+x \
http://hello.qwerty.sh
# With git, download shell script, verify it, keep it, make it executable.
--chmod=a+x \
--force --when-missing \
https://github.com/rduplain/qwerty.sh.git \
web/hello/hello.sh:hello-from-git
Call qwerty.sh:
qwerty.sh --rc .qwertyrc
This will result in two local files (hello-from-checksum
, hello-from-git
)
applying two separate command-line invocations of qwerty.sh, using only a
single download of the qwerty.sh program itself.
Provide multiple --rc
flags for multiple run-command files. Add the
--cd-on-rc
flag to have qwerty.sh change directories to that of the
run-command file when processing its commands.
Specifying multiple rc files in a pattern/glob requires quoting. Otherwise, the
shell will expand filepaths before passing them to qwerty.sh, which will fail
because --rc
takes a single argument. For example, to specify rc files in a
directory, quote the argument with single (') or double (") quotes to delay
expansion (and use .*
not *
if the target rc files are hidden dotfiles):
--rc='.qwertyrc.d/*'
Note that the shell language has limitations when commenting with the
line-continuation backslash (\
). A block of lines is joined as though the
backslashes and their adjacent newlines are not there. As such, no line can be
"commented-out" without moving it outside of the backslash-concatenated block
of lines, separated by newlines.
Example interpreted as a stand-alone comment followed by an rc line:
# --flag=value \
--flag=value
Example interpreted as a comment only, without any rc lines:
# --flag=value \
--flag=value
Provide processor architecture and operating system details to qwerty.sh to conditionally execute a command:
--arch=ARCHITECTURE Run only if `uname -m` matches.
--sys=OPERATING_SYSTEM Run only if `uname -s` matches.
--when=COMMAND Run only if COMMAND is successful.
For example, on flags --arch=x86_64 --sys=Linux
, qwerty.sh will only proceed
on 64-bit x86 Linux machines. On --sys=Linux
alone (no --arch
given),
qwerty.sh will only proceed on Linux machines (of any architecture).
Values are case-insensitive and will match specifically what is reported by
uname
: -m
and -s
for architecture and kernel/system, respectively. Pass
multiple --sys
and --arch
flags as needed to support target platforms. Only
one match of each category is needed to continue execution.
An --all-sub-arch
flag is accepted in order to support architectures which
have multiple sub-architectures. Namely, ARM systems report a wide variety of
names in uname -m
(armv6
, armv6-m
, ..., armv7l
, ...). On --arch=arm --all-sub-arch
, qwerty.sh will proceed on all platforms that report an
architecture starting with arm
. More broadly, --all-sub-arch
matches all
uname -m
output that starts with the given --arch
value.
Conditional execution is especially useful when downloading platform-dependent binaries for projects that run on a variety of platforms. A run-command (rc) file can specify qwerty.sh invocations across multiple platforms, and qwerty.sh will skip any commands for which the system conditions are not met. This approach allows a single qwerty.sh invocation to download platform-dependent files and binaries without additional logic.
An example to put it all together, hosts.qwertyrc
:
# https://qwerty.sh/
# GNU/Linux 64-bit
--sys=linux --arch=x86_64 \
--sha256=baae9a4ccb17b3f9e0b868e261e39356774955a68084d3653a1d7e773dea616d \
--output="$HOME"/bin/hosts --chmod=755 \
https://github.com/rduplain/hosts/releases/download/v1.1/hosts-v1.1-x86_64-linux-gnu
# GNU/Linux on ARM, incl. Raspberry Pi
--sys=linux --arch=arm --all-sub-arch \
--sha256=31762e1448834bd3cdd76a708aa0e53cbc88144ca0151077f58d5058e6eca6ec \
--output="$HOME"/bin/hosts --chmod=755 \
https://github.com/rduplain/hosts/releases/download/v1.1/hosts-v1.1-arm-linux-gnueabihf
# Mac OS X, 64-bit
--sys=darwin --arch=x86_64 \
--sha256=6b741620cd517e23ad1748fc6193e3cdf099fb32d16c26a2edb93d847ee3635f \
--output="$HOME"/bin/hosts --chmod=755 \
https://github.com/rduplain/hosts/releases/download/v1.1/hosts-v1.1-x86_64-apple-darwin
# FreeBSD 64-bit
--sys=freebsd --arch=amd64 \
--sha256=412d4e850d3a26685a6954f75d40665fd128a42712a4d4572bd1371232795a44 \
--output="$HOME"/bin/hosts --chmod=755 \
https://github.com/rduplain/hosts/releases/download/v1.1/hosts-v1.1-x86_64-freebsd
Then:
qwerty.sh --rc hosts.qwertyrc
Hook shell expressions into qwerty.sh at specific events during its runtime. Each hook supports repeat command-line flags and runs the given shell expressions in order.
All shell expressions passed to qwerty.sh can be quoted in order to include shell features of pipelines and redirection. Further, qwerty.sh logs only to stderr in order to preserve stdout for download and hook output.
Hooks run on start, after parsing arguments and before conditional execution.
The on-start hook runs in the original working directory of qwerty.sh.
Hooks run after conditions match; run when no conditions are given. See Conditional Execution.
The on-match hook runs in the original working directory of qwerty.sh.
Hooks run on (after) download and before writing output.
This is useful in unpacking zipped binary executables, especially when using run-command (rc) files to specify downloads for multiple supported platforms.
The following example downloads a .zip matching the given checksum, unzips a binary, outputs it to a specific filepath, makes it executable, and only runs if this resulting file does not exist on repeat runs of qwerty.sh (which is essential to prevent qwerty.sh from rejecting the resulting file on its next run, because the checksum of the resulting file is not the same as that of the specified .zip download):
# Skip the $QWERTY_SH line when using .qwertyrc files.
eval "$QWERTY_SH" \
--sys=linux --arch=x86_64 \
--when='! test -e bin/program' \
--sha256=1234567890abcdef \
--output=bin/program --chmod=a+x \
--on-download='unzip $DOWNLOAD && mv program $DOWNLOAD' \
http://dist.example.com/program_1.0_linux_amd64.zip
The on-download hook runs in the temporary working directory of the download
before qwerty.sh writes its output. When using a checksum, an on-download hook
can substitute output by overwriting the file at $DOWNLOAD
. When using git,
an on-download hook can modify any path within the cloned repository.
Hooks run after writing output.
This is useful in unpacking archived directories and running setup commands. Note that these steps are often best run within a script or Makefile that calls qwerty.sh, with the on-output hook provided for cases where a project would prefer a single, complete qwerty.sh invocation to run setup commands.
The following example downloads an archive matching the given checksum, unpacks it to a relative directory, and only runs if a resulting file does not exist on repeat runs of qwerty.sh:
# Skip the first line (`qwerty.sh \`) when using .qwertyrc files.
qwerty.sh \
--when='! test -e .usr/local/project-1.0' \
--sha256=1234567890abcdef \
--output=.usr/src/project-1.0.tar.gz \
--on-output='mkdir -p .usr/local' \
--on-output='cd .usr/local; tar -xf ../src/project-1.0.tar.gz' \
http://dist.example.com/project-1.0.tar.gz
The on-output hook runs in the original working directory of qwerty.sh.
To build a static binary from the qwerty.sh download, consider an on-download hook instead of on-output with build steps. Substitute the download with the resulting binary when using a checksum or specify the resulting build filepath for output when using git.
Hooks run on finish, after qwerty.sh finishes its full main routine.
Note that with run-command (rc) files, this hook runs at the finish of each rc line invocation.
The on-finish hook runs in the original working directory of qwerty.sh.
Only run code from trusted sources. This includes copy/paste qwerty.sh invocations and run-command (rc) .qwertyrc files.
The qwerty.sh project has a single focus: provide a script as a service.
The script downloads and runs locally; no information given to the script is
provided to the qwerty.sh server. When running qwerty.sh, only the curl
portion of the command line is known to the qwerty.sh server, which only
indicates a request to download the qwerty.sh script.
The qwerty.sh script is provided over HTTPS. Having a trusted certificate authority is essential in getting a trusted qwerty.sh script. This HTTPS encryption initiates a web of trust:
- Determine details (e.g. a checksum or git) which validate a given file.
- Get the qwerty.sh script, knowing that it transmitted through HTTPS.
- Tell the qwerty.sh script known details about the target file to download.
Ideally everything could be verified against signed artifacts, i.e. GPG. The
entire purpose of qwerty.sh
is to acknowledge the reality that many files are
downloaded without any verification whatsoever (neither git nor checksum) and
piped freely into sh
and other interpreters, (!!!) and identifies a trusted
HTTPS connection as the lowest common denominator in trust.
Skeptical users can fork the qwerty.sh project on GitHub or elsewhere:
Q=https://.../qwerty.sh/v.../qwerty.sh # Set URL of fork here.
alias qwerty.sh="curl --proto '=https' --tlsv1.2 -sSf $Q | Q=$Q sh -s -"
Alternatively, run a qwerty.sh
web server.
See: web/README.md
qwerty.sh supports vendoring, to allow an external project to wrap or rename
qwerty.sh in its operation. For example, a cmd
with subcommands could have
cmd download
call out to a local qwerty.sh file:
QWERTY_SH_PROG='cmd download' path/to/qwerty.sh "$@"
In this mode, qwerty.sh will function as usual but rewrite all messages and
usage to indicate $QWERTY_SH_PROG
instead of qwerty.sh
.
Importantly, this approach allows external projects to download qwerty.sh as-is without modification. With this approach, updating the vendored qwerty.sh program is just a download of a new version:
mkdir -p path/to/
curl --proto '=https' --tlsv1.2 -sSf $Q > path/to/qwerty.sh
chmod a+x path/to/qwerty.sh
Calling qwerty.sh
via curl
also supports $QWERTY_SH_PROG
.
Bootstrap a toolchain on an internet-connected Unix-like system with just one dependency: a keyboard.