| 1 | #!/bin/sh |
| 2 | # Install budctl and budctl-gui on Linux, or remove them. |
| 3 | # |
| 4 | # curl -fsSL https://github.com/krolyxon/budctl/releases/latest/download/install.sh | sh |
| 5 | # ./install.sh in an unpacked release: installs the binaries next to it |
| 6 | # ./install.sh --source in a checkout: builds with cargo, then installs |
| 7 | # ./install.sh --uninstall |
| 8 | # |
| 9 | # Installs into ~/.local by default (bin/ and share/applications/), so no root is needed. |
| 10 | |
| 11 | set -eu |
| 12 | |
| 13 | REPO=krolyxon/budctl |
| 14 | PREFIX=$HOME/.local |
| 15 | VERSION=latest |
| 16 | ACTION=install |
| 17 | FROM=auto |
| 18 | |
| 19 | usage() { |
| 20 | cat <<'EOF' |
| 21 | Install budctl (command line) and budctl-gui (desktop app), or remove them. |
| 22 | |
| 23 | Usage: |
| 24 | install.sh [--prefix DIR] [--version X.Y.Z] [--source] |
| 25 | install.sh --uninstall [--prefix DIR] |
| 26 | |
| 27 | Options: |
| 28 | --prefix DIR Install into DIR/bin and DIR/share/applications (default: ~/.local) |
| 29 | --version X.Y.Z Download this release instead of the latest |
| 30 | --source Build from this checkout with cargo instead of downloading |
| 31 | --uninstall Remove what install.sh installed. Your config (~/.config/budctl) |
| 32 | and log (~/.local/state/budctl) are left alone. |
| 33 | -h, --help Show this help |
| 34 | EOF |
| 35 | } |
| 36 | |
| 37 | fail() { |
| 38 | printf 'install.sh: %s\n' "$1" >&2 |
| 39 | exit 1 |
| 40 | } |
| 41 | |
| 42 | say() { |
| 43 | printf '%s\n' "$1" |
| 44 | } |
| 45 | |
| 46 | while [ "$#" -gt 0 ]; do |
| 47 | case "$1" in |
| 48 | --prefix) |
| 49 | [ "$#" -ge 2 ] && [ -n "$2" ] || fail "--prefix needs a directory" |
| 50 | PREFIX=$2 |
| 51 | shift 2 |
| 52 | ;; |
| 53 | --version) |
| 54 | [ "$#" -ge 2 ] && [ -n "$2" ] || fail "--version needs a version, like 0.1.1" |
| 55 | VERSION=${2#v} |
| 56 | shift 2 |
| 57 | ;; |
| 58 | --source) |
| 59 | FROM=source |
| 60 | shift |
| 61 | ;; |
| 62 | --uninstall) |
| 63 | ACTION=uninstall |
| 64 | shift |
| 65 | ;; |
| 66 | -h | --help) |
| 67 | usage |
| 68 | exit 0 |
| 69 | ;; |
| 70 | *) |
| 71 | fail "unknown option: $1 (see --help)" |
| 72 | ;; |
| 73 | esac |
| 74 | done |
| 75 | |
| 76 | [ "$(uname -s)" = Linux ] || fail "budctl runs on Linux only" |
| 77 | |
| 78 | BIN_DIR=$PREFIX/bin |
| 79 | APP_DIR=$PREFIX/share/applications |
| 80 | |
| 81 | # Use sudo only when the prefix cannot be written as this user (such as /usr/local). |
| 82 | SUDO= |
| 83 | writable_dir=$PREFIX |
| 84 | while [ ! -d "$writable_dir" ]; do |
| 85 | writable_dir=$(dirname "$writable_dir") |
| 86 | done |
| 87 | if [ ! -w "$writable_dir" ] && [ "$(id -u)" -ne 0 ]; then |
| 88 | command -v sudo >/dev/null 2>&1 || fail "$PREFIX is not writable and sudo was not found" |
| 89 | SUDO=sudo |
| 90 | fi |
| 91 | |
| 92 | if [ "$ACTION" = uninstall ]; then |
| 93 | for file in "$BIN_DIR/budctl" "$BIN_DIR/budctl-gui" "$APP_DIR/budctl.desktop"; do |
| 94 | if [ -e "$file" ]; then |
| 95 | $SUDO rm -f "$file" |
| 96 | say "Removed $file" |
| 97 | fi |
| 98 | done |
| 99 | say "Your config (~/.config/budctl) and log (~/.local/state/budctl) were left alone." |
| 100 | exit 0 |
| 101 | fi |
| 102 | |
| 103 | WORK=$(mktemp -d) |
| 104 | trap 'rm -rf "$WORK"' EXIT INT TERM |
| 105 | |
| 106 | # Where the files to install come from: a release unpacked next to this script, this checkout |
| 107 | # built with cargo, or a release downloaded from GitHub. |
| 108 | SCRIPT_DIR=$(CDPATH='' cd -- "$(dirname -- "$0")" && pwd) |
| 109 | if [ "$FROM" = auto ]; then |
| 110 | if [ -f "$SCRIPT_DIR/install.sh" ] && [ -x "$SCRIPT_DIR/budctl" ] && [ -x "$SCRIPT_DIR/budctl-gui" ]; then |
| 111 | FROM=bundle |
| 112 | else |
| 113 | FROM=download |
| 114 | fi |
| 115 | fi |
| 116 | |
| 117 | case "$FROM" in |
| 118 | bundle) |
| 119 | FILES=$SCRIPT_DIR |
| 120 | DESKTOP=$SCRIPT_DIR/budctl.desktop |
| 121 | ;; |
| 122 | source) |
| 123 | [ -f "$SCRIPT_DIR/Cargo.toml" ] || fail "--source needs a budctl checkout next to install.sh" |
| 124 | command -v cargo >/dev/null 2>&1 || fail "cargo was not found; install Rust from https://rustup.rs/" |
| 125 | say "Building budctl in $SCRIPT_DIR" |
| 126 | cargo build --release --locked --manifest-path "$SCRIPT_DIR/Cargo.toml" -p budctl -p budctl-gui |
| 127 | FILES=$SCRIPT_DIR/target/release |
| 128 | DESKTOP=$SCRIPT_DIR/contrib/budctl.desktop |
| 129 | ;; |
| 130 | download) |
| 131 | case "$(uname -m)" in |
| 132 | x86_64 | amd64) TARGET=x86_64-unknown-linux-gnu ;; |
| 133 | aarch64 | arm64) TARGET=aarch64-unknown-linux-gnu ;; |
| 134 | *) fail "no release build for $(uname -m); build from a checkout with ./install.sh --source" ;; |
| 135 | esac |
| 136 | ARCHIVE=budctl-$TARGET.tar.gz |
| 137 | if [ "$VERSION" = latest ]; then |
| 138 | URL=https://github.com/$REPO/releases/latest/download |
| 139 | else |
| 140 | URL=https://github.com/$REPO/releases/download/v$VERSION |
| 141 | fi |
| 142 | |
| 143 | if command -v curl >/dev/null 2>&1; then |
| 144 | fetch() { curl -fsSL --proto '=https' --tlsv1.2 -o "$2" "$1"; } |
| 145 | elif command -v wget >/dev/null 2>&1; then |
| 146 | fetch() { wget -q -O "$2" "$1"; } |
| 147 | else |
| 148 | fail "curl or wget is needed to download budctl" |
| 149 | fi |
| 150 | |
| 151 | say "Downloading $URL/$ARCHIVE" |
| 152 | fetch "$URL/$ARCHIVE" "$WORK/$ARCHIVE" || fail "download failed: $URL/$ARCHIVE" |
| 153 | fetch "$URL/$ARCHIVE.sha256" "$WORK/$ARCHIVE.sha256" || fail "download failed: $URL/$ARCHIVE.sha256" |
| 154 | (cd "$WORK" && sha256sum -c --quiet "$ARCHIVE.sha256") || fail "checksum mismatch for $ARCHIVE" |
| 155 | tar -xzf "$WORK/$ARCHIVE" -C "$WORK" |
| 156 | FILES=$WORK/budctl-$TARGET |
| 157 | DESKTOP=$FILES/budctl.desktop |
| 158 | ;; |
| 159 | esac |
| 160 | |
| 161 | for name in budctl budctl-gui; do |
| 162 | [ -x "$FILES/$name" ] || fail "$name was not found in $FILES" |
| 163 | done |
| 164 | [ -f "$DESKTOP" ] || fail "budctl.desktop was not found at $DESKTOP" |
| 165 | |
| 166 | # The launcher entry runs the binary by its full path, so it works even when the launcher's PATH |
| 167 | # lacks the install directory. |
| 168 | sed "s|^Exec=budctl-gui|Exec=$BIN_DIR/budctl-gui|" "$DESKTOP" >"$WORK/budctl.desktop" |
| 169 | |
| 170 | $SUDO install -d "$BIN_DIR" "$APP_DIR" |
| 171 | $SUDO install -m 0755 "$FILES/budctl" "$FILES/budctl-gui" "$BIN_DIR/" |
| 172 | $SUDO install -m 0644 "$WORK/budctl.desktop" "$APP_DIR/budctl.desktop" |
| 173 | |
| 174 | say "Installed $("$BIN_DIR/budctl" --version) to $BIN_DIR (budctl, budctl-gui)" |
| 175 | say "Installed the launcher entry to $APP_DIR/budctl.desktop" |
| 176 | case ":$PATH:" in |
| 177 | *":$BIN_DIR:"*) ;; |
| 178 | *) say "Note: $BIN_DIR is not on your PATH. Add it to run budctl from a terminal." ;; |
| 179 | esac |