#!/bin/sh # ServerSecretVault (ssv) installer # # curl -fsSL https://serversecretvault.com/install.sh | sudo sh # # Downloads the ssv package for this server's architecture, verifies its SHA-256 # checksum and installs ssv to /usr/local/bin. Options go after "sh -s --": # # curl -fsSL https://serversecretvault.com/install.sh | sudo sh -s -- --version 0.1.0 # # --version X.Y.Z install this version instead of the latest # --dir DIR install into DIR instead of /usr/local/bin # # SSV_BASE_URL replaces https://serversecretvault.com/download, e.g. for a mirror. # # Everything runs from main() at the end, so a download cut off midway runs nothing. set -eu main() { base=${SSV_BASE_URL:-https://serversecretvault.com/download} version= dir=/usr/local/bin while [ $# -gt 0 ]; do case $1 in --version) [ $# -ge 2 ] || die "--version needs a value" version=${2#v} shift 2 ;; --dir) [ $# -ge 2 ] || die "--dir needs a directory" dir=$2 shift 2 ;; -h | --help) echo "usage: install.sh [--version X.Y.Z] [--dir DIR]" return 0 ;; *) die "unknown option: $1" ;; esac done [ "$(uname -s)" = Linux ] || die "ssv runs on Linux only" case $(uname -m) in x86_64 | amd64) arch=amd64 ;; aarch64 | arm64) arch=arm64 ;; *) die "there is no ssv package for $(uname -m); packages exist for x86_64 and aarch64" ;; esac for tool in tar sha256sum awk mktemp; do command -v "$tool" >/dev/null 2>&1 || die "$tool is required" done mkdir -p "$dir" 2>/dev/null || true if ! [ -d "$dir" ] || ! [ -w "$dir" ]; then die "cannot write to $dir; run with sudo (curl ... | sudo sh) or choose another --dir" fi tmp=$(mktemp -d) || die "cannot create a temporary directory" staged=$dir/.ssv.new.$$ trap 'rm -rf "$tmp"; rm -f "$staged"' EXIT trap 'exit 1' INT TERM if [ -z "$version" ]; then fetch "$base/latest.txt" "$tmp/latest.txt" version=$(tr -d ' \t\r\n' <"$tmp/latest.txt") fi case $version in '' | *[!0-9A-Za-z.-]*) die "invalid version: '$version'" ;; esac pkg=ssv-$version-linux-$arch echo "Downloading ssv $version for linux/$arch..." fetch "$base/$version/$pkg.tar.gz" "$tmp/$pkg.tar.gz" fetch "$base/$version/SHA256SUMS" "$tmp/SHA256SUMS" expected=$(awk -v f="$pkg.tar.gz" '$2 == f { print $1 }' "$tmp/SHA256SUMS") actual=$(sha256sum "$tmp/$pkg.tar.gz" | cut -d ' ' -f 1) [ -n "$expected" ] || die "SHA256SUMS has no entry for $pkg.tar.gz" [ "$actual" = "$expected" ] || die "checksum mismatch for $pkg.tar.gz; nothing was installed" echo "Checksum verified." tar -xzf "$tmp/$pkg.tar.gz" -C "$tmp" || die "cannot extract $pkg.tar.gz" old=$("$dir/ssv" version 2>/dev/null) || old= # Install under a temporary name and rename it into place, so an ssv that is # running right now (e.g. from cron) is never modified. install -m 0755 "$tmp/$pkg/ssv" "$staged" mv -f "$staged" "$dir/ssv" new=$("$dir/ssv" version 2>/dev/null) || die "installed $dir/ssv, but it does not run; is $dir on a noexec filesystem?" if [ -n "$old" ] && [ "$old" != "$new" ]; then echo "Upgraded $old to $new at $dir/ssv" else echo "Installed $new to $dir/ssv" fi make_reachable "$dir" if ! command -v age >/dev/null 2>&1 && ! [ -x /usr/local/bin/age ]; then echo "Note: 'ssv backup' also needs the age encryption tool, which isn't installed yet. '$cmd backup' shows how to install it on this server." fi echo echo "Get started: sudo $cmd audit ($cmd help lists all commands)" echo "ssv is proprietary software; '$cmd license' shows the license." } # make_reachable makes sure "sudo ssv" and "ssv" find the binary in $1, says what # it did, and sets cmd to the command to tell the user to run. On RHEL, Rocky, # AlmaLinux and CloudLinux, sudo only searches its secure_path # (/sbin:/bin:/usr/sbin:/usr/bin), so for the default /usr/local/bin it adds a # /usr/bin/ssv link. Keep in sync with install.sh. make_reachable() { cmd=ssv sudo_path=$(LC_ALL=C sudo -V 2>/dev/null | sed -n 's/^Value to override user.s .PATH with: //p') case ":$sudo_path:" in "::" | *":$1:"*) ;; *) if [ "$1" = /usr/local/bin ] && link_into_usr_bin "$1/ssv"; then echo "Linked /usr/bin/ssv to $1/ssv so that 'sudo ssv' works; sudo on this server only searches $sudo_path." else cmd=$1/ssv echo "Note: sudo on this server doesn't search $1, so run ssv as 'sudo $cmd'." fi ;; esac [ "$cmd" = ssv ] || return 0 found=$(command -v ssv 2>/dev/null) || found= if [ -z "$found" ]; then cmd=$1/ssv echo "Note: $1 is not in your PATH; run it as $cmd or add the directory to PATH." elif [ "$(readlink -f "$found")" != "$(readlink -f "$1/ssv")" ]; then cmd=$1/ssv echo "Note: another ssv ($found) comes first in your PATH; run this one as $cmd." fi } # link_into_usr_bin points /usr/bin/ssv at $1, unless something else is there. link_into_usr_bin() { if [ -e /usr/bin/ssv ] || [ -L /usr/bin/ssv ]; then [ "$(readlink /usr/bin/ssv)" = "$1" ] && return 0 return 1 fi ln -s "$1" /usr/bin/ssv 2>/dev/null } die() { echo "ssv installer: $*" >&2 exit 1 } fetch() { if command -v curl >/dev/null 2>&1; then curl -fsSL --proto '=https' --tlsv1.2 -o "$2" "$1" || die "download failed: $1" elif command -v wget >/dev/null 2>&1; then wget -q -O "$2" "$1" || die "download failed: $1" else die "curl or wget is required" fi } main "$@"