├── .github └── workflows │ └── ci.yml ├── .pre-commit-config.yaml ├── Changelog.md ├── LICENSE ├── README.md ├── bin └── sdd ├── bootstrap.sh ├── completion ├── _sdd └── sdd ├── demo.svg ├── development.md ├── lib └── sdd │ ├── apps │ └── user │ │ ├── bat │ │ ├── borg │ │ ├── broot │ │ ├── circleci │ │ ├── dasel │ │ ├── delta │ │ ├── diff-so-fancy │ │ ├── direnv │ │ ├── docker-compose │ │ ├── dust │ │ ├── fd │ │ ├── ffsend │ │ ├── gh │ │ ├── git-trim │ │ ├── gitui │ │ ├── go │ │ ├── hub │ │ ├── jira │ │ ├── jq │ │ ├── ncdu │ │ ├── oh-my-zsh │ │ ├── pandoc │ │ ├── pip │ │ ├── python │ │ ├── qmlfmt │ │ ├── qrcp │ │ ├── ripgrep │ │ ├── sdd │ │ ├── shellcheck │ │ ├── shfmt │ │ ├── slack-term │ │ ├── telegram │ │ ├── wuzz │ │ ├── xh │ │ └── xsv │ └── framework │ └── utils.bash ├── release └── test ├── apps ├── bat.bats ├── borg.bats ├── broot.bats ├── circleci.bats ├── dasel.bats ├── delta.bats ├── diff-so-fancy.bats ├── direnv.bats ├── docker-compose.bats ├── dust.bats ├── fd.bats ├── ffsend.bats ├── gh.bats ├── git-trim.bats ├── gitui.bats ├── go.bats ├── hub.bats ├── jira.bats ├── jq.bats ├── ncdu.bats ├── oh-my-zsh.bats ├── pandoc.bats ├── pip.bats ├── python.bats ├── qmlfmt.bats ├── qrcp.bats ├── ripgrep.bats ├── sdd.bats ├── shellcheck.bats ├── shfmt.bats ├── slack-term.bats ├── telegram.bats ├── wuzz.bats ├── xh.bats └── xsv.bats ├── framework ├── bin.bats ├── bootstrap.bats └── fixtures │ └── valid_app ├── run.sh └── setup ├── Dockerfile ├── docker-compose.test.yml ├── pre-requirements.txt ├── requirements.txt └── venv /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: CI 4 | 5 | # Controls when the action will run. 6 | on: 7 | # Triggers the workflow on push or pull request events but only for the master branch 8 | push: 9 | branches: [ master ] 10 | pull_request: 11 | branches: [ master ] 12 | 13 | # Allows you to run this workflow manually from the Actions tab 14 | workflow_dispatch: 15 | 16 | jobs: 17 | # This workflow contains a single job called "build" 18 | build: 19 | # The type of runner that the job will run on 20 | runs-on: ubuntu-latest 21 | container: 22 | image: pylipp/sdd:latest 23 | 24 | steps: 25 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 26 | - uses: actions/checkout@v2 27 | 28 | - name: Check style 29 | run: | 30 | /opt/shellcheck -x bin/sdd lib/sdd/framework/utils.bash 31 | 32 | - name: Run test suite 33 | run: | 34 | ls /__w/sdd/sdd 35 | export PATH=/root/.local/bin:/__w/sdd/sdd/bin:$PATH 36 | echo $PATH 37 | ./test/run.sh 38 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v2.4.0 4 | hooks: 5 | - id: trailing-whitespace 6 | - id: end-of-file-fixer 7 | - id: check-yaml 8 | - id: check-added-large-files 9 | - id: check-merge-conflict 10 | - repo: local 11 | hooks: 12 | - id: shellcheck 13 | name: shellcheck 14 | entry: pylipp/sdd:latest /opt/shellcheck -x 15 | language: docker_image 16 | types: [shell] 17 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). 5 | 6 | ## [v0.2.3.1] - 2025-04-01 7 | ### Fixed 8 | - App management file for [jq](https://github.com/jqlang/jq): update GitHub repo, install man page 9 | 10 | ## [v0.2.3.0] - 2023-10-05 11 | ### Added 12 | - App management file for [Python](https://github.com/indygreg/python-build-standalone) 13 | 14 | ## [v0.2.2.0] - 2023-01-08 15 | ### Added 16 | - App management file for [docker-compose](https://github.com/docker/compose) (v2) 17 | - App management file for [ffsend](https://github.com/timvisee/ffsend) 18 | - App management file for [borg](https://www.borgbackup.org/) 19 | - App management file for [circleci](https://github.com/CircleCI-Public/circleci-cli) 20 | 21 | ## [v0.2.1.0] - 2022-01-22 22 | ### Added 23 | - App management file for [xsv](https://github.com/BurntSushi/xsv) 24 | - App management file for [Telegram](https://github.com/telegramdesktop/tdesktop) 25 | - App management file for [Pandoc](https://github.com/jgm/pandoc) 26 | - App management file for [wuzz](https://github.com/asciimoo/wuzz) 27 | - App management file for [xh](https://github.com/ducaale/xh) 28 | - App management file for [gitui](https://github.com/extrawurst/gitui) 29 | - App management file for [ncdu](https://dev.yorhel.nl/ncdu) 30 | - App management file for [dasel](https://github.com/TomWright/dasel) 31 | - App management file for [git-trim](https://github.com/foriequal0/git-trim) 32 | - App management file for [go](https://github.com/golang/go). Note that you should add `export GOROOT=~/.local/go; export GOPATH=~/.local/share/goprojects; export PATH=$GOPATH/bin:$GOROOT/bin:$PATH` or similar to your `~/.profile` 33 | ### Changed 34 | - Use GitHub Actions for CI, see #15 35 | ### Fixed 36 | - New download URL for `ncdu`, see #18 37 | - Update bash completion file path for `fd`, see #18 38 | - Support installation of `pip` on Python 3.5, see #18 39 | 40 | ## [v0.2.0.0] - 2020-07-31 41 | ### Added 42 | - App management file for [gh](https://github.com/cli/cli) 43 | - App management file for [jira](https://github.com/go-jira/jira) 44 | - App management file for [delta](https://github.com/dandavison/delta) 45 | - App management file for [qrcp](https://github.com/claudiodangelis/qrcp) 46 | - App management file for [slack-term](https://github.com/erroneousboat/slack-term) 47 | - App management file for [dust](https://github.com/bootandy/dust) 48 | - The corresponding bash completion is installed with `fd`. 49 | - The corresponding bash completion is installed with `ripgrep`. 50 | - man pages are installed with `gh` (available since v0.9.0) 51 | - Short forms for all provided options, see #11 52 | - Bash and zsh completion scripts, see #13 53 | ### Changes 54 | - Avoid using GitHub API calls, see #11 55 | - `list` command without further option defaults to show installed packages, see #11 56 | - Bash completion for apps is now installed into `~/.local/share/bash-completion/completions`, see #13 57 | ### Fixed 58 | - Avoid false matching by part of package names, see #11 59 | - Update download URL for shellcheck releases, see #14 60 | 61 | ## [v0.1.1.0] - 2020-03-14 62 | ### Added 63 | - App management file for [shfmt](https://github.com/mvdan/sh) 64 | - When installing shellcheck, the corresponding man page is installed if pandoc is available. 65 | - When running install/upgrade/uninstall, a spinner is displayed to indicate background activity. 66 | - `sdd` is more verbose about internal processes if the `SDD_VERBOSE` environment variable is set. 67 | ### Changed 68 | - Specifying a version for installing or upgrading pip is possible. 69 | - Upgrading oh-my-zsh now git-pulls in existing `$ZSH` repository. 70 | - Any output from `sdd_*` management functions is redirected to a log file. 71 | ### Fixed 72 | - Handle non-existing custom apps directory when listing available apps. 73 | 74 | ## [v0.1.0.0] - 2020-02-06 75 | First release. 76 | ### Added 77 | - Changelog file and related tooling. 78 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Philipp Metzner 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `setup-da-distro` 2 | 3 | [![Build Status](https://travis-ci.org/pylipp/sdd.svg?branch=master)](https://travis-ci.org/pylipp/sdd) 4 | 5 | > A framework to manage programs from web sources for non-privileged users on Linux systems 6 | ## Motivation 7 | 8 | During occasional strolls on reddit or github, my attention is often drawn towards programs that increase productivity or provide an enhancement over others. (As a somewhat irrelevant side note - these programs mostly work in the command line.) Usually these programs are available for download as binary or script, meaning that naturally, the management (installation, upgrade, removal) of those programs has to be performed manually. At this point `sdd` comes into play: It provides a framework to automatize the tasks of managing the programs (or, in `sdd` terminology, 'apps'). The procedures to manage specific apps are defined within scripts in this repository (at `lib/sdd/apps/user/`). 9 | 10 | `sdd` enables me to keep track of my favorite programs, on different machines. I'm working towards having systems set up in a reproducible way on my machines. `sdd` helps me, since I might have different Linux distributions installed on these machine, with different package manager providing different versions of required programs (or none at all). I can freeze the versions of all apps managed by sdd with `sdd list --installed > sdd_freeze.txt`, and re-create them with `sdd install <(cat sdd_freeze.txt | xargs)`. 11 | 12 | Finally on some systems I might not have root access, and hence I can't install missing programs using a package manager. `sdd` allows for unprivileged management of programs. 13 | 14 | ## WARNINGS 15 | 16 | `sdd` is a simple collection of bash scripts, not a mature package manager (neither do I aim to turn it into one...). Using it might break things on your system (e.g. overwrite existing program files). 17 | 18 | When using `sdd`, you execute functionality to manipulate your system. Especially, you download programs from third parties, and install them on your system. Most sources are provided by GitHub releases pages. Keep in mind that repositories can be compromised, and malicious code placed inside; and `sdd` will still happily download it. (If you have an idea how to mitigate this security flaw, please open an issue.) 19 | 20 | `sdd` is targeted to 64bit Linux systems. Some apps might not work when installed to different architectures. If available, `bash` and/or `zsh` shell completion and `man` pages are set up when installing an app. 21 | 22 | ## Demo 23 | 24 | The following screencast demonstrates how `sdd` is used to install, upgrade and uninstalled the [`fd`](https://github.com/sharkdp/fd) utility. 25 | 26 | ![Demo](./demo.svg) 27 | 28 | ## Installation 29 | 30 | Clone the directory and run the bootstrap script to install `sdd` to `~/.local`: 31 | 32 | git clone https://github.com/pylipp/sdd 33 | cd sdd 34 | git checkout v0.1.1.0 # or any other revision 35 | ./bootstrap.sh 36 | 37 | You can specify the installation directory with the `PREFIX` environment variable: 38 | 39 | PREFIX=/usr ./bootstrap.sh 40 | 41 | Please verify that the `bin` sub-directory of the installation directory is present in your `PATH`. You might want to append this to your shell configuration file: 42 | 43 | export PATH="~/.local/bin:$PATH" 44 | 45 | Same applies for the `MANPATH`: 46 | 47 | export MANPATH="~/.local/share/man:$MANPATH" 48 | 49 | For enabling `zsh` completion functions (`oh-my-zsh` users: put this before the line that sources `oh-my-zsh.sh` since it calls `compinit` for setting up completions): 50 | 51 | fpath=(~/.local/share/zsh/site-functions $fpath) 52 | 53 | For enabling `bash` completion functions, you should be fine if you already use the [`bash-completion`](https://github.com/scop/bash-completion) package. Otherwise add this snippet to your `~/.bashrc`: 54 | 55 | ```bash 56 | if [ -d ~/.local/share/bash_completion ]; then 57 | # Source custom completion files 58 | while IFS= read -r -d '' f; do 59 | . "$f" 60 | done < <(find ~/.local/share/bash_completion -type f -print0) 61 | fi 62 | ``` 63 | 64 | `sdd` is tested with `bash` 4.4.12. 65 | 66 | ### Dependencies 67 | 68 | `sdd` depends on `bash`, `git`, and `wget`. Python-related apps require `python3`. 69 | 70 | ## Upgrading 71 | 72 | Once the program is bootstrapped, upgrade to the latest version by 73 | 74 | sdd upgrade sdd 75 | 76 | ## Usage 77 | 78 | ### Installing an app 79 | 80 | Install an app to `SDD_INSTALL_PREFIX` (defaults to `~/.local`) with 81 | 82 | sdd install 83 | 84 | You can specify a custom installation prefix like this: 85 | 86 | SDD_INSTALL_PREFIX=~/bin sdd install 87 | 88 | or by exporting the `SDD_INSTALL_PREFIX` environment variable. 89 | 90 | By default, `sdd` installs the latest version of the app available. You can specify a version for installation: 91 | 92 | sdd install = 93 | 94 | > This command overwrites an existing installation of the app without additional conformation. 95 | 96 | > The format of the `` specifier depends on the app that is managed (usually it's the tag of the release on GitHub). 97 | 98 | ### Upgrading an app 99 | 100 | To upgrade an app to the latest version available, run 101 | 102 | sdd upgrade 103 | 104 | If you want to upgrade to a specific version, run 105 | 106 | sdd upgrade = 107 | 108 | Internally, `sdd` executes un- and re-installation of the app for upgrading unless a specific upgrade routine has been defined. 109 | The usage of `SDD_INSTALL_PREFIX` is the same as for the `install` command. 110 | 111 | ### Uninstalling an app 112 | 113 | To uninstall an app, run 114 | 115 | sdd uninstall 116 | 117 | The usage of `SDD_INSTALL_PREFIX` is the same as for the `install` command. 118 | 119 | ### Batch commands 120 | 121 | The commands `install`, `upgrade`, and `uninstall` can take multiple arguments to manage apps, e.g. 122 | 123 | sdd install = 124 | 125 | ### Listing app management information 126 | 127 | List installed apps by running 128 | 129 | sdd list 130 | sdd list --installed 131 | 132 | List all apps available for management in `sdd` with 133 | 134 | sdd list --available 135 | 136 | List all installed apps that can be upgraded to a more recent version with 137 | 138 | sdd list --upgradable 139 | 140 | The `list` command options come in short forms, too: `-i`, `-a`, `-u` 141 | 142 | ### General help 143 | 144 | High-level program output during management is forwarded to the terminal. Output of the `sdd_*` functions of the app management file is in `/tmp/sdd--.stderr`. For increased verbosity when running `sdd`, set the respective environment variable before invoking the program 145 | 146 | SDD_VERBOSE=1 sdd install 147 | 148 | You can always consult 149 | 150 | sdd --help 151 | 152 | ## Apps available 153 | 154 | In alphabetical order: 155 | 156 | Name | Description 157 | :--- | :--- 158 | [bat](https://github.com/sharkdp/bat) | A cat(1) clone with syntax highlighting and Git integration 159 | [borg](https://www.borgbackup.org/) | Deduplicating archiver with compression and authenticated encryption 160 | [broot](https://github.com/Canop/broot) | A new way to see and navigate directory trees 161 | [circleci](https://github.com/CircleCI-Public/circleci-cli) | Use CircleCI from the command line 162 | [dasel](https://github.com/TomWright/dasel) | Query and update data structures from the command line 163 | [delta](https://github.com/dandavison/delta) | A syntax-highlighter for git and diff output 164 | [diff-so-fancy](https://github.com/so-fancy/diff-so-fancy) | Human readable diffs 165 | [direnv](https://github.com/direnv/direnv) | Handle environment variables depending on current directory 166 | [docker-compose](https://github.com/docker/compose) | Define and run multi-container applications with Docker (v2) 167 | [dust](https://github.com/bootandy/dust) | A more intuitive version of du in rust 168 | [fd](https://github.com/sharkdp/fd) | A simple, fast and user-friendly alternative to 'find' 169 | [ffsend](https://github.com/timvisee/ffsend) | Easily and securely share files from the command line 170 | [gh](https://github.com/cli/cli) | GitHub's official command line tool 171 | [gitui](https://github.com/extrawurst/gitui) | blazing fast terminal-ui for git written in rust 172 | [git-trim](https://github.com/foriequal0/git-trim) | Automatically trims your branches whose tracking remote refs are merged or stray 173 | [go](https://github.com/golang/go) | The Go programming language 174 | [hub](https://github.com/github/hub) | Command line tool to interact with GitHub 175 | [jira](https://github.com/go-jira/jira) | Simple JIRA command line client in Go 176 | [jq](https://github.com/stedolan/jq) | Command line JSON processor 177 | [ncdu](https://dev.yorhel.nl/ncdu) | Disk usage analyzer with ncurses interface 178 | [oh-my-zsh](https://github.com/robbyrussell/oh-my-zsh) | Framework for managing zsh configuration 179 | [Pandoc](https://github.com/jgm/pandoc) | Universal markup converter 180 | [pip](https://pypi.org/project/pip/) | Python package manager 181 | [Python](https://github.com/indygreg/python-build-standalone) | Python language installed from redistributable builds 182 | [qrcp](https://github.com/claudiodangelis/qrcp) | Transfer files over wifi from your computer to your mobile device by scanning a QR code 183 | [ripgrep](https://github.com/BurntSushi/ripgrep) | Line-oriented text search tool 184 | sdd | Thanks for being here :) 185 | [slack-term](https://github.com/erroneousboat/slack-term) | Slack client for your terminal 186 | [ShellCheck](https://github.com/koalaman/shellcheck) | A static analysis tool for shell scripts 187 | [shfmt](https://github.com/mvdan/sh) | A shell parser, formatter, and interpreter (sh/bash/mksh) 188 | [Telegram](https://github.com/telegramdesktop/tdesktop) | Telegram Desktop messaging app 189 | [wuzz](https://github.com/asciimoo/wuzz) | Interactive cli tool for HTTP inspection 190 | [xh](https://github.com/ducaale/xh) | Friendly and fast tool for sending HTTP requests 191 | [xsv](https://github.com/BurntSushi/xsv) | A fast CSV command line toolkit written in Rust 192 | 193 | ## Customization 194 | 195 | You can both 196 | 197 | - define app management files for apps that are not shipped with `sdd`, and 198 | - extend app management files for apps that are shipped with `sdd`. 199 | 200 | The procedure in either case is: 201 | 202 | 1. Create an empty bash file named after the app in `~/.config/sdd/apps` (without `.bash` extension). 203 | 1. Add the functions `sdd_install` and `sdd_uninstall` with respective functionality. It's mandatory to add a function, even if without functionality (define `sdd_uninstall() { return; }`). 204 | 1. Optionally, you can add an `sdd_upgrade` function. It will be executed for upgrading, instead of `sdd_uninstall` followed by `sdd_install`. 205 | 1. You're able to manage the app as described in the 'Usage' section. `sdd` tells you when it found a customization for the app specified on the command line. 206 | 207 | For exemplary files, see my personal definitions and extensions [here](https://github.com/pylipp/dotfiles/tree/master/sdd_apps). 208 | 209 | ## Project structure 210 | 211 | It is distinguished between 212 | 213 | - framework files, 214 | - app management files, 215 | - testing files, and 216 | - project meta-files. 217 | 218 | ### Description 219 | 220 | 1. Framework files contain the logic to run the program. They provide generic utility methods (generating symlinks, reading environment variables, etc.). Examples are: program executable, library files. 221 | 1. App management files contain instructions to manage specific apps. For each app, at least one management file exists. A management file contains at least methods for installing, upgrading, and uninstalling an app. Management files are organized in directories indicating their level (user or root). 222 | 1. Testing files cover the functionality of both the framework and the app management files. They are executed in an isolated environment. 223 | 1. Project meta-files comprise documentation files, configuration files for development tools, an installation script, among others. 224 | 225 | ## Contributing 226 | 227 | ### Requirements 228 | 229 | - `git` 230 | - `docker` 231 | - `python3` 232 | - optionally: `docker-compose` 233 | 234 | ### Development environment 235 | 236 | Clone this repository and pull the Docker image to enable testing and style-checking. 237 | 238 | git clone https://github.com/pylipp/sdd 239 | docker pull pylipp/sdd 240 | 241 | Set up the local environment for style-checking using the [pre-commit](https://pre-commit.com/) framework. 242 | 243 | test/setup/venv 244 | 245 | ### Testing 246 | 247 | The program is tested in a container environment using the `bats` framework. Invoke the test runner by 248 | 249 | test/run.sh 250 | 251 | You might want to skip app tests since they require an internet connection 252 | 253 | NO_APP_TESTS=1 test/run.sh 254 | 255 | For attaching to the test container after the tests have completed, do 256 | 257 | test/run.sh --debug 258 | 259 | For running specific tests (paths relative to repository root) 260 | 261 | test/run.sh test/apps/fd.bats test/apps/sdd.bats 262 | 263 | For creating a Docker container and attaching it to the terminal, do 264 | 265 | test/run.sh --open 266 | 267 | For style checking, run 268 | 269 | test/run.sh --style 270 | 271 | For building the image, run 272 | 273 | docker build test/setup -t sdd:latest 274 | 275 | You can also build and test the image in a single command by 276 | 277 | docker-compose -f test/setup/docker-compose.test.yml up 278 | 279 | Note that DockerHub automatically builds the image when source code is pushed to GitHub, and pushes it the DockerHub repository if the tests succeeded. The tests are defined in `test/setup/docker-compose.test.yml`. 280 | 281 | ### Extending 282 | 283 | You're looking for managing an app but it's not included in `sdd` yet? Here's how contribute an app management script: 284 | 285 | 1. Fork this repository. 286 | 1. In your fork, create a feature branch. 287 | 1. Create an empty bash file named after the app in `lib/sdd/apps/user`. 288 | 1. Add a test in `test/apps/.bats`, e.g. verifying the version of the app to be installed. 289 | 1. Add the functions `sdd_install` and `sdd_uninstall` with respective functionality. 290 | 1. Add app name, link, and description to the table of available apps in the README file. 291 | 1. Add the new files, commit, and push. 292 | 1. Open a PR! 293 | 294 | ### Releasing 295 | 296 | 1. Update Changelog. 297 | 1. Run `./release VERSION` 298 | 299 | ## Related projects 300 | 301 | Use case | Tool 302 | --- | --- 303 | Managing Python packages (system-wide or user-specific) | pip 304 | Managing Python apps (system-wide or user-specific) | [pipx](https://pipxproject.github.io/pipx/) 305 | Generate packages from Makefile and track installation by package manager | [CheckInstall](https://asic-linux.com.mx/~izto/checkinstall/) 306 | Declarative whole-system configuration; unprivileged package management | [GNU Guix](https://guix.gnu.org/) 307 | Creating packages of various formats | [fpm](https://github.com/jordansissel/fpm) 308 | 309 | Note that maintaining packages (deb, rpm, etc.) might still require root privileges, depending on your system. 310 | -------------------------------------------------------------------------------- /bin/sdd: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Main executable of the sdd program 4 | 5 | SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 6 | 7 | # shellcheck source=lib/sdd/framework/utils.bash 8 | source "$SCRIPTDIR"/../lib/sdd/framework/utils.bash 9 | 10 | SDD_INSTALL_PREFIX=${SDD_INSTALL_PREFIX:-$HOME/.local} 11 | export SDD_INSTALL_PREFIX 12 | mkdir -p "$SDD_INSTALL_PREFIX"/bin 13 | 14 | SDD_DATA_DIR=${XDG_DATA_DIR:-$HOME/.local/share}/sdd 15 | export SDD_DATA_DIR 16 | mkdir -p "$SDD_DATA_DIR"/apps 17 | 18 | SDD_BASH_COMPLETION_DIR=${BASH_COMPLETION_USER_DIR:-${XDG_DATA_HOME:-$SDD_INSTALL_PREFIX/share}/bash-completion}/completions 19 | SDD_ZSH_COMPLETION_DIR="${XDG_DATA_HOME:-$SDD_INSTALL_PREFIX/share}"/zsh/site-functions 20 | export SDD_BASH_COMPLETION_DIR 21 | export SDD_ZSH_COMPLETION_DIR 22 | mkdir -p "$SDD_BASH_COMPLETION_DIR" "$SDD_ZSH_COMPLETION_DIR" 23 | 24 | main() { 25 | local command=$1 26 | local exit_code=0 27 | 28 | case "$command" in 29 | install) 30 | shift 31 | utils_install "$@" 32 | exit_code=$? 33 | ;; 34 | upgrade) 35 | shift 36 | utils_upgrade "$@" 37 | exit_code=$? 38 | ;; 39 | uninstall) 40 | shift 41 | utils_uninstall "$@" 42 | exit_code=$? 43 | ;; 44 | list) 45 | shift 46 | utils_list "$@" 47 | exit_code=$? 48 | ;; 49 | -h | --help) 50 | utils_usage 51 | ;; 52 | -V | --version) 53 | utils_version 54 | exit_code=$? 55 | ;; 56 | ?*) 57 | printf 'Unknown command "%s"\n' "$command" >&2 58 | exit_code=127 59 | ;; 60 | *) 61 | utils_usage 62 | ;; 63 | esac 64 | 65 | exit $exit_code 66 | } 67 | 68 | main "$@" 69 | -------------------------------------------------------------------------------- /bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 6 | 7 | prefix=${PREFIX:-~/.local} 8 | 9 | for dep in wget bash; do 10 | if ! command -v $dep >/dev/null 2>&1; then 11 | echo "Could not find $dep." >&2 12 | exit 1 13 | fi 14 | done 15 | 16 | mkdir -p "$prefix"/{bin,lib} 17 | 18 | cp "$SCRIPTDIR"/bin/sdd "$prefix"/bin 19 | cp -r "$SCRIPTDIR"/lib/sdd "$prefix"/lib 20 | 21 | if [[ ! "$PATH" == *"$prefix/bin"* ]]; then 22 | export PATH="$prefix/bin:$PATH" 23 | fi 24 | 25 | # Install shell completions, compatible to bash-completion 26 | # https://github.com/scop/bash-completion/blob/328ce5be5b4b8822b3b9421dab4460c56990df0b/bash_completion#L2159 27 | SDD_BASH_COMPLETION_DIR=${BASH_COMPLETION_USER_DIR:-${XDG_DATA_HOME:-$prefix/share}/bash-completion}/completions 28 | SDD_ZSH_COMPLETION_DIR="${XDG_DATA_HOME:-$prefix/share}"/zsh/site-functions 29 | mkdir -p "$SDD_BASH_COMPLETION_DIR" "$SDD_ZSH_COMPLETION_DIR" 30 | cp "$SCRIPTDIR"/completion/sdd "$SDD_BASH_COMPLETION_DIR" 31 | cp "$SCRIPTDIR"/completion/_sdd "$SDD_ZSH_COMPLETION_DIR" 32 | 33 | # Record installed version 34 | if ! latest_tag="$(git tag --list --sort -refname | grep -m1 -E 'v0.[0-9]+.[0-9]+.[0-9]+')"; then 35 | echo "Failed to find latest tag!" >&2 36 | exit 1 37 | fi 38 | 39 | head_sha="$(git rev-parse HEAD)" 40 | if [[ "$(git rev-parse "$latest_tag")" != "$head_sha" ]]; then 41 | # construct version identifier of form 'v0.X.Y.Z (+N @d34dc0ffee)' 42 | nr_commits_since_latest_tag="$(git rev-list "$latest_tag".. --count)" 43 | latest_tag="$latest_tag (+$nr_commits_since_latest_tag @$head_sha)" 44 | fi 45 | 46 | echo "$latest_tag" > "$prefix"/lib/sdd/version 47 | 48 | SDD_APPS_DIR=${XDG_DATA_HOME:-$HOME/.local/share}/sdd/apps 49 | mkdir -p "$SDD_APPS_DIR" 50 | echo sdd="$head_sha" >> "$SDD_APPS_DIR"/installed 51 | -------------------------------------------------------------------------------- /completion/_sdd: -------------------------------------------------------------------------------- 1 | #compdef sdd 2 | # 3 | # @author: Sergei Eremenko (https://github.com/SmartFinn) 4 | # @license: MIT license (MIT) 5 | # @link: https://github.com/pylipp/sdd 6 | 7 | local state line ret=1 8 | 9 | _sdd_list_available() { 10 | local app_name 11 | local -a available_apps 12 | 13 | (( $+commands[sdd] )) || return 1 14 | 15 | while read -r _ app_name; do 16 | [ -n "$app_name" ] || continue 17 | available_apps+=("$app_name") 18 | done < <(sdd list --available) 19 | 20 | _wanted colors exlp 'available apps' compadd -- "${available_apps[@]}" 21 | return 0 22 | } 23 | 24 | _sdd_list_installed() { 25 | local app_name 26 | local -a installed_apps 27 | 28 | (( $+commands[sdd] )) || return 1 29 | 30 | while IFS='=' read -r app_name _; do 31 | [ -n "$app_name" ] || continue 32 | installed_apps+=("$app_name") 33 | done < <(sdd list --installed) 34 | 35 | _wanted colors exlp 'installed apps' compadd -- "${installed_apps[@]}" 36 | return 0 37 | } 38 | 39 | _arguments -C \ 40 | '(-h --help)'{-h,--help}'[Display help message]' \ 41 | '(-V --version)'{-v,--verbose}'[Display version information]' \ 42 | '1:commands:->cmds' \ 43 | '*:arguments:->args' && ret=0 44 | 45 | case $state in 46 | (cmds) 47 | local -a cmds 48 | 49 | cmds=( 50 | 'install:Install specified packages' 51 | 'upgrade:Upgrade installed packages' 52 | 'uninstall:Uninstall specified packages' 53 | 'list:List installed packages' 54 | ) 55 | 56 | _describe -t commands 'commands' cmds && ret=0 57 | ;; 58 | (args) 59 | case $line[1] in 60 | (install) 61 | _arguments \ 62 | '*:available apps:_sdd_list_available' && ret=0 63 | ;; 64 | (uninstall|upgrade) 65 | _arguments \ 66 | '*:installed apps:_sdd_list_installed' && ret=0 67 | ;; 68 | (list) 69 | _values 'options' \ 70 | {-a,--available}'[List apps available for installation]' \ 71 | {-i,--installed}'[List installed apps]' \ 72 | {-u,--upgradable}'[List apps that can be upgraded]' \ 73 | && ret=0 74 | ;; 75 | esac 76 | ;; 77 | esac 78 | 79 | return $ret 80 | 81 | # vim: ft=zsh sw=4 ts=4 et 82 | -------------------------------------------------------------------------------- /completion/sdd: -------------------------------------------------------------------------------- 1 | # sdd completion 2 | # 3 | # @author: Sergei Eremenko (https://github.com/SmartFinn) 4 | # @license: MIT license (MIT) 5 | # @link: https://github.com/pylipp/sdd 6 | 7 | _sdd_list_available() { 8 | local app_name 9 | 10 | command -v sdd >/dev/null || return 1 11 | 12 | while read -r _ app_name; do 13 | [ -n "$app_name" ] || continue 14 | printf '%s\n' "$app_name" 15 | done < <(sdd list --available) 16 | 17 | return 0 18 | } 19 | 20 | _sdd_list_installed() { 21 | local app_name 22 | 23 | command -v sdd >/dev/null || return 1 24 | 25 | while IFS='=' read -r app_name _; do 26 | [ -n "$app_name" ] || continue 27 | printf '%s\n' "$app_name" 28 | done < <(sdd list --installed) 29 | 30 | return 0 31 | } 32 | 33 | _sdd_completion() { 34 | local cur prev word cword 35 | local -a words=() 36 | local -a opts=( 37 | install 38 | list 39 | uninstall 40 | upgrade 41 | -h --help 42 | -V --version 43 | ) 44 | local -a list_opts=( 45 | -a --available 46 | -i --installed 47 | -u --upgradable 48 | ) 49 | 50 | _init_completion || return 51 | 52 | for word in "${words[@]}"; do 53 | case "$word" in 54 | install) 55 | COMPREPLY=( $(compgen -W "$(_sdd_list_available)" -- "$cur") ) 56 | return 0 57 | ;; 58 | uninstall|upgrade) 59 | COMPREPLY=( $(compgen -W "$(_sdd_list_installed)" -- "$cur") ) 60 | return 0 61 | ;; 62 | esac 63 | done 64 | 65 | case "$prev" in 66 | list) 67 | COMPREPLY=($(compgen -W "${list_opts[*]}" -- "$cur")) 68 | return 0 69 | ;; 70 | -h | --help | \ 71 | -V | --version | \ 72 | -a | --available | \ 73 | -i | --installed | \ 74 | -u | --upgradable) 75 | # stop completion if one of these option already specified 76 | return 0 77 | ;; 78 | *) 79 | COMPREPLY=($(compgen -W "${opts[*]}" -- "$cur")) 80 | return 0 81 | ;; 82 | esac 83 | } 84 | 85 | complete -F _sdd_completion sdd 86 | 87 | # vim: filetype=sh sw=4 ts=4 et 88 | -------------------------------------------------------------------------------- /development.md: -------------------------------------------------------------------------------- 1 | It is attempted to derive the development of `sdd` according to requirements and specifications. 2 | 3 | ### Program 4 | 5 | Requirement | Specification 6 | --- | --- 7 | The program shall run on Linux systems. | The program can be executed in a Docker container. 8 | The program shall have as little dependencies as possible. | The program depends on `bash`, `git` and `wget`. Python-related apps require `python3`. 9 | The program shall be simple to install. | A bootstrap installation script is provided. 10 | The program shall be simple to upgrade. | The program is able to upgrade itself. 11 | The program shall expose a user-friendly command line interface. | The program complies to standards given by common command line tools. 12 | The program shall provide functionality to manage apps that are not made available by distribution package managers. The fundamental functionality comprises installation, removal, and upgrading. Management includes the app's binary (i.e. main executable), runtime files (e.g. library files), and convenience (e.g. shell completion or man pages) files. | The program provides an `install` command to install one or more apps and a `uninstall` command to uninstall one or more apps. 13 | The program shall allow `user`s (i.e. without requiring super-user privileges) to manage apps. | The program manages apps in the user's home directory in subdirectories of `~/.local`. 14 | The program shall optionally allow `root` to manage apps. | 15 | The program shall optionally allow for 'hybrid' (user and root) management. | 16 | The program shall allow for custom management that extends or overwrites the built-in management. | The program takes custom app management files in the `~/.config/sdd/apps` directory into account for additional management instructions. 17 | The program shall inform about optional dependencies required during app installation. | 18 | The program shall install the latest released version of an app if not specified otherwise. | The program is able to find the latest version of an app online. 19 | The program shall take user-defined app versions into account for installation. | The program takes into account an app version specified in the CLI when installing. This takes precedence of an app version specified in the program configuration. 20 | The program shall keep track of installed apps. | The program holds a record of installed apps. 21 | The program shall enable reproducible app installations. | The record of installed apps can be queried, and e.g. be redirected to a file. 22 | The program shall upgrade an app only if the specified version is not yet installed. | 23 | The program shall install app dependencies using the distribution package manager if unavoidable. | 24 | The program shall provide useful output when a command fails. | The program validates user input early and verifies app management files. 25 | The program shall be configurable. | 26 | The program shall provide functionality to list installed apps. | The program provides a `list` command with the option `--installed`. 27 | The program shall provide functionality to list available apps. | The program provides a `list` command with the option `--available`. 28 | The program shall provide functionality to manage system configuration (e.g. enabling automatic mounting of USB devices). | 29 | The program shall optionally output information about internal procedures. | The program provides a `--verbose` option. 30 | 31 | ### Development 32 | 33 | The program shall be extensively tested using state-of-the-art tools (CI, containers, test libraries). 34 | The apps available for installation via the program shall be extensible by community contributions. 35 | The program shall be developed using modern methods (linter, formatter) if not obstructive. 36 | 37 | ### Exemplary visualization 38 | 39 | . 40 | ├── bin 41 | │   └── program 42 | ├── installer 43 | ├── lib 44 | │   └── program 45 | │   ├── apps 46 | │   │   ├── root 47 | │   │   │   └── reqs 48 | │   │   └── user 49 | │   └── framework 50 | └── test 51 | ├── apps 52 | ├── framework 53 | └── setup 54 | -------------------------------------------------------------------------------- /lib/sdd/apps/user/bat: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | sdd_install() { 4 | local version=$1 5 | local package=bat-$version-x86_64-unknown-linux-musl 6 | 7 | wget -P /tmp https://github.com/sharkdp/bat/releases/download/$version/$package.tar.gz 8 | cd /tmp 9 | tar xf $package.tar.gz 10 | 11 | mv $package/bat "$SDD_INSTALL_PREFIX"/bin 12 | 13 | # Install man page 14 | mkdir -p "$SDD_INSTALL_PREFIX"/share/man/man1 15 | mv $package/bat.1 "$SDD_INSTALL_PREFIX"/share/man/man1 16 | 17 | rm -rf $package.tar.gz $package 18 | } 19 | 20 | sdd_uninstall() { 21 | rm -f "$SDD_INSTALL_PREFIX"/bin/bat 22 | rm -f "$SDD_INSTALL_PREFIX"/share/man/man1/bat.1 23 | } 24 | 25 | sdd_fetch_latest_version() { 26 | _tag_name_of_latest_github_release sharkdp bat 27 | } 28 | -------------------------------------------------------------------------------- /lib/sdd/apps/user/borg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | sdd_install() { 4 | local version=$1 5 | wget -O "$SDD_INSTALL_PREFIX/bin/borg" https://github.com/borgbackup/borg/releases/download/"$version"/borg-linux64 6 | chmod u+x "$SDD_INSTALL_PREFIX/bin/borg" 7 | } 8 | 9 | sdd_uninstall() { 10 | rm -f "$SDD_INSTALL_PREFIX/bin/borg" 11 | } 12 | 13 | sdd_fetch_latest_version() { 14 | _tag_name_of_latest_github_release borgbackup borg 15 | } 16 | -------------------------------------------------------------------------------- /lib/sdd/apps/user/broot: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | sdd_install() { 4 | local version=$1 5 | wget -O "$SDD_INSTALL_PREFIX/bin/broot" https://github.com/Canop/broot/releases/download/$version/broot 6 | chmod u+x "$SDD_INSTALL_PREFIX/bin/broot" 7 | } 8 | 9 | sdd_uninstall() { 10 | rm -f "$SDD_INSTALL_PREFIX/bin/broot" 11 | } 12 | 13 | sdd_fetch_latest_version() { 14 | _tag_name_of_latest_github_release Canop broot 15 | } 16 | -------------------------------------------------------------------------------- /lib/sdd/apps/user/circleci: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | sdd_install() { 4 | local version=$1 5 | local package=circleci-cli_${version:1}_linux_amd64 6 | local archive="$package".tar.gz 7 | wget -P /tmp https://github.com/CircleCI-Public/circleci-cli/releases/download/"$version"/"$archive" 8 | 9 | cd /tmp || return 1 10 | tar xfv "$archive" 11 | mv "$package"/circleci "$SDD_INSTALL_PREFIX"/bin/circleci 12 | "$SDD_INSTALL_PREFIX"/bin/circleci completion bash > "$SDD_BASH_COMPLETION_DIR"/circleci 13 | "$SDD_INSTALL_PREFIX"/bin/circleci completion zsh > "$SDD_ZSH_COMPLETION_DIR"/_circleci 14 | rm -rfv "$archive" "$package" 15 | } 16 | 17 | sdd_uninstall() { 18 | rm -fv "$SDD_INSTALL_PREFIX"/bin/circleci 19 | rm -fv "$SDD_BASH_COMPLETION_DIR"/circleci 20 | rm -fv "$SDD_ZSH_COMPLETION_DIR"/_circleci 21 | } 22 | 23 | sdd_fetch_latest_version() { 24 | _tag_name_of_latest_github_release CircleCI-Public circleci-cli 25 | } 26 | -------------------------------------------------------------------------------- /lib/sdd/apps/user/dasel: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | sdd_install() { 4 | local version=$1 5 | wget -O "$SDD_INSTALL_PREFIX/bin/dasel" https://github.com/TomWright/dasel/releases/download/$version/dasel_linux_amd64 6 | chmod u+x "$SDD_INSTALL_PREFIX/bin/dasel" 7 | } 8 | 9 | sdd_uninstall() { 10 | rm -f "$SDD_INSTALL_PREFIX/bin/dasel" 11 | } 12 | 13 | sdd_fetch_latest_version() { 14 | _tag_name_of_latest_github_release TomWright dasel 15 | } 16 | -------------------------------------------------------------------------------- /lib/sdd/apps/user/delta: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | sdd_install() { 4 | local version=$1 5 | local package=delta-$version-x86_64-unknown-linux-musl 6 | 7 | wget -P /tmp https://github.com/dandavison/delta/releases/download/$version/$package.tar.gz 8 | cd /tmp 9 | tar xf $package.tar.gz 10 | 11 | mv $package/delta "$SDD_INSTALL_PREFIX"/bin 12 | 13 | wget -O "$SDD_BASH_COMPLETION_DIR"/delta \ 14 | https://raw.githubusercontent.com/dandavison/delta/$version/completion/bash/completion.sh 15 | 16 | rm -rf $package.tar.gz $package 17 | } 18 | 19 | sdd_uninstall() { 20 | rm -fv "$SDD_INSTALL_PREFIX"/bin/delta 21 | rm -fv "$SDD_BASH_COMPLETION_DIR"/delta 22 | } 23 | 24 | sdd_fetch_latest_version() { 25 | _tag_name_of_latest_github_release dandavison delta 26 | } 27 | -------------------------------------------------------------------------------- /lib/sdd/apps/user/diff-so-fancy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | sdd_install() { 4 | local version=$1 5 | wget -O "$SDD_INSTALL_PREFIX/bin/diff-so-fancy" https://raw.githubusercontent.com/so-fancy/diff-so-fancy/$version/third_party/build_fatpack/diff-so-fancy 6 | chmod u+x "$SDD_INSTALL_PREFIX/bin/diff-so-fancy" 7 | # git config --global core.pager "diff-so-fancy | less --tabs=4 -RFX" 8 | } 9 | 10 | sdd_uninstall() { 11 | rm -f "$SDD_INSTALL_PREFIX/bin/diff-so-fancy" 12 | } 13 | 14 | sdd_fetch_latest_version() { 15 | _tag_name_of_latest_github_release so-fancy diff-so-fancy 16 | } 17 | -------------------------------------------------------------------------------- /lib/sdd/apps/user/direnv: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | sdd_install() { 4 | local version=$1 5 | wget -O "$SDD_INSTALL_PREFIX/bin/direnv" https://github.com/direnv/direnv/releases/download/$version/direnv.linux-amd64 6 | chmod u+x "$SDD_INSTALL_PREFIX/bin/direnv" 7 | 8 | # Install man page 9 | mkdir -p "$SDD_INSTALL_PREFIX"/share/man/man1 10 | wget -O "$SDD_INSTALL_PREFIX"/share/man/man1/direnv.1 https://raw.githubusercontent.com/direnv/direnv/$version/man/direnv.1 11 | } 12 | 13 | sdd_uninstall() { 14 | rm -f "$SDD_INSTALL_PREFIX/bin/direnv" 15 | rm -f "$SDD_INSTALL_PREFIX"/share/man/man1/direnv.1 16 | } 17 | 18 | sdd_fetch_latest_version() { 19 | _tag_name_of_latest_github_release direnv direnv 20 | } 21 | -------------------------------------------------------------------------------- /lib/sdd/apps/user/docker-compose: -------------------------------------------------------------------------------- 1 | sdd_install() { 2 | local version=$1 3 | local executable 4 | executable=docker-compose-linux-x86_64 5 | 6 | wget -P /tmp https://github.com/docker/compose/releases/download/"$version"/$executable 7 | chmod u+x /tmp/$executable 8 | # Docker plugin should go to ~/.docker/cli-plugins 9 | mv -v /tmp/$executable "$SDD_INSTALL_PREFIX"/bin/docker-compose 10 | } 11 | 12 | sdd_uninstall() { 13 | rm -rfv "$SDD_INSTALL_PREFIX"/bin/docker-compose 14 | } 15 | 16 | sdd_fetch_latest_version() { 17 | _tag_name_of_latest_github_release docker compose 18 | } 19 | -------------------------------------------------------------------------------- /lib/sdd/apps/user/dust: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | sdd_install() { 4 | local version=$1 5 | local package=dust-$version-x86_64-unknown-linux-musl 6 | 7 | wget -P /tmp https://github.com/bootandy/dust/releases/download/$version/$package.tar.gz 8 | cd /tmp 9 | tar xf $package.tar.gz 10 | 11 | mv $package/dust "$SDD_INSTALL_PREFIX"/bin 12 | 13 | rm -rf $package.tar.gz $package 14 | } 15 | 16 | sdd_uninstall() { 17 | rm -f "$SDD_INSTALL_PREFIX"/bin/dust 18 | } 19 | 20 | sdd_fetch_latest_version() { 21 | _tag_name_of_latest_github_release bootandy dust 22 | } 23 | -------------------------------------------------------------------------------- /lib/sdd/apps/user/fd: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | sdd_install() { 4 | local version=$1 5 | local package=fd-$version-x86_64-unknown-linux-musl 6 | 7 | wget -P /tmp https://github.com/sharkdp/fd/releases/download/$version/$package.tar.gz 8 | cd /tmp 9 | tar xf $package.tar.gz 10 | 11 | mv $package/fd "$SDD_INSTALL_PREFIX"/bin 12 | 13 | # Install man page 14 | mkdir -p "$SDD_INSTALL_PREFIX"/share/man/man1 15 | mv $package/fd.1 "$SDD_INSTALL_PREFIX"/share/man/man1 16 | 17 | cp "$package"/autocomplete/fd.bash-completion "$SDD_BASH_COMPLETION_DIR"/fd 18 | cp "$package"/autocomplete/fd.bash "$SDD_BASH_COMPLETION_DIR"/fd 19 | cp "$package"/autocomplete/_fd "$SDD_ZSH_COMPLETION_DIR"/_fd 20 | 21 | rm -rf $package.tar.gz $package 22 | } 23 | 24 | sdd_uninstall() { 25 | rm -fv "$SDD_INSTALL_PREFIX"/bin/fd 26 | rm -fv "$SDD_INSTALL_PREFIX"/share/man/man1/fd.1 27 | rm -fv "$SDD_BASH_COMPLETION_DIR"/fd 28 | rm -fv "$SDD_ZSH_COMPLETION_DIR"/_fd 29 | } 30 | 31 | sdd_fetch_latest_version() { 32 | _tag_name_of_latest_github_release sharkdp fd 33 | } 34 | -------------------------------------------------------------------------------- /lib/sdd/apps/user/ffsend: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | sdd_install() { 4 | local version=$1 5 | wget -O "$SDD_INSTALL_PREFIX"/bin/ffsend https://github.com/timvisee/ffsend/releases/download/"$version"/ffsend-"$version"-linux-x64-static 6 | chmod u+x "$SDD_INSTALL_PREFIX"/bin/ffsend 7 | } 8 | 9 | sdd_uninstall() { 10 | rm -vf "$SDD_INSTALL_PREFIX"/bin/ffsend 11 | } 12 | 13 | sdd_fetch_latest_version() { 14 | _tag_name_of_latest_github_release timvisee ffsend 15 | } 16 | -------------------------------------------------------------------------------- /lib/sdd/apps/user/gh: -------------------------------------------------------------------------------- 1 | sdd_install() { 2 | local version=$1 3 | local arch=amd64 4 | if [[ $(arch) = armv7l ]]; then 5 | arch=arm64 6 | fi 7 | 8 | local package_dir archive 9 | # Strip prefixed v from version number for package name 10 | package_dir=gh_$(echo "$version" | tr -d v)_linux_$arch 11 | archive="$package_dir".tar.gz 12 | 13 | wget -P /tmp https://github.com/cli/cli/releases/download/"$version"/"$archive" 14 | cd /tmp 15 | tar xfv "$archive" 16 | 17 | mv -v "$package_dir"/bin/gh "$SDD_INSTALL_PREFIX"/bin 18 | 19 | "$SDD_INSTALL_PREFIX"/bin/gh completion --shell bash > "$SDD_BASH_COMPLETION_DIR"/gh 20 | "$SDD_INSTALL_PREFIX"/bin/gh completion --shell zsh > "$SDD_ZSH_COMPLETION_DIR"/_gh 21 | 22 | mkdir -p "$SDD_INSTALL_PREFIX"/share/man/man1 23 | mv "$package_dir"/share/man/man1/* "$SDD_INSTALL_PREFIX"/share/man/man1 24 | 25 | rm -rfv "$package_dir" "$archive" 26 | } 27 | 28 | sdd_uninstall() { 29 | rm -rfv "$SDD_INSTALL_PREFIX"/bin/gh 30 | rm -rfv "$SDD_BASH_COMPLETION_DIR"/gh 31 | rm -rfv "$SDD_ZSH_COMPLETION_DIR"/_gh 32 | rm -rfv "$SDD_INSTALL_PREFIX"/share/man/man1/gh*.1 33 | } 34 | 35 | sdd_fetch_latest_version() { 36 | _tag_name_of_latest_github_release cli cli 37 | } 38 | -------------------------------------------------------------------------------- /lib/sdd/apps/user/git-trim: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | sdd_install() { 4 | local version=$1 5 | local package=git-trim 6 | local archive=$package-linux-"$version".tgz 7 | 8 | wget -P /tmp https://github.com/foriequal0/git-trim/releases/download/"$version"/"$archive" 9 | cd /tmp || return 1 10 | tar vxf "$archive" 11 | 12 | mv $package/git-trim "$SDD_INSTALL_PREFIX"/bin 13 | 14 | rm -rfv "$archive" $package 15 | } 16 | 17 | sdd_uninstall() { 18 | rm -fv "$SDD_INSTALL_PREFIX"/bin/git-trim 19 | } 20 | 21 | sdd_fetch_latest_version() { 22 | _tag_name_of_latest_github_release foriequal0 git-trim 23 | } 24 | -------------------------------------------------------------------------------- /lib/sdd/apps/user/gitui: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | sdd_install() { 4 | local version=$1 5 | local archive=gitui-linux-musl.tar.gz 6 | 7 | wget -P /tmp https://github.com/extrawurst/gitui/releases/download/"$version"/"$archive" 8 | cd /tmp 9 | tar vxf "$archive" 10 | 11 | mv gitui "$SDD_INSTALL_PREFIX"/bin 12 | 13 | rm "$archive" 14 | } 15 | 16 | sdd_uninstall() { 17 | rm -f "$SDD_INSTALL_PREFIX"/bin/gitui 18 | } 19 | 20 | sdd_fetch_latest_version() { 21 | _tag_name_of_latest_github_release extrawurst gitui 22 | } 23 | -------------------------------------------------------------------------------- /lib/sdd/apps/user/go: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | sdd_install() { 4 | local version=$1 5 | local archive=go$version.linux-amd64.tar.gz 6 | echo $version 7 | echo $archive 8 | 9 | wget -P /tmp https://dl.google.com/go/"$archive" 10 | cd /tmp || return 1 11 | tar xfv "$archive" 12 | mv go "$SDD_INSTALL_PREFIX" 13 | 14 | rm -rfv "$archive" 15 | } 16 | 17 | sdd_uninstall() { 18 | rm -rvf "$SDD_INSTALL_PREFIX"/go 19 | } 20 | 21 | sdd_fetch_latest_version() { 22 | # hard-coding since I can't find an API to query the latest version 23 | echo 1.15.5 24 | } 25 | -------------------------------------------------------------------------------- /lib/sdd/apps/user/hub: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sdd_install() { 4 | local version=$1 5 | local arch=amd64 6 | if [[ $(arch) = armv7l ]]; then 7 | arch=arm 8 | fi 9 | 10 | # Strip prefixed v from version number for package name 11 | local package=hub-linux-$arch-$(echo $version | tr -d v) 12 | 13 | wget -P /tmp https://github.com/github/hub/releases/download/$version/$package.tgz 14 | cd /tmp 15 | tar xf $package.tgz 16 | 17 | PREFIX="$SDD_INSTALL_PREFIX" $package/install 18 | 19 | cp $package/etc/hub.zsh_completion "$SDD_ZSH_COMPLETION_DIR"/_hub 20 | 21 | rm -rf $package $package.tgz 22 | } 23 | 24 | sdd_uninstall() { 25 | rm -rf "$SDD_INSTALL_PREFIX"/bin/hub 26 | rm -rf "$SDD_INSTALL_PREFIX"/share/vim/**/pullrequest.vim 27 | rm -rf "$SDD_INSTALL_PREFIX"/share/man/man1/hub* 28 | rm -rf "$SDD_ZSH_COMPLETION_DIR"/_hub 29 | } 30 | 31 | sdd_fetch_latest_version() { 32 | _tag_name_of_latest_github_release github hub 33 | } 34 | -------------------------------------------------------------------------------- /lib/sdd/apps/user/jira: -------------------------------------------------------------------------------- 1 | sdd_install() { 2 | local version=$1 3 | wget -O "$SDD_INSTALL_PREFIX"/bin/jira https://github.com/go-jira/jira/releases/download/"$version"/jira-linux-amd64 4 | chmod u+x "$SDD_INSTALL_PREFIX"/bin/jira 5 | 6 | # Work around non-zero exit code (https://github.com/go-jira/jira/issues/326) 7 | "$SDD_INSTALL_PREFIX"/bin/jira --completion-script-bash > "$SDD_BASH_COMPLETION_DIR"/jira || true 8 | "$SDD_INSTALL_PREFIX"/bin/jira --completion-script-zsh > "$SDD_ZSH_COMPLETION_DIR"/_jira || true 9 | } 10 | 11 | sdd_uninstall() { 12 | rm -fv "$SDD_INSTALL_PREFIX"/bin/jira 13 | rm -fv "$SDD_BASH_COMPLETION_DIR"/jira 14 | rm -fv "$SDD_ZSH_COMPLETION_DIR"/_jira 15 | } 16 | 17 | sdd_fetch_latest_version() { 18 | _tag_name_of_latest_github_release go-jira jira 19 | } 20 | -------------------------------------------------------------------------------- /lib/sdd/apps/user/jq: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | sdd_install() { 4 | local version=$1 5 | wget -O "$SDD_INSTALL_PREFIX/bin/jq" https://github.com/jqlang/jq/releases/download/"$version"/jq-linux64 6 | chmod u+x "$SDD_INSTALL_PREFIX/bin/jq" 7 | 8 | mkdir -p "$SDD_INSTALL_PREFIX"/share/man/man1 9 | wget -O "$SDD_INSTALL_PREFIX"/share/man/man1/jq.1 https://raw.githubusercontent.com/jqlang/jq/refs/tags/"$version"/jq.1.prebuilt 10 | } 11 | 12 | sdd_uninstall() { 13 | rm -rfv "$SDD_INSTALL_PREFIX/bin/jq" 14 | rm -rfv "$SDD_INSTALL_PREFIX/share/man/man1/jq.1" 15 | } 16 | 17 | sdd_fetch_latest_version() { 18 | _tag_name_of_latest_github_release jqlang jq 19 | } 20 | -------------------------------------------------------------------------------- /lib/sdd/apps/user/ncdu: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | sdd_install() { 4 | local version=$1 5 | local archive 6 | if [[ $version = 1.* ]]; then 7 | archive=ncdu-linux-x86_64-"$version".tar.gz 8 | else 9 | archive=ncdu-"$version"-linux-x86_64.tar.gz 10 | fi 11 | wget -P /tmp https://dev.yorhel.nl/download/"$archive" 12 | 13 | cd /tmp || return 1 14 | tar xfv "$archive" 15 | mv ncdu "$SDD_INSTALL_PREFIX"/bin 16 | rm "$archive" 17 | } 18 | 19 | sdd_uninstall() { 20 | rm -f "$SDD_INSTALL_PREFIX"/bin/ncdu 21 | } 22 | 23 | sdd_fetch_latest_version() { 24 | echo 2.0 25 | } 26 | -------------------------------------------------------------------------------- /lib/sdd/apps/user/oh-my-zsh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | sdd_install() { 4 | local version=${1:-master} 5 | wget -P /tmp https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/"$version"/tools/install.sh 6 | local install_path=/tmp/install.sh 7 | 8 | # Don't execute zsh when installation is done 9 | sed -i '/env zsh/ d' $install_path 10 | 11 | chmod +x $install_path 12 | # Trick the oh-my-zsh logic to run chsh 13 | SHELL=zsh $install_path 14 | 15 | rm $install_path 16 | } 17 | 18 | sdd_upgrade() { 19 | local version=${1:-master} 20 | 21 | cd "${ZSH:-$HOME/.oh-my-zsh}" || return 1 22 | git checkout master 23 | git pull 24 | git checkout "$version" 25 | } 26 | 27 | sdd_uninstall() { 28 | rm -rf "${ZSH:-$HOME/.oh-my-zsh}" 29 | } 30 | 31 | sdd_fetch_latest_version() { 32 | _sha_of_github_master robbyrussell oh-my-zsh 33 | } 34 | -------------------------------------------------------------------------------- /lib/sdd/apps/user/pandoc: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | sdd_install() { 4 | local version=$1 5 | local package=pandoc-"$version" 6 | local archive="$package"-linux-amd64.tar.gz 7 | 8 | wget -P /tmp https://github.com/jgm/pandoc/releases/download/"$version"/"$archive" 9 | cd /tmp 10 | tar xf "$archive" 11 | 12 | mv "$package"/bin/pandoc "$SDD_INSTALL_PREFIX"/bin 13 | mv "$package"/bin/pandoc-citeproc "$SDD_INSTALL_PREFIX"/bin 14 | 15 | # Install man page 16 | mkdir -p "$SDD_INSTALL_PREFIX"/share/man/man1 17 | mv "$package"/share/man/man1/* "$SDD_INSTALL_PREFIX"/share/man/man1 18 | 19 | "$SDD_INSTALL_PREFIX"/bin/pandoc --bash-completion > "$SDD_BASH_COMPLETION_DIR"/pandoc 20 | 21 | rm -rf "$archive" "$package" 22 | } 23 | 24 | sdd_uninstall() { 25 | rm -fv "$SDD_INSTALL_PREFIX"/bin/pandoc 26 | rm -fv "$SDD_INSTALL_PREFIX"/share/man/man1/pandoc.1.gz 27 | rm -fv "$SDD_INSTALL_PREFIX"/share/man/man1/pandoc-citeproc.1.gz 28 | rm -fv "$SDD_BASH_COMPLETION_DIR"/pandoc 29 | } 30 | 31 | sdd_fetch_latest_version() { 32 | _tag_name_of_latest_github_release jgm pandoc 33 | } 34 | -------------------------------------------------------------------------------- /lib/sdd/apps/user/pip: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | sdd_install() { 4 | command -v python3 >/dev/null 2>&1 || return 1 5 | 6 | if [[ $(python3 --version) = "Python 3.5."* ]]; then 7 | wget -P /tmp https://bootstrap.pypa.io/pip/3.5/get-pip.py 8 | else 9 | wget -P /tmp https://bootstrap.pypa.io/get-pip.py 10 | fi 11 | python3 /tmp/get-pip.py --user 12 | sdd_upgrade "$1" 13 | 14 | ~/.local/bin/pip completion --bash > "$SDD_BASH_COMPLETION_DIR"/pip 15 | ~/.local/bin/pip completion --zsh > "$SDD_ZSH_COMPLETION_DIR"/_pip 16 | 17 | rm /tmp/get-pip.py 18 | } 19 | 20 | sdd_upgrade() { 21 | ~/.local/bin/pip install --user --upgrade pip=="$1" 22 | } 23 | 24 | sdd_uninstall() { 25 | ~/.local/bin/pip uninstall --yes pip 26 | rm -rvf "$SDD_BASH_COMPLETION_DIR"/pip "$SDD_ZSH_COMPLETION_DIR"/_pip 27 | } 28 | 29 | sdd_fetch_latest_version() { 30 | _name_of_latest_github_tag pypa pip 31 | } 32 | -------------------------------------------------------------------------------- /lib/sdd/apps/user/python: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | sdd_install() { 4 | local date=20231002 5 | local version=3.12.0+$date 6 | local package=cpython-$version-x86_64-unknown-linux-gnu-install_only 7 | local archive=$package.tar.gz 8 | 9 | wget -P /tmp https://github.com/indygreg/python-build-standalone/releases/download/$date/$archive 10 | cd /tmp || return 1 11 | tar vxf $archive 12 | 13 | # Install man page 14 | mkdir -p "$SDD_INSTALL_PREFIX"/share/man/man1 15 | mv python/share/man/man1/python3* "$SDD_INSTALL_PREFIX"/share/man/man1 16 | 17 | # Install rest of software 18 | mkdir -p "$SDD_INSTALL_PREFIX"/python 19 | mv python "$SDD_INSTALL_PREFIX"/python/$version 20 | 21 | rm -rf $archive 22 | } 23 | 24 | sdd_uninstall() { 25 | echo "Uninstalling disabled." >&2 26 | } 27 | 28 | sdd_fetch_latest_version() { 29 | _tag_name_of_latest_github_release indygreg python-build-standalone 30 | } 31 | -------------------------------------------------------------------------------- /lib/sdd/apps/user/qmlfmt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | QMLFMT_TARFILE=qmlfmt.tar.gz 4 | 5 | sdd_install() { 6 | local version=$1 7 | wget -P /tmp https://github.com/jesperhh/qmlfmt/releases/download/$version/$QMLFMT_TARFILE 8 | tar xf /tmp/$QMLFMT_TARFILE 9 | 10 | mv /tmp/qmlfmt $SDD_INSTALL_PREFIX/bin 11 | 12 | rm /tmp/$QMLFMT_TARFILE 13 | } 14 | 15 | sdd_fetch_latest_version() { 16 | wget -qO- https://api.github.com/repos/jesperhh/qmlfmt/releases/latest | grep tag_name | awk '{ print $2; }' | sed 's/[",]//g' 17 | } 18 | -------------------------------------------------------------------------------- /lib/sdd/apps/user/qrcp: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | sdd_install() { 4 | local version=$1 5 | local package=qrcp_"$version"_linux_x86_64 6 | 7 | wget -P /tmp https://github.com/claudiodangelis/qrcp/releases/download/$version/$package.tar.gz 8 | cd /tmp 9 | tar vxf $package.tar.gz 10 | 11 | mv qrcp "$SDD_INSTALL_PREFIX"/bin 12 | 13 | rm -rf $package.tar.gz 14 | } 15 | 16 | sdd_uninstall() { 17 | rm -fv "$SDD_INSTALL_PREFIX"/bin/qrcp 18 | } 19 | 20 | sdd_fetch_latest_version() { 21 | _tag_name_of_latest_github_release claudiodangelis qrcp 22 | } 23 | -------------------------------------------------------------------------------- /lib/sdd/apps/user/ripgrep: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | sdd_install() { 4 | local version=$1 5 | local arch=x86_64 6 | local suffix=musl 7 | 8 | if [[ $(arch) = arm* ]]; then 9 | arch=arm 10 | suffix=gnueabihf 11 | fi 12 | 13 | local package=ripgrep-$version-$arch-unknown-linux-$suffix 14 | 15 | wget -P /tmp https://github.com/BurntSushi/ripgrep/releases/download/$version/$package.tar.gz 16 | cd /tmp 17 | tar xf $package.tar.gz 18 | 19 | # Install binary 20 | mv $package/rg "$SDD_INSTALL_PREFIX"/bin 21 | 22 | mv $package/complete/rg.bash "$SDD_BASH_COMPLETION_DIR"/rg 23 | mv $package/complete/_rg "$SDD_ZSH_COMPLETION_DIR" 24 | 25 | # Install man page 26 | mkdir -p "$SDD_INSTALL_PREFIX"/share/man/man1 27 | mv $package/doc/rg.1 "$SDD_INSTALL_PREFIX"/share/man/man1 28 | 29 | rm -rf $package.tar.gz $package 30 | } 31 | 32 | sdd_uninstall() { 33 | rm -f "$SDD_INSTALL_PREFIX"/bin/rg 34 | rm -f "$SDD_BASH_COMPLETION_DIR"/rg 35 | rm -f "$SDD_ZSH_COMPLETION_DIR"/_rg 36 | rm -f "$SDD_INSTALL_PREFIX"/share/man/man1/rg.1 37 | } 38 | 39 | sdd_fetch_latest_version() { 40 | _tag_name_of_latest_github_release BurntSushi ripgrep 41 | } 42 | -------------------------------------------------------------------------------- /lib/sdd/apps/user/sdd: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | sdd_install() { 4 | local version=$1 5 | git clone https://github.com/pylipp/sdd /tmp/sdd 6 | cd /tmp/sdd 7 | git checkout $version >/dev/null 8 | ./bootstrap.sh 9 | cd .. 10 | rm -rf sdd 11 | } 12 | 13 | sdd_upgrade() { 14 | sdd_install "$1" 15 | } 16 | 17 | sdd_uninstall() { 18 | rm -f "$SDD_INSTALL_PREFIX/bin/sdd" 19 | rm -rf "$SDD_INSTALL_PREFIX/lib/sdd" 20 | } 21 | 22 | sdd_fetch_latest_version() { 23 | _tag_name_of_latest_github_release pylipp sdd 24 | } 25 | -------------------------------------------------------------------------------- /lib/sdd/apps/user/shellcheck: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | sdd_install() { 4 | local version=$1 5 | local package=shellcheck-$version 6 | local archive=$package.linux.x86_64.tar.xz 7 | 8 | wget -P /tmp https://github.com/koalaman/shellcheck/releases/download/"$version"/"$archive" 9 | cd /tmp 10 | tar xf $archive 11 | 12 | mv $package/shellcheck "$SDD_INSTALL_PREFIX"/bin 13 | 14 | # Compile manpage if pandoc available 15 | if command -v pandoc >/dev/null 2>&1; then 16 | wget https://raw.githubusercontent.com/koalaman/shellcheck/"$version"/shellcheck.1.md 17 | 18 | local option= 19 | if pandoc --version | grep -E '^pandoc 1\.' >/dev/null 2>&1; then 20 | # pandoc v1.X requires an extra option but in 2.X it's the default for markdown 21 | # https://github.com/jgm/pandoc/issues/3416 22 | option=--smart 23 | fi 24 | pandoc -s $option -f markdown -t man shellcheck.1.md -o shellcheck.1 25 | 26 | mkdir -p "$SDD_INSTALL_PREFIX"/share/man/man1 27 | mv shellcheck.1 "$SDD_INSTALL_PREFIX"/share/man/man1 28 | rm shellcheck.1.md 29 | else 30 | echo "pandoc not available - can't build man page" >&2 31 | fi 32 | 33 | rm -rf $archive $package 34 | } 35 | 36 | sdd_uninstall() { 37 | rm -v "$SDD_INSTALL_PREFIX"/bin/shellcheck 38 | } 39 | 40 | sdd_fetch_latest_version() { 41 | _name_of_latest_github_tag koalaman shellcheck 42 | } 43 | -------------------------------------------------------------------------------- /lib/sdd/apps/user/shfmt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | sdd_install() { 4 | local version=$1 5 | wget -O "$SDD_INSTALL_PREFIX/bin/shfmt" https://github.com/mvdan/sh/releases/download/"$version/shfmt_$version"_linux_amd64 6 | chmod u+x "$SDD_INSTALL_PREFIX/bin/shfmt" 7 | } 8 | 9 | sdd_uninstall() { 10 | rm -f "$SDD_INSTALL_PREFIX/bin/shfmt" 11 | } 12 | 13 | sdd_fetch_latest_version() { 14 | _tag_name_of_latest_github_release mvdan sh 15 | } 16 | -------------------------------------------------------------------------------- /lib/sdd/apps/user/slack-term: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | sdd_install() { 4 | local version=$1 5 | wget -O "$SDD_INSTALL_PREFIX/bin/slack-term" https://github.com/erroneousboat/slack-term/releases/download/$version/slack-term-linux-amd64 6 | chmod u+x "$SDD_INSTALL_PREFIX/bin/slack-term" 7 | } 8 | 9 | sdd_uninstall() { 10 | rm -f "$SDD_INSTALL_PREFIX/bin/slack-term" 11 | } 12 | 13 | sdd_fetch_latest_version() { 14 | _tag_name_of_latest_github_release erroneousboat slack-term 15 | } 16 | -------------------------------------------------------------------------------- /lib/sdd/apps/user/telegram: -------------------------------------------------------------------------------- 1 | sdd_install() { 2 | local version=$1 3 | # Strip 'v' from version tag 4 | local archive=tsetup."${version:1}".tar.xz 5 | 6 | wget -P /tmp https://github.com/telegramdesktop/tdesktop/releases/download/"$version"/"$archive" 7 | tar xvf /tmp/"$archive" -C /tmp 8 | 9 | mv /tmp/Telegram/Telegram "$SDD_INSTALL_PREFIX"/bin/telegram 10 | mv /tmp/Telegram/Updater "$SDD_INSTALL_PREFIX"/bin/update-telegram 11 | 12 | rm -f /tmp/"$archive" 13 | } 14 | 15 | sdd_uninstall() { 16 | rm -vf "$SDD_INSTALL_PREFIX"/bin/telegram 17 | rm -vf "$SDD_INSTALL_PREFIX"/bin/update-telegram 18 | } 19 | 20 | sdd_fetch_latest_version() { 21 | _tag_name_of_latest_github_release telegramdesktop tdesktop 22 | } 23 | -------------------------------------------------------------------------------- /lib/sdd/apps/user/wuzz: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | sdd_install() { 4 | local version="$1" 5 | wget -O "$SDD_INSTALL_PREFIX"/bin/wuzz https://github.com/stedolan/wuzz/releases/download/"$version"/wuzz_linux_amd64 6 | chmod u+x "$SDD_INSTALL_PREFIX"/bin/wuzz 7 | } 8 | 9 | sdd_uninstall() { 10 | rm -f "$SDD_INSTALL_PREFIX"/bin/wuzz 11 | } 12 | 13 | sdd_fetch_latest_version() { 14 | _tag_name_of_latest_github_release asciimoo wuzz 15 | } 16 | -------------------------------------------------------------------------------- /lib/sdd/apps/user/xh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | sdd_install() { 4 | local version=$1 5 | local package=xh-$version-x86_64-unknown-linux-musl 6 | local archive=$package.tar.gz 7 | 8 | wget -P /tmp https://github.com/ducaale/xh/releases/download/"$version"/"$archive" 9 | cd /tmp || return 1 10 | tar xfv "$archive" 11 | 12 | mv -v "$package"/xh "$SDD_INSTALL_PREFIX"/bin 13 | 14 | # Install man page 15 | mkdir -p "$SDD_INSTALL_PREFIX"/share/man/man1 16 | mv -v "$package"/doc/xh.1 "$SDD_INSTALL_PREFIX"/share/man/man1 17 | 18 | cp -v "$package"/completions/xh.bash "$SDD_BASH_COMPLETION_DIR"/xh 19 | cp -v "$package"/completions/_xh "$SDD_ZSH_COMPLETION_DIR"/_xh 20 | 21 | rm -rfv "$archive" "$package" 22 | } 23 | 24 | sdd_uninstall() { 25 | rm -fv "$SDD_INSTALL_PREFIX"/bin/xh 26 | rm -fv "$SDD_INSTALL_PREFIX"/share/man/man1/xh.1 27 | rm -fv "$SDD_BASH_COMPLETION_DIR"/xh 28 | rm -fv "$SDD_ZSH_COMPLETION_DIR"/_xh 29 | } 30 | 31 | sdd_fetch_latest_version() { 32 | _tag_name_of_latest_github_release ducaale xh 33 | } 34 | -------------------------------------------------------------------------------- /lib/sdd/apps/user/xsv: -------------------------------------------------------------------------------- 1 | sdd_install() { 2 | local version=$1 3 | local package=xsv-$version-x86_64-unknown-linux-musl 4 | 5 | wget -P /tmp https://github.com/BurntSushi/xsv/releases/download/"$version"/"$package".tar.gz 6 | cd /tmp || return 1 7 | tar xfv "$package".tar.gz 8 | 9 | # Install binary 10 | mv xsv "$SDD_INSTALL_PREFIX"/bin 11 | 12 | rm -rfv "$package".tar.gz 13 | } 14 | 15 | sdd_uninstall() { 16 | rm -fv "$SDD_INSTALL_PREFIX"/bin/xsv 17 | } 18 | 19 | sdd_fetch_latest_version() { 20 | _tag_name_of_latest_github_release BurntSushi xsv 21 | } 22 | -------------------------------------------------------------------------------- /lib/sdd/framework/utils.bash: -------------------------------------------------------------------------------- 1 | # Utility functions for sdd framework 2 | 3 | # Disable "Can't follow non-constant source" warning for this file 4 | # shellcheck disable=SC1090 5 | true 6 | 7 | FRAMEWORKDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 8 | 9 | utils_usage() { 10 | cat <&2 45 | return 1 46 | fi 47 | 48 | cat "$version_filepath" 49 | } 50 | 51 | _validate_apps() { 52 | # Check whether management files for specified apps are available 53 | # Publish valid apps/appvers to stdout 54 | # Args: APP[=VERSION] [APP[=VERSION]] ... 55 | 56 | local return_code=0 57 | local appfilepath app nr_misses 58 | local valid_appvers=() 59 | 60 | for appver in "$@"; do 61 | app=$(_get_app_name "$appver") 62 | 63 | nr_misses=0 64 | 65 | for dir in "$FRAMEWORKDIR/../apps/user" "$HOME/.config/sdd/apps"; do 66 | appfilepath="$dir/$app" 67 | 68 | if [ ! -f "$appfilepath" ]; then 69 | ((nr_misses++)) 70 | fi 71 | done 72 | 73 | if [ "$nr_misses" -eq 2 ]; then 74 | printf 'App "%s" could not be found.\n' "$app" >&2 75 | return_code=2 76 | else 77 | valid_appvers+=("$appver") 78 | fi 79 | done 80 | 81 | echo "${valid_appvers[@]}" 82 | return $return_code 83 | } 84 | 85 | utils_install() { 86 | # Install one or more apps 87 | # Args: APP[=VERSION] [APP[=VERSION]] ... 88 | _manage_apps install "$@" 89 | } 90 | 91 | utils_uninstall() { 92 | # Uninstall one or more apps 93 | # Args: APP[=VERSION] [APP[=VERSION]] ... 94 | _manage_apps uninstall "$@" 95 | } 96 | 97 | utils_upgrade() { 98 | # Upgrade one or more apps 99 | # Args: APP[=VERSION] [APP[=VERSION]] ... 100 | _manage_apps upgrade "$@" 101 | } 102 | 103 | _manage_apps() { 104 | # Manage one or more apps 105 | # Args: METHOD APP[=VERSION] [APP[=VERSION]] ... 106 | local return_code=0 107 | 108 | local manage 109 | manage="$1" 110 | shift 111 | 112 | if [ $# -eq 0 ]; then 113 | printf 'Specify at least one app to %s.\n' "$manage" >&2 114 | return 1 115 | fi 116 | 117 | local appvers=() 118 | # shellcheck disable=SC2207 119 | appvers=($(_validate_apps "$@")) 120 | return_code=$? 121 | 122 | local app stderrlog rclog tmplog 123 | rclog=/tmp/sdd-manage.rc 124 | tmplog=/tmp/sdd-manage.tmp 125 | 126 | for appver in "${appvers[@]}"; do 127 | app=$(_get_app_name "$appver") 128 | 129 | if [ -f "$HOME/.config/sdd/apps/$app" ]; then 130 | printf 'Custom %s function for "%s" found.\n' "$manage" "$app" >&2 131 | fi 132 | 133 | stderrlog=/tmp/sdd-$manage-$app.stderr 134 | 135 | # Invoke management function. If failed, track return code in file. 136 | # Redirect stdout (=combined stdout and stderr from sdd_* functions) to log file 137 | rm -f $rclog 138 | if [ -t 1 ]; then 139 | # stdout attached to terminal 140 | 141 | if [ -n "$SDD_VERBOSE" ]; then 142 | # Run manage and forward any output additionally to terminal 143 | { _"$manage"_single_app "$appver" || echo $? > $rclog; } | tee -a "$stderrlog" 144 | 145 | else 146 | # Run manage in background and display spinner 147 | { _"$manage"_single_app "$appver" || echo $? > $rclog; } > "$stderrlog" 2> "$tmplog" & 148 | _spin $! 149 | # Show additional output from e.g. _get_app_version now to not collide with spinner 150 | cat "$tmplog" 151 | fi 152 | else 153 | { _"$manage"_single_app "$appver" || echo $? > $rclog; } > "$stderrlog" 154 | fi 155 | 156 | if [ -e $rclog ]; then 157 | printf 'Failed to %s "%s". See %s.\n' "$manage" "$app" "$stderrlog" > >(tee -a "$stderrlog" >&2 ) 158 | 159 | ((return_code+=$(cat $rclog))) 160 | else 161 | printf 'Succeeded to %s "%s".\n' "$manage" "$app" > >(tee -a "$stderrlog" >&2 ) 162 | fi 163 | done 164 | 165 | return "$return_code" 166 | } 167 | 168 | _spin() { 169 | # Display spinner until process corresponding to given PID has finished 170 | # Args: PID 171 | # https://unix.stackexchange.com/a/225183/192726 172 | local pid i spinchars 173 | pid=$1 174 | i=1 175 | spinchars='/-\|' 176 | 177 | while [ -d /proc/"$pid" ]; do 178 | printf "\b%s" "${spinchars:i++%${#spinchars}:1}" 179 | sleep 0.2 180 | done 181 | printf "\b" 182 | } 183 | 184 | _install_single_app() { 185 | # Install single, valid app 186 | # Args: APP[=VERSION] 187 | local return_code=0 188 | 189 | local appver="$1" 190 | local app version 191 | app=$(_get_app_name "$appver") 192 | version=$(_get_app_version "$appver") 193 | 194 | local success=False 195 | local appfilepath 196 | for dir in "$FRAMEWORKDIR/../apps/user" "$HOME/.config/sdd/apps"; do 197 | appfilepath="$dir/$app" 198 | 199 | if [ ! -f "$appfilepath" ]; then continue; fi 200 | 201 | unset -f sdd_install 2> /dev/null || true 202 | source "$appfilepath" 203 | 204 | if sdd_install "$version" 2>&1 && [ $success = False ]; then 205 | success=True 206 | 207 | # Record installed app and version (can be empty) 208 | echo "$app=$version" >> "$SDD_DATA_DIR"/apps/installed 209 | fi 210 | done 211 | 212 | if [ $success = False ]; then 213 | return_code=4 214 | fi 215 | 216 | return $return_code 217 | } 218 | 219 | _uninstall_single_app() { 220 | # Uninstall single, valid app 221 | # Args: APP[=VERSION] 222 | local return_code=0 223 | 224 | local appver="$1" 225 | local app 226 | app=$(_get_app_name "$appver") 227 | 228 | local success=False 229 | local appfilepath 230 | for dir in "$FRAMEWORKDIR/../apps/user" "$HOME/.config/sdd/apps"; do 231 | appfilepath="$dir/$app" 232 | 233 | if [ ! -f "$appfilepath" ]; then continue; fi 234 | 235 | unset -f sdd_uninstall 2> /dev/null || true 236 | source "$appfilepath" 237 | 238 | if sdd_uninstall 2>&1 && [ $success = False ]; then 239 | success=True 240 | 241 | if [ -f "$SDD_DATA_DIR"/apps/installed ]; then 242 | # Remove app install records 243 | sed -i "/^$app=/d" "$SDD_DATA_DIR"/apps/installed 244 | fi 245 | fi 246 | done 247 | 248 | if [ $success = False ]; then 249 | return_code=8 250 | fi 251 | 252 | return $return_code 253 | } 254 | 255 | _upgrade_single_app() { 256 | # Upgrade single, valid app 257 | # Args: APP[=VERSION] 258 | local return_code=0 259 | 260 | local appver="$1" 261 | local app sdd_upgrade_defined 262 | app=$(_get_app_name "$appver") 263 | sdd_upgrade_defined=False 264 | 265 | # Evaluate whether any app management file defines sdd_upgrade 266 | local appfilepath 267 | for dir in "$FRAMEWORKDIR/../apps/user" "$HOME/.config/sdd/apps"; do 268 | appfilepath="$dir/$app" 269 | 270 | if [ ! -f "$appfilepath" ]; then continue; fi 271 | 272 | unset -f sdd_upgrade 2> /dev/null || true 273 | source "$appfilepath" 274 | 275 | if type -t sdd_upgrade >/dev/null; then 276 | sdd_upgrade_defined=True 277 | break 278 | fi 279 | done 280 | 281 | if [ $sdd_upgrade_defined = True ]; then 282 | local version success 283 | version=$(_get_app_version "$appver") 284 | success=False 285 | 286 | for dir in "$FRAMEWORKDIR/../apps/user" "$HOME/.config/sdd/apps"; do 287 | appfilepath="$dir/$app" 288 | 289 | if [ ! -f "$appfilepath" ]; then continue; fi 290 | 291 | unset -f sdd_upgrade 2> /dev/null || true 292 | source "$appfilepath" 293 | 294 | if sdd_upgrade "$version" 2>&1 && [ $success = False ]; then 295 | success=True 296 | 297 | # Record upgraded app and version (can be empty) 298 | echo "$app=$version" >> "$SDD_DATA_DIR"/apps/installed 299 | fi 300 | done 301 | 302 | if [ $success = False ]; then 303 | return_code=16 304 | fi 305 | 306 | else 307 | # Fall back to uninstalling, and re-installing app 308 | _uninstall_single_app "$app" 309 | return_code=$? 310 | 311 | if [ $return_code -eq 0 ]; then 312 | _install_single_app "$appver" 313 | return_code=$? 314 | fi 315 | fi 316 | 317 | return $return_code 318 | } 319 | 320 | _get_app_version() { 321 | # Obtain app version. Try to parse app=ver argument, and if no version 322 | # specified there, try to read it from app management files 323 | # Args: APP[=VERSION] 324 | local version appver app 325 | appver="$1" 326 | app=$(_get_app_name "$appver") 327 | 328 | if [[ $appver = $app=* ]]; then 329 | version=$(_get_app_version_from_appver "$appver") 330 | printf 'Specified version: %s\n' "$version" >&2 331 | fi 332 | 333 | if [ -z "$version" ]; then 334 | version=$(_get_app_version_from_files "$app") 335 | 336 | if [[ -n "$version" ]]; then 337 | printf 'Latest version available: %s\n' "$version" >&2 338 | fi 339 | fi 340 | 341 | echo "$version" 342 | } 343 | 344 | _get_app_version_from_files() { 345 | # Determine relevant version of app from app management files by executing 346 | # the sdd_fetch_latest_version() functions. 347 | # The custom definition takes precedence over the built-in one. 348 | local app="$1" 349 | local appfilepath version version_from_file 350 | 351 | for dir in "$FRAMEWORKDIR/../apps/user" "$HOME/.config/sdd/apps"; do 352 | appfilepath="$dir/$app" 353 | 354 | if [ ! -f "$appfilepath" ]; then continue; fi 355 | 356 | unset -f sdd_fetch_latest_version 2> /dev/null || true 357 | source "$appfilepath" 358 | 359 | if version_from_file=$(sdd_fetch_latest_version 2>/dev/null); then 360 | version=$version_from_file 361 | fi 362 | done 363 | 364 | echo "$version" 365 | } 366 | 367 | utils_list() { 368 | # List apps of various kinds 369 | # ARGS: OPTION 370 | local option=$1 371 | 372 | case "$option" in 373 | -i | --installed | "") 374 | if [ -f "$SDD_DATA_DIR"/apps/installed ]; then 375 | # List apps installed most recently by filtering unique app names first 376 | tac "$SDD_DATA_DIR"/apps/installed | sort -t= -k1,1 -u 377 | fi 378 | ;; 379 | -a | --available) 380 | printf 'Built-in:\n' 381 | find "$FRAMEWORKDIR/../apps/user" -type f -printf '- %f\n' | sort 382 | 383 | if [ -d "$HOME/.config/sdd/apps" ]; then 384 | printf '\nCustom:\n' 385 | find "$HOME/.config/sdd/apps" -follow -type f -printf '- %f\n' | sort 386 | fi 387 | ;; 388 | -u | --upgradable) 389 | local name installed_version newest_version 390 | 391 | utils_list --installed | while IFS='=' read -r name installed_version; do 392 | newest_version=$(_get_app_version_from_files "$name") 393 | 394 | if [[ "$installed_version" != "$newest_version" ]]; then 395 | printf '%s (%s -> %s)\n' "$name" "$installed_version" "$newest_version" 396 | fi 397 | done 398 | ;; 399 | *) 400 | printf 'Unknown option "%s".\n' "$option" >&2 401 | return 1 402 | ;; 403 | esac 404 | } 405 | 406 | _get_app_name() { 407 | # Separate app name from an app=version tuple 408 | # E.g. with input 'foo=1.0.0' the function publishes 'foo' 409 | echo "$1" | cut -d"=" -f1 410 | } 411 | 412 | _get_app_version_from_appver() { 413 | # Separate app version from an app=version tuple 414 | # E.g. with input 'foo=1.0.0' the function publishes '1.0.0' 415 | echo "$1" | cut -d"=" -f2 416 | } 417 | 418 | _tag_name_of_latest_github_release() { 419 | # Fetch tag name of latest release on GitHub 420 | local github_user=$1 421 | local repo_name=$2 422 | 423 | # Get tag name from URL redirection to avoid GitHub API limits 424 | awk -F'[ /]' -v e=1 '/Location:/ {e=0; print $(NF-1);} END {exit(e);}' < <( 425 | wget -o- --max-redirect 0 "https://github.com/$github_user/$repo_name/releases/latest" 426 | ) 427 | } 428 | 429 | _sha_of_github_master() { 430 | # Fetch SHA of latest commit on GitHub master branch 431 | local github_user=$1 432 | local repo_name=$2 433 | 434 | # Get latest commit hash from atom feed to avoid GitHub API limits 435 | wget -qO- "https://github.com/$github_user/$repo_name/commits/master.atom" | 436 | awk -F'[/<>]' '/tag:github.com,2008:Grit::Commit/ {i[n++] = $(NF-3);} 437 | END {print i[0];}' 438 | } 439 | 440 | _name_of_latest_github_tag() { 441 | local github_user=$1 442 | local repo_name=$2 443 | 444 | # Get latest tag from atom feed to avoid GitHub API limits 445 | wget -qO- "https://github.com/$github_user/$repo_name/tags.atom" | 446 | awk -F'[/<>]' '/tag:github.com,2008:Repository/ {i[n++] = $(NF-3);} 447 | END {print i[0];}' 448 | } 449 | -------------------------------------------------------------------------------- /release: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ $# -ne 1 ]; then 4 | printf "Usage: %s VERSION\n" "$0" >&2 5 | exit 1 6 | fi 7 | 8 | version="$1" 9 | if ! echo "$version" | grep -m1 -E 'v0.[0-9]+.[0-9]+.[0-9]+' >/dev/null 2>&1; then 10 | printf "Version '%s' not in format v0.X.Y.Z\n" "$version" >&2 11 | exit 1 12 | fi 13 | 14 | set -e 15 | git tag "$version" 16 | git push --tags origin master 17 | gh release create "$version" -n "$(awk -v RS='' "/\[$version\]/" Changelog.md | tail -n+2)" -t "$version" 18 | -------------------------------------------------------------------------------- /test/apps/bat.bats: -------------------------------------------------------------------------------- 1 | @test "bat of recent version can be installed and uninstalled" { 2 | run sdd install bat 3 | [ $status -eq 0 ] 4 | [[ "${lines[0]}" = 'Latest version available: '* ]] 5 | [ "${lines[-1]}" = 'Succeeded to install "bat".' ] 6 | 7 | run bat --version 8 | [ $status -eq 0 ] 9 | 10 | run sdd uninstall bat 11 | [ $status -eq 0 ] 12 | [ "$output" = 'Succeeded to uninstall "bat".' ] 13 | 14 | run which bat 15 | [ $status -eq 1 ] 16 | } 17 | -------------------------------------------------------------------------------- /test/apps/borg.bats: -------------------------------------------------------------------------------- 1 | @test "borg of recent version can be installed and uninstalled" { 2 | run sdd install borg 3 | [ $status -eq 0 ] 4 | [[ "${lines[0]}" = 'Latest version available: '* ]] 5 | [ "${lines[-1]}" = 'Succeeded to install "borg".' ] 6 | 7 | run borg --version 8 | [ $status -eq 0 ] 9 | 10 | run sdd uninstall borg 11 | [ $status -eq 0 ] 12 | [ "$output" = 'Succeeded to uninstall "borg".' ] 13 | 14 | run which borg 15 | [ $status -eq 1 ] 16 | } 17 | -------------------------------------------------------------------------------- /test/apps/broot.bats: -------------------------------------------------------------------------------- 1 | @test "broot of recent version can be installed and uninstalled" { 2 | run sdd install broot 3 | [ $status -eq 0 ] 4 | [[ "${lines[0]}" = 'Latest version available: '* ]] 5 | [ "${lines[-1]}" = 'Succeeded to install "broot".' ] 6 | 7 | run broot --version 8 | [ $status -eq 0 ] 9 | 10 | run sdd uninstall broot 11 | [ $status -eq 0 ] 12 | [ "$output" = 'Succeeded to uninstall "broot".' ] 13 | 14 | run which broot 15 | [ $status -eq 1 ] 16 | } 17 | -------------------------------------------------------------------------------- /test/apps/circleci.bats: -------------------------------------------------------------------------------- 1 | @test "circleci of recent version can be installed and uninstalled" { 2 | run sdd install circleci 3 | [ $status -eq 0 ] 4 | [[ "${lines[0]}" = 'Latest version available: '* ]] 5 | [ "${lines[-1]}" = 'Succeeded to install "circleci".' ] 6 | 7 | run circleci version 8 | [ $status -eq 0 ] 9 | 10 | [ -f ~/.local/share/bash-completion/completions/circleci ] 11 | [ -f ~/.local/share/zsh/site-functions/_circleci ] 12 | 13 | run sdd uninstall circleci 14 | [ $status -eq 0 ] 15 | [ "${lines[-1]}" = 'Succeeded to uninstall "circleci".' ] 16 | 17 | run which circleci 18 | [ $status -eq 1 ] 19 | } 20 | -------------------------------------------------------------------------------- /test/apps/dasel.bats: -------------------------------------------------------------------------------- 1 | @test "dasel of recent version can be installed and uninstalled" { 2 | run sdd install dasel 3 | [ $status -eq 0 ] 4 | [[ "${lines[0]}" = 'Latest version available: '* ]] 5 | [ "${lines[-1]}" = 'Succeeded to install "dasel".' ] 6 | 7 | run dasel --version 8 | [ $status -eq 0 ] 9 | 10 | run sdd uninstall dasel 11 | [ $status -eq 0 ] 12 | [ "$output" = 'Succeeded to uninstall "dasel".' ] 13 | 14 | run which dasel 15 | [ $status -eq 1 ] 16 | } 17 | -------------------------------------------------------------------------------- /test/apps/delta.bats: -------------------------------------------------------------------------------- 1 | @test "delta of recent version can be installed and uninstalled" { 2 | # Pin version because atm the latest version is tagged 'windows-strip-binary' 3 | # which is not a valid version identifier and cannot be resolved in Github URLs 4 | run sdd install delta=0.1.1 5 | [ $status -eq 0 ] 6 | # [[ "${lines[0]}" = 'Latest version available: '* ]] 7 | [ "${lines[-1]}" = 'Succeeded to install "delta".' ] 8 | 9 | run delta --version 10 | [ $status -eq 0 ] 11 | 12 | [ -f ~/.local/share/bash-completion/completions/delta ] 13 | 14 | run sdd uninstall delta 15 | [ $status -eq 0 ] 16 | [ "$output" = 'Succeeded to uninstall "delta".' ] 17 | 18 | run which delta 19 | [ $status -eq 1 ] 20 | } 21 | -------------------------------------------------------------------------------- /test/apps/diff-so-fancy.bats: -------------------------------------------------------------------------------- 1 | @test "diff-so-fancy of recent version can be installed and uninstalled" { 2 | run sdd install diff-so-fancy 3 | [ $status -eq 0 ] 4 | [[ "${lines[0]}" = 'Latest version available: '* ]] 5 | [ "${lines[-1]}" = 'Succeeded to install "diff-so-fancy".' ] 6 | 7 | run diff-so-fancy --colors 8 | [ $status -eq 0 ] 9 | 10 | run sdd uninstall diff-so-fancy 11 | [ $status -eq 0 ] 12 | [ "$output" = 'Succeeded to uninstall "diff-so-fancy".' ] 13 | 14 | run which diff-so-fancy 15 | [ $status -eq 1 ] 16 | } 17 | -------------------------------------------------------------------------------- /test/apps/direnv.bats: -------------------------------------------------------------------------------- 1 | @test "direnv of recent version can be installed and uninstalled" { 2 | run sdd install direnv 3 | [ $status -eq 0 ] 4 | [[ "${lines[0]}" = 'Latest version available: '* ]] 5 | [ "${lines[-1]}" = 'Succeeded to install "direnv".' ] 6 | 7 | run direnv version 8 | [ $status -eq 0 ] 9 | 10 | run sdd uninstall direnv 11 | [ $status -eq 0 ] 12 | [ "$output" = 'Succeeded to uninstall "direnv".' ] 13 | 14 | run which direnv 15 | [ $status -eq 1 ] 16 | } 17 | -------------------------------------------------------------------------------- /test/apps/docker-compose.bats: -------------------------------------------------------------------------------- 1 | @test "docker-compose of recent version can be installed and uninstalled" { 2 | run sdd install docker-compose 3 | [ $status -eq 0 ] 4 | [[ "${lines[0]}" = 'Latest version available: '* ]] 5 | [ "${lines[-1]}" = 'Succeeded to install "docker-compose".' ] 6 | 7 | run docker-compose --version 8 | [ $status -eq 0 ] 9 | 10 | run sdd uninstall docker-compose 11 | [ $status -eq 0 ] 12 | [ "$output" = 'Succeeded to uninstall "docker-compose".' ] 13 | 14 | run which docker-compose 15 | [ $status -eq 1 ] 16 | } 17 | -------------------------------------------------------------------------------- /test/apps/dust.bats: -------------------------------------------------------------------------------- 1 | @test "dust of recent version can be installed and uninstalled" { 2 | run sdd install dust 3 | [ $status -eq 0 ] 4 | [[ "${lines[0]}" = 'Latest version available: '* ]] 5 | [ "${lines[-1]}" = 'Succeeded to install "dust".' ] 6 | 7 | run dust --version 8 | [ $status -eq 0 ] 9 | 10 | run sdd uninstall dust 11 | [ $status -eq 0 ] 12 | [ "$output" = 'Succeeded to uninstall "dust".' ] 13 | 14 | run which dust 15 | [ $status -eq 1 ] 16 | } 17 | -------------------------------------------------------------------------------- /test/apps/fd.bats: -------------------------------------------------------------------------------- 1 | @test "fd of recent version can be installed and uninstalled" { 2 | run sdd install fd 3 | [ $status -eq 0 ] 4 | [[ "${lines[0]}" = 'Latest version available: '* ]] 5 | [ "${lines[-1]}" = 'Succeeded to install "fd".' ] 6 | 7 | run fd --version 8 | [ $status -eq 0 ] 9 | 10 | [ -f ~/.local/share/bash-completion/completions/fd ] 11 | [ -f ~/.local/share/zsh/site-functions/_fd ] 12 | 13 | run sdd uninstall fd 14 | [ $status -eq 0 ] 15 | [ "${lines[-1]}" = 'Succeeded to uninstall "fd".' ] 16 | 17 | run which fd 18 | [ $status -eq 1 ] 19 | } 20 | -------------------------------------------------------------------------------- /test/apps/ffsend.bats: -------------------------------------------------------------------------------- 1 | @test "ffsend of recent version can be installed and uninstalled" { 2 | run sdd install ffsend 3 | [ $status -eq 0 ] 4 | [[ "${lines[0]}" = 'Latest version available: '* ]] 5 | [ "${lines[-1]}" = 'Succeeded to install "ffsend".' ] 6 | 7 | run ffsend --version 8 | [ $status -eq 0 ] 9 | 10 | run sdd uninstall ffsend 11 | [ $status -eq 0 ] 12 | [ "$output" = 'Succeeded to uninstall "ffsend".' ] 13 | 14 | run which ffsend 15 | [ $status -eq 1 ] 16 | } 17 | -------------------------------------------------------------------------------- /test/apps/gh.bats: -------------------------------------------------------------------------------- 1 | @test "gh of recent version can be installed and uninstalled" { 2 | run sdd install gh 3 | [ $status -eq 0 ] 4 | [[ "${lines[0]}" = 'Latest version available: '* ]] 5 | [ "${lines[-1]}" = 'Succeeded to install "gh".' ] 6 | 7 | run gh --version 8 | [ $status -eq 0 ] 9 | 10 | [ -f ~/.local/share/bash-completion/completions/gh ] 11 | [ -f ~/.local/share/zsh/site-functions/_gh ] 12 | [ -f ~/.local/share/man/man1/gh.1 ] 13 | 14 | run sdd uninstall gh 15 | [ $status -eq 0 ] 16 | [ "$output" = 'Succeeded to uninstall "gh".' ] 17 | 18 | run which gh 19 | [ $status -eq 1 ] 20 | } 21 | -------------------------------------------------------------------------------- /test/apps/git-trim.bats: -------------------------------------------------------------------------------- 1 | @test "git-trim of recent version can be installed and uninstalled" { 2 | run sdd install git-trim 3 | [ $status -eq 0 ] 4 | [[ "${lines[0]}" = 'Latest version available: '* ]] 5 | [ "${lines[-1]}" = 'Succeeded to install "git-trim".' ] 6 | 7 | run git-trim --version 8 | [ $status -eq 0 ] 9 | 10 | run sdd uninstall git-trim 11 | [ $status -eq 0 ] 12 | [ "$output" = 'Succeeded to uninstall "git-trim".' ] 13 | 14 | run which git-trim 15 | [ $status -eq 1 ] 16 | } 17 | -------------------------------------------------------------------------------- /test/apps/gitui.bats: -------------------------------------------------------------------------------- 1 | @test "gitui of recent version can be installed and uninstalled" { 2 | run sdd install gitui 3 | [ $status -eq 0 ] 4 | [[ "${lines[0]}" = 'Latest version available: '* ]] 5 | [ "${lines[-1]}" = 'Succeeded to install "gitui".' ] 6 | 7 | run gitui --version 8 | [ $status -eq 0 ] 9 | 10 | run sdd uninstall gitui 11 | [ $status -eq 0 ] 12 | [ "$output" = 'Succeeded to uninstall "gitui".' ] 13 | 14 | run which gitui 15 | [ $status -eq 1 ] 16 | } 17 | -------------------------------------------------------------------------------- /test/apps/go.bats: -------------------------------------------------------------------------------- 1 | @test "go of recent version can be installed and uninstalled" { 2 | run sdd install go 3 | [ $status -eq 0 ] 4 | [[ "${lines[0]}" = 'Latest version available: '* ]] 5 | [ "${lines[-1]}" = 'Succeeded to install "go".' ] 6 | 7 | run ~/.local/go/bin/go version 8 | [ $status -eq 0 ] 9 | 10 | run sdd uninstall go 11 | [ $status -eq 0 ] 12 | [ "$output" = 'Succeeded to uninstall "go".' ] 13 | 14 | run which go 15 | [ $status -eq 1 ] 16 | } 17 | -------------------------------------------------------------------------------- /test/apps/hub.bats: -------------------------------------------------------------------------------- 1 | @test "hub of recent version can be installed and uninstalled" { 2 | run sdd install hub 3 | [ $status -eq 0 ] 4 | [[ "${lines[0]}" = 'Latest version available: '* ]] 5 | [ "${lines[-1]}" = 'Succeeded to install "hub".' ] 6 | 7 | run hub --version 8 | [ $status -eq 0 ] 9 | 10 | [ -f ~/.local/share/zsh/site-functions/_hub ] 11 | 12 | run sdd uninstall hub 13 | [ $status -eq 0 ] 14 | [ "$output" = 'Succeeded to uninstall "hub".' ] 15 | 16 | run which hub 17 | [ $status -eq 1 ] 18 | } 19 | -------------------------------------------------------------------------------- /test/apps/jira.bats: -------------------------------------------------------------------------------- 1 | @test "jira of recent version can be installed and uninstalled" { 2 | run sdd install jira 3 | [ $status -eq 0 ] 4 | [[ "${lines[0]}" = 'Latest version available: '* ]] 5 | [ "${lines[-1]}" = 'Succeeded to install "jira".' ] 6 | 7 | run jira version 8 | [ $status -eq 0 ] 9 | 10 | [ -f ~/.local/share/bash-completion/completions/jira ] 11 | [ -f ~/.local/share/zsh/site-functions/_jira ] 12 | 13 | run sdd uninstall jira 14 | [ $status -eq 0 ] 15 | [ "$output" = 'Succeeded to uninstall "jira".' ] 16 | 17 | run which jira 18 | [ $status -eq 1 ] 19 | } 20 | -------------------------------------------------------------------------------- /test/apps/jq.bats: -------------------------------------------------------------------------------- 1 | @test "jq of recent version can be installed and uninstalled" { 2 | run sdd install jq 3 | [ $status -eq 0 ] 4 | [[ "${lines[0]}" = 'Latest version available: '* ]] 5 | [ "${lines[-1]}" = 'Succeeded to install "jq".' ] 6 | 7 | run jq --version 8 | [ $status -eq 0 ] 9 | 10 | run sdd uninstall jq 11 | [ $status -eq 0 ] 12 | [ "$output" = 'Succeeded to uninstall "jq".' ] 13 | 14 | run which jq 15 | [ $status -eq 1 ] 16 | } 17 | -------------------------------------------------------------------------------- /test/apps/ncdu.bats: -------------------------------------------------------------------------------- 1 | @test "ncdu of recent version can be installed and uninstalled" { 2 | run sdd install ncdu 3 | [ $status -eq 0 ] 4 | [[ "${lines[0]}" = 'Latest version available: '* ]] 5 | [ "${lines[-1]}" = 'Succeeded to install "ncdu".' ] 6 | 7 | run ncdu -v 8 | [ $status -eq 0 ] 9 | 10 | run sdd uninstall ncdu 11 | [ $status -eq 0 ] 12 | [ "$output" = 'Succeeded to uninstall "ncdu".' ] 13 | 14 | run which ncdu 15 | [ $status -eq 1 ] 16 | } 17 | -------------------------------------------------------------------------------- /test/apps/oh-my-zsh.bats: -------------------------------------------------------------------------------- 1 | zsh_mock=~/.local/bin/zsh 2 | 3 | setup() { 4 | mkdir -p $(dirname $zsh_mock) 5 | touch $zsh_mock 6 | chmod +x $zsh_mock 7 | } 8 | 9 | teardown() { 10 | rm -f $zsh_mock 11 | } 12 | 13 | @test "oh-my-zsh of recent version can be installed and uninstalled" { 14 | run sdd install oh-my-zsh 15 | [ $status -eq 0 ] 16 | [[ "${lines[0]}" = 'Latest version available: '* ]] 17 | [ "${lines[-1]}" = 'Succeeded to install "oh-my-zsh".' ] 18 | 19 | [ -d ~/.oh-my-zsh ] 20 | 21 | run sdd upgrade oh-my-zsh 22 | [ $status -eq 0 ] 23 | [ "${lines[-1]}" = 'Succeeded to upgrade "oh-my-zsh".' ] 24 | 25 | run sdd uninstall oh-my-zsh 26 | [ $status -eq 0 ] 27 | [ "$output" = 'Succeeded to uninstall "oh-my-zsh".' ] 28 | } 29 | -------------------------------------------------------------------------------- /test/apps/pandoc.bats: -------------------------------------------------------------------------------- 1 | @test "pandoc of recent version can be installed and uninstalled" { 2 | run sdd install pandoc 3 | [ $status -eq 0 ] 4 | [[ "${lines[0]}" = 'Latest version available: '* ]] 5 | [ "${lines[-1]}" = 'Succeeded to install "pandoc".' ] 6 | 7 | run pandoc --version 8 | [ $status -eq 0 ] 9 | 10 | [ -f ~/.local/share/bash-completion/completions/pandoc ] 11 | 12 | run sdd uninstall pandoc 13 | [ $status -eq 0 ] 14 | [ "${lines[-1]}" = 'Succeeded to uninstall "pandoc".' ] 15 | 16 | run which pandoc 17 | [ $status -eq 1 ] 18 | } 19 | -------------------------------------------------------------------------------- /test/apps/pip.bats: -------------------------------------------------------------------------------- 1 | @test "pip of recent version can be installed, upgraded and uninstalled" { 2 | run sdd install pip=19.0 3 | [ $status -eq 0 ] 4 | [ "${lines[0]}" = 'Specified version: 19.0' ] 5 | [ "${lines[-1]}" = 'Succeeded to install "pip".' ] 6 | 7 | run pip --version 8 | [ $status -eq 0 ] 9 | 10 | [ -f ~/.local/share/bash-completion/completions/pip ] 11 | [ -f ~/.local/share/zsh/site-functions/_pip ] 12 | 13 | run sdd upgrade pip=20.0.1 14 | [ $status -eq 0 ] 15 | [ "${lines[-1]}" = 'Succeeded to upgrade "pip".' ] 16 | 17 | run sdd uninstall pip 18 | [ $status -eq 0 ] 19 | [ "${lines[-1]}" = 'Succeeded to uninstall "pip".' ] 20 | 21 | run which pip 22 | [ $status -eq 1 ] 23 | } 24 | -------------------------------------------------------------------------------- /test/apps/python.bats: -------------------------------------------------------------------------------- 1 | @test "python of recent version can be installed and uninstalled" { 2 | run sdd install python 3 | [ $status -eq 0 ] 4 | [[ "${lines[0]}" = 'Latest version available: '* ]] 5 | [ "${lines[-1]}" = 'Succeeded to install "python".' ] 6 | 7 | run ~/.local/python/3.10.9+20230116/bin/python3 --version 8 | [ $status -eq 0 ] 9 | 10 | run sdd uninstall python 11 | [ $status -eq 0 ] 12 | [ "${lines[-1]}" = 'Succeeded to uninstall "python".' ] 13 | } 14 | -------------------------------------------------------------------------------- /test/apps/qmlfmt.bats: -------------------------------------------------------------------------------- 1 | @test "qmlfmt of recent version is present in PATH" { 2 | skip 3 | run sdd install qmlfmt 4 | echo $output 5 | [ $status -eq 0 ] 6 | [[ "${lines[0]}" = 'Latest version available: '* ]] 7 | [ "${lines[-1]}" = 'Succeeded to install "qmlfmt".' ] 8 | 9 | run qmlfmt --version 10 | [ $status -eq 0 ] 11 | } 12 | -------------------------------------------------------------------------------- /test/apps/qrcp.bats: -------------------------------------------------------------------------------- 1 | @test "qrcp of recent version can be installed and uninstalled" { 2 | run sdd install qrcp 3 | [ $status -eq 0 ] 4 | [[ "${lines[0]}" = 'Latest version available: '* ]] 5 | [ "${lines[-1]}" = 'Succeeded to install "qrcp".' ] 6 | 7 | run qrcp version 8 | [ $status -eq 0 ] 9 | 10 | run sdd uninstall qrcp 11 | [ $status -eq 0 ] 12 | [ "${lines[-1]}" = 'Succeeded to uninstall "qrcp".' ] 13 | 14 | run which qrcp 15 | [ $status -eq 1 ] 16 | } 17 | -------------------------------------------------------------------------------- /test/apps/ripgrep.bats: -------------------------------------------------------------------------------- 1 | @test "ripgrep of recent version can be installed and uninstalled" { 2 | run sdd install ripgrep 3 | [ $status -eq 0 ] 4 | [[ "${lines[0]}" = 'Latest version available: '* ]] 5 | [ "${lines[-1]}" = 'Succeeded to install "ripgrep".' ] 6 | 7 | run rg --version 8 | [ $status -eq 0 ] 9 | 10 | [ -f ~/.local/share/bash-completion/completions/rg ] 11 | [ -f ~/.local/share/zsh/site-functions/_rg ] 12 | 13 | run sdd uninstall ripgrep 14 | [ $status -eq 0 ] 15 | [ "$output" = 'Succeeded to uninstall "ripgrep".' ] 16 | 17 | run which rg 18 | [ $status -eq 1 ] 19 | } 20 | -------------------------------------------------------------------------------- /test/apps/sdd.bats: -------------------------------------------------------------------------------- 1 | @test "sdd of recent version can be installed and uninstalled" { 2 | run sdd install sdd 3 | [ $status -eq 0 ] 4 | [[ "${lines[0]}" = 'Latest version available: '* ]] 5 | [ "${lines[-1]}" = 'Succeeded to install "sdd".' ] 6 | 7 | # Installed sdd should be first in PATH 8 | run which sdd 9 | [ $status -eq 0 ] 10 | [ $output = "$HOME/.local/bin/sdd" ] 11 | 12 | # hen-egg issue... at the time of writing the test, the sdd_upgrade or the 13 | # sdd_uninstall command is not yet present in the repo which is cloned for 14 | # installation. Hence explicitely use the 'test' binary 15 | run /opt/sdd/bin/sdd upgrade sdd 16 | [ $status -eq 0 ] 17 | 18 | run /opt/sdd/bin/sdd uninstall sdd 19 | [ $status -eq 0 ] 20 | [ "$output" = 'Succeeded to uninstall "sdd".' ] 21 | 22 | # The binary under test is still in the path 23 | run which sdd 24 | [ $status -eq 0 ] 25 | [ $output = /opt/sdd/bin/sdd ] 26 | 27 | [ ! -e "$HOME/.local/lib/sdd" ] 28 | } 29 | -------------------------------------------------------------------------------- /test/apps/shellcheck.bats: -------------------------------------------------------------------------------- 1 | @test "shellcheck of recent version can be installed and uninstalled" { 2 | run sdd install shellcheck 3 | [ $status -eq 0 ] 4 | [[ "${lines[0]}" = 'Latest version available: '* ]] 5 | [ "${lines[-1]}" = 'Succeeded to install "shellcheck".' ] 6 | 7 | run shellcheck --version 8 | [ $status -eq 0 ] 9 | 10 | run sdd uninstall shellcheck 11 | [ $status -eq 0 ] 12 | [ "${lines[-1]}" = 'Succeeded to uninstall "shellcheck".' ] 13 | 14 | run which shellcheck 15 | [ $status -eq 1 ] 16 | } 17 | -------------------------------------------------------------------------------- /test/apps/shfmt.bats: -------------------------------------------------------------------------------- 1 | @test "shfmt of recent version can be installed and uninstalled" { 2 | run sdd install shfmt 3 | [ $status -eq 0 ] 4 | [[ "${lines[0]}" = 'Latest version available: '* ]] 5 | [ "${lines[-1]}" = 'Succeeded to install "shfmt".' ] 6 | 7 | run shfmt --version 8 | [ $status -eq 0 ] 9 | 10 | run sdd uninstall shfmt 11 | [ $status -eq 0 ] 12 | [ "$output" = 'Succeeded to uninstall "shfmt".' ] 13 | 14 | run which shfmt 15 | [ $status -eq 1 ] 16 | } 17 | -------------------------------------------------------------------------------- /test/apps/slack-term.bats: -------------------------------------------------------------------------------- 1 | @test "slack-term of recent version can be installed and uninstalled" { 2 | run sdd install slack-term 3 | [ $status -eq 0 ] 4 | [[ "${lines[0]}" = 'Latest version available: '* ]] 5 | [ "${lines[-1]}" = 'Succeeded to install "slack-term".' ] 6 | 7 | run slack-term -help 8 | [ $status -eq 2 ] 9 | 10 | run sdd uninstall slack-term 11 | [ $status -eq 0 ] 12 | [ "$output" = 'Succeeded to uninstall "slack-term".' ] 13 | 14 | run which slack-term 15 | [ $status -eq 1 ] 16 | } 17 | -------------------------------------------------------------------------------- /test/apps/telegram.bats: -------------------------------------------------------------------------------- 1 | @test "telegram of recent version can be installed and uninstalled" { 2 | run sdd install telegram 3 | [ $status -eq 0 ] 4 | [[ "${lines[0]}" = 'Latest version available: '* ]] 5 | [ "${lines[-1]}" = 'Succeeded to install "telegram".' ] 6 | 7 | run which telegram 8 | [ $status -eq 0 ] 9 | 10 | run sdd uninstall telegram 11 | [ $status -eq 0 ] 12 | [ "$output" = 'Succeeded to uninstall "telegram".' ] 13 | 14 | run which telegram 15 | [ $status -eq 1 ] 16 | } 17 | -------------------------------------------------------------------------------- /test/apps/wuzz.bats: -------------------------------------------------------------------------------- 1 | @test "wuzz of recent version can be installed and uninstalled" { 2 | run sdd install wuzz 3 | [ $status -eq 0 ] 4 | [[ "${lines[0]}" = 'Latest version available: '* ]] 5 | [ "${lines[-1]}" = 'Succeeded to install "wuzz".' ] 6 | 7 | run wuzz --version 8 | [ $status -eq 0 ] 9 | 10 | run sdd uninstall wuzz 11 | [ $status -eq 0 ] 12 | [ "$output" = 'Succeeded to uninstall "wuzz".' ] 13 | 14 | run which wuzz 15 | [ $status -eq 1 ] 16 | } 17 | -------------------------------------------------------------------------------- /test/apps/xh.bats: -------------------------------------------------------------------------------- 1 | @test "xh of recent version can be installed and uninstalled" { 2 | run sdd install xh 3 | [ $status -eq 0 ] 4 | [[ "${lines[0]}" = 'Latest version available: '* ]] 5 | [ "${lines[-1]}" = 'Succeeded to install "xh".' ] 6 | 7 | run xh --version 8 | [ $status -eq 0 ] 9 | 10 | [ -f ~/.local/share/bash-completion/completions/xh ] 11 | [ -f ~/.local/share/zsh/site-functions/_xh ] 12 | 13 | run sdd uninstall xh 14 | [ $status -eq 0 ] 15 | [ "${lines[-1]}" = 'Succeeded to uninstall "xh".' ] 16 | 17 | run which xh 18 | [ $status -eq 1 ] 19 | } 20 | -------------------------------------------------------------------------------- /test/apps/xsv.bats: -------------------------------------------------------------------------------- 1 | @test "xsv of recent version can be installed and uninstalled" { 2 | run sdd install xsv 3 | [ $status -eq 0 ] 4 | [[ "${lines[0]}" = 'Latest version available: '* ]] 5 | [ "${lines[-1]}" = 'Succeeded to install "xsv".' ] 6 | 7 | run xsv --version 8 | [ $status -eq 0 ] 9 | 10 | run sdd uninstall xsv 11 | [ $status -eq 0 ] 12 | [ "$output" = 'Succeeded to uninstall "xsv".' ] 13 | 14 | run which xsv 15 | [ $status -eq 1 ] 16 | } 17 | -------------------------------------------------------------------------------- /test/framework/bin.bats: -------------------------------------------------------------------------------- 1 | load '/usr/local/libexec/bats-support/load.bash' 2 | load '/usr/local/libexec/bats-assert/load.bash' 3 | 4 | validappfilepath="$BATS_TEST_DIRNAME"/../../lib/sdd/apps/user/valid_app 5 | superappfilepath="$BATS_TEST_DIRNAME"/../../lib/sdd/apps/user/super_app 6 | validcustomappfilepath=$HOME/.config/sdd/apps/valid_app 7 | invalidappfilepath="$BATS_TEST_DIRNAME"/../../lib/sdd/apps/user/invalid_app 8 | appsrecordfilepath=$HOME/.local/share/sdd/apps/installed 9 | fixturesfilepath="$BATS_TEST_DIRNAME"/fixtures 10 | 11 | setup() { 12 | mkdir -p $(dirname $validcustomappfilepath) 13 | } 14 | 15 | teardown() { 16 | rm -f "$validappfilepath" 17 | rm -f "$superappfilepath" 18 | rm -f "$invalidappfilepath" 19 | rm -f ${SDD_INSTALL_PREFIX:-$HOME/.local}/bin/valid_app 20 | rm -f "$appsrecordfilepath" 21 | rm -rf $HOME/.config/sdd 22 | } 23 | 24 | @test "invoking main executable prints usage" { 25 | run sdd 26 | [ "$status" -eq 0 ] 27 | [ "${lines[0]}" = "Usage: sdd [OPTIONS] COMMAND [APP [APP...]]" ] 28 | } 29 | 30 | @test "invoking unknown command fails" { 31 | run sdd unknown-command 32 | [ "$status" -eq 127 ] 33 | [ "$output" = 'Unknown command "unknown-command"' ] 34 | } 35 | 36 | @test "invoking help option prints usage" { 37 | run sdd --help 38 | [ "$status" -eq 0 ] 39 | [ "${lines[0]}" = "Usage: sdd [OPTIONS] COMMAND [APP [APP...]]" ] 40 | } 41 | 42 | @test "invoking install command without argument fails" { 43 | run sdd install 44 | [ "$status" -eq 1 ] 45 | [ "$output" = 'Specify at least one app to install.' ] 46 | } 47 | 48 | @test "invoking install command with non-existing app fails" { 49 | run sdd install non_existing_app 50 | [ "$status" -eq 2 ] 51 | [ "$output" = 'App "non_existing_app" could not be found.' ] 52 | } 53 | 54 | @test "invoking install command with existing app but without sdd_install present fails" { 55 | touch $invalidappfilepath 56 | 57 | run sdd install invalid_app 58 | assert_failure 4 59 | assert_line -n 0 'Failed to install "invalid_app". See /tmp/sdd-install-invalid_app.stderr.' 60 | } 61 | 62 | @test "invoking install command with valid app succeeds" { 63 | # Create app management file for 'valid_app' containing an sdd_install 64 | # function that creates an executable 'valid_app' 65 | cp "$fixturesfilepath"/valid_app $validappfilepath 66 | 67 | run sdd install valid_app 68 | [ "$status" -eq 0 ] 69 | assert_line -n 0 'Latest version available: 1.0' 70 | assert_line -n 1 'Succeeded to install "valid_app".' 71 | assert_equal ${#lines[@]} 2 72 | 73 | # Execute the app 74 | run valid_app 75 | [ "$status" -eq 0 ] 76 | 77 | # The installed app version is recorded 78 | [ "$(tail -n1 $appsrecordfilepath)" = "valid_app=1.0" ] 79 | } 80 | 81 | @test "invoking install command with valid custom app succeeds" { 82 | # Create app management file for 'valid_app' containing an sdd_install 83 | # function that creates an executable 'valid_app' 84 | cp "$fixturesfilepath"/valid_app $validcustomappfilepath 85 | 86 | run sdd install valid_app 87 | [ "$status" -eq 0 ] 88 | [ "${lines[0]}" = 'Custom install function for "valid_app" found.' ] 89 | [ "${lines[1]}" = 'Latest version available: 1.0' ] 90 | [ "${lines[2]}" = 'Succeeded to install "valid_app".' ] 91 | assert_equal ${#lines[@]} 3 92 | 93 | # Execute the app 94 | run valid_app 95 | [ "$status" -eq 0 ] 96 | 97 | # The installed app version is recorded 98 | [ "$(tail -n1 $appsrecordfilepath)" = "valid_app=1.0" ] 99 | } 100 | 101 | @test "invoking install command with valid customized app succeeds" { 102 | # Custom app management file specifies newer version 103 | cp "$fixturesfilepath"/valid_app $validcustomappfilepath 104 | sed -i 's/1.0/1.1/' $validcustomappfilepath 105 | cp "$fixturesfilepath"/valid_app $validappfilepath 106 | 107 | run sdd install valid_app 108 | [ "$status" -eq 0 ] 109 | [ "${lines[0]}" = 'Custom install function for "valid_app" found.' ] 110 | [ "${lines[1]}" = 'Latest version available: 1.1' ] 111 | [ "${lines[2]}" = 'Succeeded to install "valid_app".' ] 112 | assert_equal ${#lines[@]} 3 113 | 114 | # Execute the app 115 | run valid_app 116 | [ "$status" -eq 0 ] 117 | 118 | # The installed app version is recorded 119 | [ "$(tail -n1 $appsrecordfilepath)" = "valid_app=1.1" ] 120 | } 121 | 122 | @test "invoking install command with valid customized app without sdd_fetch_latest_version succeeds" { 123 | echo "sdd_install() { return 0; }" > $validcustomappfilepath 124 | cp "$fixturesfilepath"/valid_app $validappfilepath 125 | 126 | run sdd install valid_app 127 | [ "$status" -eq 0 ] 128 | [ "${lines[0]}" = 'Custom install function for "valid_app" found.' ] 129 | [ "${lines[1]}" = 'Latest version available: 1.0' ] 130 | [ "${lines[2]}" = 'Succeeded to install "valid_app".' ] 131 | assert_equal ${#lines[@]} 3 132 | 133 | # Execute the app 134 | run valid_app 135 | [ "$status" -eq 0 ] 136 | 137 | # The installed app version is recorded 138 | [ "$(tail -n1 $appsrecordfilepath)" = "valid_app=1.0" ] 139 | } 140 | 141 | @test "invoking install command with valid and non-existing app installs only valid one" { 142 | cp "$fixturesfilepath"/valid_app $validappfilepath 143 | 144 | run sdd install valid_app non_existing_app 145 | assert_failure 2 146 | [ "${lines[0]}" = 'App "non_existing_app" could not be found.' ] 147 | # Two more lines about installing valid_app 148 | assert_equal ${#lines[@]} 3 149 | 150 | run valid_app 151 | [ "$status" -eq 0 ] 152 | 153 | run which invalid_app 154 | [ "$status" -eq 1 ] 155 | } 156 | 157 | @test "invoking install command with valid and invalid app installs only valid one" { 158 | cp "$fixturesfilepath"/valid_app $validappfilepath 159 | touch $invalidappfilepath 160 | 161 | run sdd install valid_app invalid_app 162 | assert_failure 4 163 | assert_line -n 0 'Latest version available: 1.0' 164 | assert_line -n 1 'Succeeded to install "valid_app".' 165 | assert_line -n 2 'Failed to install "invalid_app". See /tmp/sdd-install-invalid_app.stderr.' 166 | assert_equal ${#lines[@]} 3 167 | 168 | run valid_app 169 | [ "$status" -eq 0 ] 170 | 171 | run which invalid_app 172 | [ "$status" -eq 1 ] 173 | } 174 | 175 | @test "invoking install command with valid custom and non-existing app installs only custom one" { 176 | cp "$fixturesfilepath"/valid_app $validcustomappfilepath 177 | 178 | run sdd install valid_app non_existing_app 179 | assert_failure 2 180 | [ "${lines[0]}" = 'App "non_existing_app" could not be found.' ] 181 | [ "${lines[1]}" = 'Custom install function for "valid_app" found.' ] 182 | [ "${lines[2]}" = 'Latest version available: 1.0' ] 183 | [ "${lines[3]}" = 'Succeeded to install "valid_app".' ] 184 | assert_equal ${#lines[@]} 4 185 | 186 | run valid_app 187 | [ "$status" -eq 0 ] 188 | 189 | run which non_existing_app 190 | [ "$status" -eq 1 ] 191 | } 192 | 193 | @test "invoking install command with valid app and version succeeds" { 194 | cp "$fixturesfilepath"/valid_app $validappfilepath 195 | 196 | run sdd install valid_app=1.1 197 | [ "$status" -eq 0 ] 198 | assert_line -n 0 'Specified version: 1.1' 199 | assert_line -n 1 'Succeeded to install "valid_app".' 200 | assert_equal ${#lines[@]} 2 201 | 202 | # The installed app version is recorded 203 | [ "$(tail -n1 $appsrecordfilepath)" = "valid_app=1.1" ] 204 | } 205 | 206 | @test "invoking uninstall command without argument fails" { 207 | run sdd uninstall 208 | [ "$status" -eq 1 ] 209 | [ "$output" = 'Specify at least one app to uninstall.' ] 210 | } 211 | 212 | @test "invoking uninstall command with non-existing app fails" { 213 | run sdd uninstall non_existing_app 214 | [ "$status" -eq 2 ] 215 | [ "$output" = 'App "non_existing_app" could not be found.' ] 216 | } 217 | 218 | @test "invoking uninstall command with existing app but without sdd_uninstall present fails" { 219 | touch $invalidappfilepath 220 | 221 | run sdd uninstall invalid_app 222 | assert_failure 8 223 | assert_line -n 0 'Failed to uninstall "invalid_app". See /tmp/sdd-uninstall-invalid_app.stderr.' 224 | } 225 | 226 | @test "invoking uninstall command with valid app succeeds" { 227 | # Create app management file for 'valid_app' containing an sdd_uninstall 228 | # function that uninstalls the executable 'valid_app' 229 | cp "$fixturesfilepath"/valid_app $validappfilepath 230 | 231 | # Install 'valid_app' 232 | sdd install valid_app 233 | run valid_app 234 | [ "$status" -eq 0 ] 235 | 236 | run sdd uninstall valid_app 237 | [ "$status" -eq 0 ] 238 | [ "${lines[0]}" = 'Succeeded to uninstall "valid_app".' ] 239 | assert_equal ${#lines[@]} 1 240 | 241 | # Check log file content 242 | run cat /tmp/sdd-uninstall-valid_app.stderr 243 | assert_output 'Succeeded to uninstall "valid_app".' 244 | 245 | run which valid_app 246 | [ "$status" -eq 1 ] 247 | } 248 | 249 | @test "invoking uninstall command with valid customized app succeeds" { 250 | echo "sdd_uninstall() { touch /tmp/uninstalled; }" > $validcustomappfilepath 251 | cp "$fixturesfilepath"/valid_app $validappfilepath 252 | 253 | run sdd install valid_app 254 | [ "$status" -eq 0 ] 255 | 256 | run sdd uninstall valid_app 257 | [ "$status" -eq 0 ] 258 | [ "${lines[0]}" = 'Custom uninstall function for "valid_app" found.' ] 259 | [ "${lines[1]}" = 'Succeeded to uninstall "valid_app".' ] 260 | assert_equal ${#lines[@]} 2 261 | 262 | # This file is expected to have been created by the custom sdd_uninstall 263 | [ -f /tmp/uninstalled ] 264 | 265 | run which valid_app 266 | [ "$status" -eq 1 ] 267 | } 268 | 269 | @test "invoking upgrade command without argument fails" { 270 | run sdd upgrade 271 | assert_failure 1 272 | assert_output 'Specify at least one app to upgrade.' 273 | assert_equal ${#lines[@]} 1 274 | } 275 | 276 | @test "invoking upgrade command with non-existing app fails" { 277 | run sdd upgrade non_existing_app 278 | assert_failure 2 279 | assert_line -n 0 'App "non_existing_app" could not be found.' 280 | assert_equal ${#lines[@]} 1 281 | } 282 | 283 | @test "invoking upgrade command with existing app succeeds" { 284 | # Assume app is already installed 285 | cp "$fixturesfilepath"/valid_app $validappfilepath 286 | sdd install valid_app 287 | 288 | # Bump version number 289 | sed -i 's/1.0/1.1/' $validappfilepath 290 | 291 | run sdd upgrade valid_app 292 | assert_success 293 | assert_line -n 0 'Latest version available: 1.1' 294 | assert_line -n 1 'Succeeded to upgrade "valid_app".' 295 | assert_equal ${#lines[@]} 2 296 | 297 | # Execute the app 298 | run valid_app 299 | assert_success 300 | 301 | # The installed app version is recorded 302 | [ "$(tail -n1 $appsrecordfilepath)" = "valid_app=1.1" ] 303 | } 304 | 305 | @test "invoking upgrade command with valid app and version succeeds" { 306 | cp "$fixturesfilepath"/valid_app $validappfilepath 307 | sdd install valid_app 308 | 309 | # Bump version number 310 | sed -i 's/1.0/1.1/' $validappfilepath 311 | 312 | run sdd upgrade valid_app=1.1 313 | assert_success 314 | assert_line -n 0 'Specified version: 1.1' 315 | assert_line -n 1 'Succeeded to upgrade "valid_app".' 316 | assert_equal ${#lines[@]} 2 317 | 318 | # The upgraded app version is recorded 319 | [ "$(tail -n1 $appsrecordfilepath)" = "valid_app=1.1" ] 320 | } 321 | 322 | @test "invoking upgrade command with existing app but without sdd_uninstall present fails" { 323 | touch $invalidappfilepath 324 | 325 | run sdd upgrade invalid_app 326 | assert_failure 8 327 | assert_line -n 0 'Failed to upgrade "invalid_app". See /tmp/sdd-upgrade-invalid_app.stderr.' 328 | assert_equal ${#lines[@]} 1 329 | } 330 | 331 | @test "invoking upgrade command with existing app but without sdd_install present fails" { 332 | cat > $invalidappfilepath </dev/null; echo Upgrading to $1...; }' >> $validappfilepath 362 | 363 | run sdd upgrade valid_app 364 | assert_success 365 | assert_line -n 0 'Latest version available: 1.0' 366 | assert_line -n 1 'Succeeded to upgrade "valid_app".' 367 | assert_equal ${#lines[@]} 2 368 | 369 | run valid_app 370 | assert_success 371 | 372 | run cat /tmp/sdd-upgrade-valid_app.stderr 373 | assert_line -n 0 'Upgrading to 1.0...' 374 | assert_line -n 1 'Succeeded to upgrade "valid_app".' 375 | 376 | # The upgraded app version is recorded 377 | [ "$(tail -n1 $appsrecordfilepath)" = "valid_app=1.0" ] 378 | } 379 | 380 | @test "invoking upgrade command with existing app and invalid sdd_upgrade fails" { 381 | echo 'sdd_upgrade() { return 1; }' > $invalidappfilepath 382 | 383 | run sdd upgrade invalid_app 384 | assert_failure 16 385 | assert_line -n 0 'Failed to upgrade "invalid_app". See /tmp/sdd-upgrade-invalid_app.stderr.' 386 | assert_equal ${#lines[@]} 1 387 | } 388 | 389 | @test "invoking upgrade command with valid custom app succeeds" { 390 | echo 'sdd_upgrade() { echo Upgrading...; }' > $validcustomappfilepath 391 | 392 | run sdd upgrade valid_app 393 | assert_success 394 | assert_line -n 0 'Custom upgrade function for "valid_app" found.' 395 | assert_line -n 1 'Succeeded to upgrade "valid_app".' 396 | assert_equal ${#lines[@]} 2 397 | 398 | run cat /tmp/sdd-upgrade-valid_app.stderr 399 | assert_line -n 0 'Upgrading...' 400 | } 401 | 402 | @test "invoking list with --installed option displays installed apps including versions" { 403 | cp "$fixturesfilepath"/valid_app $validappfilepath 404 | sdd install valid_app 405 | 406 | run sdd list --installed 407 | [ $status -eq 0 ] 408 | [ "${lines[0]}" = "valid_app=1.0" ] 409 | assert_equal ${#lines[@]} 1 410 | 411 | # Bump version number 412 | sed -i 's/1.0/1.1/' $validappfilepath 413 | sdd install valid_app 414 | 415 | run sdd list --installed 416 | [ $status -eq 0 ] 417 | [ "${lines[0]}" = "valid_app=1.1" ] 418 | assert_equal ${#lines[@]} 1 419 | 420 | sdd uninstall valid_app 421 | run sdd list --installed 422 | [ $status -eq 0 ] 423 | [ "$output" = "" ] 424 | } 425 | 426 | @test "invoking list with --available option displays available apps" { 427 | cp "$fixturesfilepath"/valid_app $validappfilepath 428 | touch $(dirname $validcustomappfilepath)/custom_app 429 | 430 | run sdd list --available 431 | assert_success 432 | assert_line -n 0 "Built-in:" 433 | assert_output -p "- valid_app" 434 | [ "${lines[-2]}" = "Custom:" ] 435 | [ "${lines[-1]}" = "- custom_app" ] 436 | 437 | # Two lines for categories, one for 'custom_app'. Empty line not counted by bats 438 | expected_nr_lines=3 439 | ((expected_nr_lines+=$(find "$BATS_TEST_DIRNAME"/../../lib/sdd/apps/user -type f | wc -l))) 440 | assert_equal ${#lines[@]} $expected_nr_lines 441 | } 442 | 443 | @test "invoking list with --upgradable option displays upgradable apps" { 444 | # Install two apps 445 | cp "$fixturesfilepath"/valid_app $validappfilepath 446 | cp "$fixturesfilepath"/valid_app $superappfilepath 447 | sed 's/valid/super/g' $superappfilepath 448 | 449 | sdd install valid_app super_app 450 | 451 | # Bump version numbers 452 | sed -i 's/1.0/2.0/' $validappfilepath 453 | sed -i 's/1.0/1.5/' $superappfilepath 454 | 455 | run sdd list --upgradable 456 | assert_success 457 | assert_line -n 0 'super_app (1.0 -> 1.5)' 458 | assert_line -n 1 'valid_app (1.0 -> 2.0)' 459 | assert_equal ${#lines[@]} 2 460 | } 461 | -------------------------------------------------------------------------------- /test/framework/bootstrap.bats: -------------------------------------------------------------------------------- 1 | teardown() { 2 | rm -rf $HOME/.local/bin/sdd $HOME/.local/lib/sdd 3 | rm -rf /usr/bin/sdd /usr/lib/sdd 4 | } 5 | 6 | @test "Default sdd installation succeeds" { 7 | [ ! -e "$HOME/.local/share/sdd/apps/installed" ] 8 | 9 | run "$BATS_TEST_DIRNAME"/../../bootstrap.sh 10 | [ $status -eq 0 ] 11 | 12 | [ -e "$HOME/.local/bin/sdd" ] 13 | 14 | run grep '^sdd=' "$HOME/.local/share/sdd/apps/installed" 15 | [ $status -eq 0 ] 16 | 17 | run "$HOME/.local/bin/sdd" 18 | [ "$status" -eq 0 ] 19 | 20 | run grep '^v' <("$HOME/.local/bin/sdd" --version) 21 | [ "$status" -eq 0 ] 22 | } 23 | 24 | @test "Custom sdd installation succeeds" { 25 | export PREFIX=/usr 26 | run "$BATS_TEST_DIRNAME"/../../bootstrap.sh 27 | [ $status -eq 0 ] 28 | 29 | [ -e /usr/bin/sdd ] 30 | 31 | run /usr/bin/sdd 32 | [ "$status" -eq 0 ] 33 | } 34 | -------------------------------------------------------------------------------- /test/framework/fixtures/valid_app: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | apppath=$SDD_INSTALL_PREFIX/bin/valid_app 4 | 5 | sdd_install() { 6 | echo '#!/usr/bin/env bash' > "$apppath" 7 | chmod +x "$apppath" 8 | } 9 | 10 | sdd_uninstall() { 11 | rm -f "$apppath" 12 | } 13 | 14 | sdd_fetch_latest_version() { 15 | echo "1.0" 16 | } 17 | -------------------------------------------------------------------------------- /test/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Test runner for sdd test suites 4 | 5 | # Usage: ./run.sh [--debug] [--open] [test [test ...]] 6 | # Options: 7 | # --debug Attach to container after running tests 8 | # --open Attach to container instead of running tests 9 | # --style Run style check (linting and formatting) 10 | # 11 | # Arguments: 12 | # test Arbitrary bats test files; relative to repository root 13 | # If omitted, all tests are run 14 | # 15 | # Environment variables: 16 | # NO_APP_TESTS If non-empty, run only framework tests. Only 17 | # effective if no tests specified 18 | 19 | if [ "$1" = "--style" ]; then 20 | ~/.virtualenvs/sdd/bin/pre-commit run --all-files 21 | exit $? 22 | fi 23 | 24 | if ! command -v docker &>/dev/null; then 25 | # Assume container environment using repository root as working directory 26 | # e.g. in the context of DockerHub Autobuild/test 27 | bats -r test 28 | exit $? 29 | fi 30 | 31 | SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 32 | ROOT_DIR="$( cd "$SCRIPT_DIR"/.. && pwd )" 33 | 34 | container_id=$(docker run -d -it --rm --volume "$ROOT_DIR":/opt/sdd --workdir /opt/sdd pylipp/sdd:latest) 35 | 36 | if [ "$1" = "--open" ]; then 37 | docker attach "$container_id" 38 | exit 39 | fi 40 | 41 | DEBUG=false 42 | if [ "$1" = "--debug" ]; then 43 | DEBUG=true 44 | shift 45 | fi 46 | 47 | if [ $# -gt 0 ]; then 48 | docker exec "$container_id" bats -r "$@" 49 | elif [[ -z $NO_APP_TESTS ]]; then 50 | docker exec "$container_id" bats -r test 51 | else 52 | docker exec "$container_id" bats -r test/framework 53 | fi 54 | 55 | test_outcome=$? 56 | 57 | if $DEBUG; then 58 | docker attach "$container_id" 59 | else 60 | docker kill "$container_id" 61 | fi 62 | 63 | exit $test_outcome 64 | -------------------------------------------------------------------------------- /test/setup/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:stretch 2 | 3 | ENV LANG C.UTF-8 4 | ENV LC_ALL C.UTF-8 5 | 6 | RUN apt-get update && \ 7 | apt-get install --yes git wget 8 | 9 | RUN apt-get install --yes python3 python3-venv 10 | 11 | RUN git clone https://github.com/bats-core/bats-core.git && \ 12 | cd bats-core && \ 13 | git checkout v1.1.0 && \ 14 | ./install.sh /usr/local && \ 15 | cd .. && \ 16 | rm -rf bats-core 17 | 18 | RUN git clone https://github.com/ztombol/bats-support /usr/local/libexec/bats-support && \ 19 | cd /usr/local/libexec/bats-support && \ 20 | git checkout v0.3.0 21 | 22 | RUN git clone https://github.com/jasonkarns/bats-assert-1 /usr/local/libexec/bats-assert && \ 23 | cd /usr/local/libexec/bats-assert && \ 24 | git checkout v2.0.0 25 | 26 | RUN version=v0.7.0 && \ 27 | package=shellcheck-$version && \ 28 | archive=$package.linux.x86_64.tar.xz && \ 29 | wget -P /tmp https://github.com/koalaman/shellcheck/releases/download/$version/$archive && \ 30 | cd /tmp && \ 31 | tar xf $archive && \ 32 | mv $package/shellcheck /opt && \ 33 | rm -rf $archive $package 34 | 35 | ENV PATH=/root/.local/bin:/opt/sdd/bin:$PATH 36 | 37 | RUN echo 'set -o vi' >> /root/.bashrc 38 | 39 | CMD ["/bin/bash"] 40 | -------------------------------------------------------------------------------- /test/setup/docker-compose.test.yml: -------------------------------------------------------------------------------- 1 | sut: 2 | build: . 3 | working_dir: /opt/sdd 4 | command: ./test/run.sh 5 | volumes: 6 | - ./../../:/opt/sdd/ 7 | -------------------------------------------------------------------------------- /test/setup/pre-requirements.txt: -------------------------------------------------------------------------------- 1 | pip==21.1 2 | setuptools 3 | wheel 4 | -------------------------------------------------------------------------------- /test/setup/requirements.txt: -------------------------------------------------------------------------------- 1 | pre-commit==1.21.0 2 | -------------------------------------------------------------------------------- /test/setup/venv: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" 4 | VENV_DIR=~/.virtualenvs/sdd 5 | 6 | rm -rf "$VENV_DIR" 7 | python3 -m venv "$VENV_DIR" 8 | # shellcheck disable=SC1090 9 | source "$VENV_DIR"/bin/activate 10 | pip install -U -r "$SCRIPTDIR"/pre-requirements.txt 11 | pip install -U -r "$SCRIPTDIR"/requirements.txt 12 | pre-commit install -f 13 | --------------------------------------------------------------------------------