├── .editorconfig ├── .github └── workflows │ ├── lint.yaml │ └── main.yaml ├── .gitignore ├── CHANGELOG.md ├── Makefile ├── PKGBUILD ├── README.md ├── README.sh ├── UNLICENSE ├── VERSION └── sysz /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | indent_style = space 3 | indent_size = 2 4 | 5 | # shfmt options 6 | # https://github.com/mvdan/sh/blob/master/cmd/shfmt/shfmt.1.scd 7 | shell_variant = bash 8 | switch_case_indent = false 9 | -------------------------------------------------------------------------------- /.github/workflows/lint.yaml: -------------------------------------------------------------------------------- 1 | name: lint 2 | on: [push, pull_request] 3 | jobs: 4 | build: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - name: Checkout 8 | uses: actions/checkout@v2 9 | 10 | - name: Run sh-checker 11 | uses: luizm/action-sh-checker@master 12 | with: 13 | sh_checker_exclude: "README.sh" 14 | -------------------------------------------------------------------------------- /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | name: Main 2 | 3 | on: release 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout 10 | uses: actions/checkout@v2 11 | - name: Release 12 | uses: softprops/action-gh-release@v1 13 | if: startsWith(github.ref, 'refs/tags/') 14 | with: 15 | files: sysz 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | pkg/ 2 | src/ 3 | *.tar.zst 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [1.4.3] - 2021-10-11 9 | 10 | ### Fixed 11 | 12 | - Ensure that fzf uses bash when calling preview by settings SHELL (#15) 13 | 14 | ## [1.4.1] - 2021-10-08 15 | 16 | ### Fixed 17 | 18 | - Ensure that fzf uses bash when calling preview 19 | 20 | ## [1.4.0] - 2021-09-21 21 | 22 | ### Added 23 | 24 | - Major Refactor 25 | - Better ctrl-s and ctrl-r handling 26 | - Better handling of fzf exit codes 27 | - Wider preview window on command prompt 28 | 29 | ### Fixed 30 | 31 | - Remove duplicates 32 | 33 | ### Removed 34 | 35 | - `--reverse`. User can set this using `FZF_DEFAULT_OPTS`. 36 | 37 | ## [1.3.1] - 2021-09-21 38 | 39 | ### Fixed 40 | 41 | - Require fzf >= 0.27.1 42 | - Unbind ctrl-v in state and daemon-reload prompts 43 | 44 | ## [1.3.0] - 2021-09-20 45 | 46 | ### Added 47 | 48 | - `cat` command 49 | - `mask` command 50 | - `unmask` command 51 | - color results based on state 52 | - `ctrl-s` to filter states 53 | - `ctrl-r` to run daemon-reload 54 | - `?` to show keybindings 55 | 56 | ### Fixed 57 | 58 | - Do not run `status` after `show` 59 | 60 | ## [1.2.3] - 2021-09-20 61 | 62 | ### Fixed 63 | 64 | - use `#!/usr/bin/env bash` 65 | - `-s` option is for state 66 | 67 | ### Added 68 | 69 | - `--state=` support 70 | 71 | ## [1.2.2] - 2021-09-17 72 | 73 | ### Fixed 74 | 75 | - root check 76 | 77 | ## [1.2.1] - 2021-09-17 78 | 79 | ### Fixed 80 | 81 | - Bug in "follow" action 82 | 83 | ## [1.2.0] - 2021-09-17 84 | 85 | ### Added 86 | 87 | - Version flag `-v` 88 | - "show" action 89 | 90 | ### Changed 91 | 92 | - Verbose flag to `-V` 93 | 94 | ### Fixed 95 | 96 | - "follow" action 97 | - root user doesn't have a --user instance 98 | 99 | ## [1.1.0] - 2021-09-02 100 | 101 | ### Added 102 | 103 | - Keybinding `CTRL-v` to cat the current unit in preview window 104 | - Support parametrized units: `unit@.service` 105 | 106 | ### Changed 107 | 108 | - Remove `cat` command 109 | - Show `cat` in preview for parametrized units: `unit@.service` 110 | 111 | ### Fixed 112 | 113 | - Show `start` command if unit is "failed" 114 | - Show `enable` command is unit is "static" 115 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | VERSION := $(shell cat VERSION) 2 | ARCHIVE := sysz-$(VERSION).tar.gz 3 | .PHONY: install clean release archive 4 | .ONESHELL: aur-release 5 | 6 | sysz: VERSION 7 | sed -i -e "s/^SYSZ_VERSION=.*/SYSZ_VERSION=$(VERSION)/" sysz 8 | 9 | $(ARCHIVE): sysz CHANGELOG.md README.md 10 | git archive --format=tar.gz -o $(ARCHIVE) --prefix sysz-$(VERSION)/ $(VERSION) 11 | 12 | clean: 13 | /bin/rm -f README.md 14 | 15 | README.md: README.sh sysz VERSION 16 | ./README.sh 17 | 18 | archive: $(ARCHIVE) 19 | 20 | PKGBUILD: VERSION $(ARCHIVE) 21 | sed -i -e "s/^pkgver=.*/pkgver=$(VERSION)/" PKGBUILD 22 | sed -i -e "s/^sha256sums=.*/sha256sums=('`sha256sum $(ARCHIVE) | cut -d' ' -f1`')/" PKGBUILD 23 | makepkg -f 24 | 25 | aur-release: PKGBUILD 26 | git commit -am 'Update PKGBUILD' 27 | git push origin master 28 | cp PKGBUILD ~/src/aur/sysz/PKGBUILD 29 | cd ~/src/aur/sysz/ 30 | makepkg -ci 31 | git commit -am "Release $(VERSION)" 32 | git push origin master 33 | 34 | github-release: VERSION sysz CHANGELOG.md README.md 35 | git commit -am 'Release $(VERSION)' 36 | git tag $(VERSION) 37 | git push origin $(VERSION) 38 | 39 | release: clean sysz README.md github-release 40 | 41 | 42 | install: 43 | install -m755 sysz /usr/local/bin/ 44 | -------------------------------------------------------------------------------- /PKGBUILD: -------------------------------------------------------------------------------- 1 | # Maintainer: Joe Hillenbrand (joehillen) 2 | 3 | pkgname=sysz 4 | pkgver=1.4.3 5 | pkgrel=0 6 | pkgdesc="fzf terminal UI for systemctl" 7 | arch=("any") 8 | url="https://github.com/joehillen/sysz" 9 | license=("UNLICENSE") 10 | depends=("bash" "fzf") 11 | source=("$pkgname-$pkgver.tar.gz::$url/archive/refs/tags/$pkgver.tar.gz") 12 | sha256sums=('186a650a1539005749df7b0c186328acc7a18bd7549d1a6279c26044d67ec0ef') 13 | 14 | package() { 15 | install -Dm775 "$srcdir/$pkgname-$pkgver/sysz" "$pkgdir/usr/bin/sysz" 16 | } 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [sysz](https://github.com/joehillen/sysz) 2 | 3 | A [fzf](https://github.com/junegunn/fzf) terminal UI for systemctl 4 | 5 | Console - Developer Tool of the Week 6 | 7 | # Demo 8 | 9 | [![asciicast](https://asciinema.org/a/BLsJz73uF7DdQj7FVGqLPhqCa.svg)](https://asciinema.org/a/BLsJz73uF7DdQj7FVGqLPhqCa) 10 | 11 | # Features 12 | 13 | VERSION: 1.4.3 14 | 15 | - See and filter both system and user units simultaneously. 16 | - Supports all unit types. 17 | - Units ordered by service, timer, socket, and the rest. 18 | - Runs `sudo` automatically and only if necessary. 19 | - Filter units by state using `ctrl-s` or the `--state` option. 20 | - Run `daemon-reload` with `ctrl-r`. 21 | - Has short versions of systemctl commands to reduce typing. 22 | - Runs status after other commands (start, stop, restart, etc). 23 | - Select multiple units, states, and commands using `TAB`. 24 | - Only prompts commands based on current state 25 | (e.g. show "start" only if the unit is inactive). 26 | 27 | # Requirements 28 | 29 | - [fzf](https://github.com/junegunn/fzf) >= [0.27.1](https://github.com/junegunn/fzf/blob/master/CHANGELOG.md#0244) 30 | - bash > 4.3 (released 2009) 31 | - awk 32 | 33 | # Installation 34 | 35 | ## Arch Linux 36 | 37 | ``` 38 | paru -S sysz 39 | ``` 40 | 41 | ## NixOS 42 | 43 | ``` 44 | nix-env -iA nixos.sysz 45 | ``` 46 | 47 | ## Using Nix 48 | 49 | ``` 50 | nix-env -iA nixpkgs.sysz 51 | ``` 52 | 53 | ## Using [`bin`](https://github.com/marcosnils/bin) 54 | 55 | ``` 56 | bin install https://github.com/joehillen/sysz 57 | ``` 58 | 59 | ## Direct Download 60 | 61 | ```sh 62 | wget -O ~/.bin/sysz https://github.com/joehillen/sysz/releases/latest/download/sysz 63 | chmod +x ~/.bin/sysz 64 | ``` 65 | 66 | ## From Source 67 | 68 | ```sh 69 | git clone https://github.com/joehillen/sysz.git 70 | cd sysz 71 | sudo make install # /usr/local/bin/sysz 72 | ``` 73 | 74 | # Usage 75 | 76 | ```text 77 | A utility for using systemctl interactively via fzf. 78 | 79 | Usage: sysz [OPTS...] [CMD] [-- ARGS...] 80 | 81 | sudo is invoked automatically, if necessary. 82 | 83 | If only one unit is chosen, available commands will be presented 84 | based on the state of the unit (e.g. "start" only shows if unit is "active"). 85 | 86 | OPTS: 87 | -u, --user Only show --user units 88 | --sys, --system Only show --system units 89 | -s STATE, --state STATE Only show units in STATE (repeatable) 90 | -V, --verbose Print the systemctl command 91 | -v, --version Print the version 92 | -h, --help Print this message 93 | 94 | If no options are given, both system and user units are shown. 95 | 96 | CMD: 97 | start systemctl start 98 | stop systemctl stop 99 | r, restart systemctl restart 100 | s, stat, status systemctl status 101 | ed, edit systemctl edit 102 | reload systemctl reload 103 | en, enable systemctl enable 104 | d, dis, disable systemctl disable 105 | c, cat systemctl cat 106 | 107 | If no command is given, one or more can be chosen interactively. 108 | 109 | ARGS are passed to the systemctl command for each selected unit. 110 | 111 | Keybindings: 112 | TAB Toggle selection. 113 | ctrl-v 'cat' the unit in the preview window. 114 | ctrl-s Select states to match. Selection is reset. 115 | ctrl-r Run daemon-reload. Selection is reset. 116 | ctrl-p History previous. 117 | ctrl-n History next. 118 | ? Show keybindings. 119 | 120 | History: 121 | sysz is stored in $XDG_CACHE_HOME/sysz/history 122 | This can be changed with the environment variable: SYSZ_HISTORY 123 | 124 | Some units are colored based on state: 125 | green active 126 | red failed 127 | yellow not-found 128 | 129 | Examples: 130 | sysz -u User units 131 | sysz --sys -s active Active system units 132 | sysz --user --state failed Failed user units 133 | 134 | Examples with commands: 135 | sysz start Start a unit 136 | sysz --sys s Get the status of system units 137 | sysz --user edit Edit user units 138 | sysz s -- -n100 Show status with 100 log lines 139 | sysz --sys -s active stop Stop an active system unit 140 | sysz -u --state failed r Restart failed user units 141 | ``` 142 | 143 | # Acknowledgements 144 | 145 | Inspired by [fuzzy-sys](https://github.com/NullSense/fuzzy-sys) by [NullSense](https://github.com/NullSense/) 146 | 147 | Thank you for [ShellCheck](https://github.com/koalaman/shellcheck) without which this would be a buggy mess. 148 | -------------------------------------------------------------------------------- /README.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | BLOCK='```' 3 | 4 | cat <README.md 5 | # [sysz](https://github.com/joehillen/sysz) 6 | 7 | A [fzf](https://github.com/junegunn/fzf) terminal UI for systemctl 8 | 9 | # Demo 10 | 11 | [![asciicast](https://asciinema.org/a/BLsJz73uF7DdQj7FVGqLPhqCa.svg)](https://asciinema.org/a/BLsJz73uF7DdQj7FVGqLPhqCa) 12 | 13 | # Features 14 | 15 | VERSION: $(cat VERSION) 16 | 17 | - See and filter both system and user units simultaneously. 18 | - Supports all unit types. 19 | - Units ordered by service, timer, socket, and the rest. 20 | - Runs \`sudo\` automatically and only if necessary. 21 | - Filter units by state using \`ctrl-s\` or the \`--state\` option. 22 | - Run \`daemon-reload\` with \`ctrl-r\`. 23 | - Has short versions of systemctl commands to reduce typing. 24 | - Runs status after other commands (start, stop, restart, etc). 25 | - Select multiple units, states, and commands using \`TAB\`. 26 | - Only prompts commands based on current state 27 | (e.g. show "start" only if the unit is inactive). 28 | 29 | # Requirements 30 | 31 | - [fzf](https://github.com/junegunn/fzf) >= [0.27.1](https://github.com/junegunn/fzf/blob/master/CHANGELOG.md#0244) 32 | - bash > 4.3 (released 2009) 33 | - awk 34 | 35 | # Installation 36 | 37 | ## Arch Linux 38 | 39 | ${BLOCK} 40 | paru -S sysz 41 | ${BLOCK} 42 | 43 | ## NixOS 44 | 45 | ${BLOCK} 46 | nix-env -iA nixos.sysz 47 | ${BLOCK} 48 | 49 | ## Using Nix 50 | 51 | ${BLOCK} 52 | nix-env -iA nixpkgs.sysz 53 | ${BLOCK} 54 | 55 | ## Using [\`bin\`](https://github.com/marcosnils/bin) 56 | 57 | ${BLOCK} 58 | bin install https://github.com/joehillen/sysz 59 | ${BLOCK} 60 | 61 | ## Direct Download 62 | 63 | ${BLOCK}sh 64 | wget -O ~/.bin/sysz https://github.com/joehillen/sysz/releases/latest/download/sysz 65 | chmod +x ~/.bin/sysz 66 | ${BLOCK} 67 | 68 | ## From Source 69 | 70 | ${BLOCK}sh 71 | git clone https://github.com/joehillen/sysz.git 72 | cd sysz 73 | sudo make install # /usr/local/bin/sysz 74 | ${BLOCK} 75 | 76 | # Usage 77 | 78 | ${BLOCK}text 79 | $(./sysz -h 2>&1 | sed -e 's:/home/[a-z]\+/.cache:$XDG_CACHE_HOME:') 80 | ${BLOCK} 81 | 82 | # Acknowledgements 83 | 84 | Inspired by [fuzzy-sys](https://github.com/NullSense/fuzzy-sys) by [NullSense](https://github.com/NullSense/) 85 | 86 | Thank you for [ShellCheck](https://github.com/koalaman/shellcheck) without which this would be a buggy mess. 87 | EOF 88 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 1.4.3 2 | -------------------------------------------------------------------------------- /sysz: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -o pipefail 4 | shopt -s lastpipe 5 | shopt -s extglob 6 | 7 | export SHELL=bash 8 | 9 | PROG=$(basename "$0") 10 | SYSZ_VERSION=1.4.3 11 | SYSZ_HISTORY=${SYSZ_HISTORY:-${XDG_CACHE_HOME:-~/.cache}/sysz/history} 12 | declare -a STATES 13 | 14 | _sysz_keys() { 15 | cat <&2 < 50 | stop systemctl stop 51 | r, restart systemctl restart 52 | s, stat, status systemctl status 53 | ed, edit systemctl edit 54 | reload systemctl reload 55 | en, enable systemctl enable 56 | d, dis, disable systemctl disable 57 | c, cat systemctl cat 58 | 59 | If no command is given, one or more can be chosen interactively. 60 | 61 | ARGS are passed to the systemctl command for each selected unit. 62 | 63 | $(_sysz_keys) 64 | 65 | History: 66 | $PROG is stored in $SYSZ_HISTORY 67 | This can be changed with the environment variable: SYSZ_HISTORY 68 | 69 | Some units are colored based on state: 70 | green active 71 | red failed 72 | yellow not-found 73 | 74 | Examples: 75 | $PROG -u User units 76 | $PROG --sys -s active Active system units 77 | $PROG --user --state failed Failed user units 78 | 79 | Examples with commands: 80 | $PROG start Start a unit 81 | $PROG --sys s Get the status of system units 82 | $PROG --user edit Edit user units 83 | $PROG s -- -n100 Show status with 100 log lines 84 | $PROG --sys -s active stop Stop an active system unit 85 | $PROG -u --state failed r Restart failed user units 86 | EOF 87 | 88 | exit 0 89 | } 90 | 91 | _sysz_run() { 92 | [[ $VERBOSE = true ]] && echo '>' "$@" >&2 93 | eval "$@" || return $? 94 | } 95 | 96 | _sysz_systemctl() { 97 | if [[ $EUID -ne 0 && $1 = --system ]]; then 98 | # only run sudo if we aren't root and it's a system unit 99 | _sysz_run sudo systemctl "$@" 100 | else 101 | _sysz_run systemctl "$@" 102 | fi 103 | } 104 | 105 | _sysz_journalctl() { 106 | if [[ $1 = --user ]]; then 107 | # use --user-unit flag if it's a user unit 108 | _sysz_run journalctl --user-unit="$2" "${@:3}" 109 | else 110 | if [[ $EUID -ne 0 ]]; then 111 | # only run sudo if we aren't root 112 | _sysz_run sudo journalctl --unit="$2" "${@:3}" 113 | else 114 | _sysz_run journalctl --unit="$2" "${@:3}" 115 | fi 116 | fi 117 | 118 | } 119 | 120 | _sysz_manager() { 121 | case ${1%% *} in 122 | '[user]') 123 | echo --user 124 | ;; 125 | '[system]') 126 | echo --system 127 | ;; 128 | *) 129 | echo "ERROR: Unknown manager: $1" >&2 130 | exit 1 131 | ;; 132 | esac 133 | } 134 | 135 | _fzf_cat() { 136 | local MANAGER 137 | MANAGER=$(_sysz_manager "$1") 138 | local UNIT 139 | UNIT=${1##* } 140 | 141 | SYSTEMD_COLORS=1 systemctl "$MANAGER" cat -- "$UNIT" 142 | } 143 | 144 | _fzf_preview() { 145 | local MANAGER 146 | MANAGER=$(_sysz_manager "$1") 147 | local UNIT 148 | UNIT=${1##* } 149 | 150 | if [[ $UNIT = *@.* ]]; then 151 | _fzf_cat "$@" 152 | else 153 | SYSTEMD_COLORS=1 systemctl "$MANAGER" status --no-pager -- "$UNIT" 154 | fi 155 | exit 0 156 | } 157 | 158 | _sysz_show() { 159 | local manager 160 | manager=$(_sysz_manager "$1") 161 | local unit 162 | unit=${1##* } 163 | 164 | _sysz_systemctl "$manager" show "$unit" -p "$2" --value 165 | } 166 | 167 | _sysz_sort() { 168 | local str 169 | local mgr 170 | local unit 171 | local n 172 | while IFS= read -r str; do 173 | mgr=${str%% *} 174 | unit_colored=${str##* } 175 | unit=${unit_colored//$'\e'[\[(]*([0-9;])[@-n]/} 176 | 177 | if [[ $unit =~ \.service$ ]]; then 178 | n=0 179 | [[ $mgr = "[system]" ]] && n=1 180 | elif [[ $unit =~ \.timer$ ]]; then 181 | n=2 182 | [[ $mgr = "[system]" ]] && n=3 183 | elif [[ $unit =~ \.socket$ ]]; then 184 | n=4 185 | [[ $mgr = "[system]" ]] && n=5 186 | elif [[ $mgr = "[user]" ]]; then 187 | n=6 188 | else 189 | # then the rest based on file extension 190 | n=7 191 | fi 192 | type=${unit##*.} 193 | unit_undashed=${unit//-/} 194 | echo "$n$type$unit_undashed $mgr $unit_colored" 195 | done | sort -bifu | cut -d' ' -f2- 196 | } 197 | 198 | _sysz_list() { 199 | local args 200 | declare -a args 201 | args=( 202 | --all 203 | --no-legend 204 | --full 205 | --plain 206 | --no-pager 207 | "${STATES[@]}" 208 | "$@" 209 | ) 210 | ( 211 | systemctl list-units "${args[@]}" 212 | systemctl list-unit-files "${args[@]}" 213 | ) | sort -u -t ' ' -k1,1 | 214 | while read -r line; do 215 | unit=${line%% *} 216 | if [[ $line = *" active "* ]]; then 217 | printf '\033[0;32m%s\033[0m\n' "$unit" # green 218 | elif [[ $line = *" failed "* ]]; then 219 | printf '\033[0;31m%s\033[0m\n' "$unit" # red 220 | elif [[ $line = *" not-found "* ]]; then 221 | printf '\033[1;33m%s\033[0m\n' "$unit" # red 222 | else 223 | echo "$unit" 224 | fi 225 | done 226 | } 227 | 228 | _sysz_list_units() { 229 | for MANAGER in "${MANAGERS[@]}"; do 230 | _sysz_list "--$MANAGER" | sed -e "s/^/[$MANAGER] /" 231 | done | _sysz_sort 232 | } 233 | 234 | # main 235 | 236 | # check fzf version 237 | MIN_FZF=0.27.1 238 | if [[ "$(printf '%s\n' "$MIN_FZF" "$(fzf --version | cut -d' ' -f1)" | sort -V | head -n1)" != "$MIN_FZF" ]]; then 239 | echo "ERROR: fzf >= $MIN_FZF required" >&2 240 | echo "https://github.com/junegunn/fzf#upgrading-fzf" >&2 241 | exit 1 242 | fi 243 | 244 | # root doesn't have user units 245 | if [[ $EUID -eq 0 ]]; then 246 | MANAGERS=(system) 247 | else 248 | MANAGERS=(user system) 249 | fi 250 | 251 | declare -a STATES 252 | while [[ -n $1 ]]; do 253 | case $1 in 254 | -u | --user) 255 | MANAGERS=(user) 256 | shift 257 | ;; 258 | --sys | --system) 259 | MANAGERS=(system) 260 | shift 261 | ;; 262 | -s | --state) 263 | STATES+=("--state=$2") 264 | shift 265 | shift 266 | ;; 267 | --state=*) 268 | STATES+=("$1") 269 | shift 270 | ;; 271 | -v | --version) 272 | echo "$PROG" $SYSZ_VERSION 273 | exit 0 274 | ;; 275 | -V | --verbose) 276 | VERBOSE=true 277 | shift 278 | ;; 279 | -h | --help) 280 | _sysz_help 281 | ;; 282 | *) 283 | break 284 | ;; 285 | esac 286 | done 287 | 288 | for STATE in "${STATES[@]}"; do 289 | STATE="${STATE##*=}" 290 | if [[ -n $STATE ]] && ! systemctl --state=help | grep -q "^${STATE}$"; then 291 | echo "ERROR: Invalid state: $STATE" >&2 292 | exit 1 293 | fi 294 | done 295 | 296 | declare CMD 297 | declare -a ARGS 298 | while [[ -n $1 ]]; do 299 | case $1 in 300 | _fzf_preview) 301 | shift 302 | _fzf_preview "$@" 303 | ;; 304 | _fzf_cat) 305 | shift 306 | _fzf_cat "$@" 307 | ;; 308 | h | help) 309 | _sysz_help 310 | ;; 311 | # Handle short names 312 | re) 313 | CMD=restart 314 | ;; 315 | s) 316 | CMD=status 317 | ;; 318 | ed) 319 | CMD=edit 320 | ;; 321 | en) 322 | CMD=enable 323 | ;; 324 | d | dis) 325 | CMD=disable 326 | ;; 327 | j) 328 | CMD=journal 329 | ;; 330 | f) 331 | CMD=follow 332 | ;; 333 | c) 334 | CMD="cat" 335 | ;; 336 | --) 337 | shift 338 | ARGS=("$@") 339 | break 340 | ;; 341 | -*) 342 | echo "ERROR: Unknown option: $1" 2>&1 343 | exit 1 344 | ;; 345 | *) 346 | # assume the next argument is a command name 347 | CMD=$1 348 | ;; 349 | esac 350 | shift 351 | done 352 | 353 | mkdir -p "$(dirname "$SYSZ_HISTORY")" 354 | touch "$SYSZ_HISTORY" 355 | 356 | function join_by { 357 | # https://stackoverflow.com/a/17841619/334632 358 | local d=${1-} f=${2-} 359 | if shift 2; then 360 | printf %s "$f" "${@/#/$d}" 361 | fi 362 | } 363 | 364 | _sysz_daemon_reload() { 365 | ( 366 | if [[ $EUID -ne 0 ]]; then 367 | echo '[system] daemon-reload' 368 | fi 369 | echo '[user] daemon-reload' 370 | ) | 371 | fzf \ 372 | --multi \ 373 | --no-info \ 374 | --prompt="Reload: " | 375 | readarray -t RELOADS || exit $? 376 | 377 | for RELOAD in "${RELOADS[@]}"; do 378 | case $RELOAD in 379 | '[user] daemon-reload') 380 | _sysz_systemctl --user daemon-reload >&2 381 | ;; 382 | '[system] daemon-reload') 383 | _sysz_systemctl --system daemon-reload >&2 384 | ;; 385 | esac 386 | done 387 | } 388 | 389 | _sysz_states() { 390 | # hide 'ing' because they are transient states 391 | # which people probably aren't looking for 392 | systemctl --state=help | 393 | grep -v ':' | 394 | grep -v 'ing' | 395 | sort -u | 396 | grep -v '^$' | 397 | fzf \ 398 | --multi \ 399 | --prompt="States: " | 400 | readarray -t PICKED_STATES || exit $? 401 | 402 | if [[ ${#PICKED_STATES[@]} -gt 0 ]]; then 403 | STATES=() 404 | fi 405 | 406 | for STATE in "${PICKED_STATES[@]}"; do 407 | STATES+=("--state=$STATE") 408 | done 409 | } 410 | 411 | while :; do 412 | UNITS=() 413 | KEY= 414 | 415 | # prompt units 416 | _sysz_list_units | 417 | fzf \ 418 | --multi \ 419 | --ansi \ 420 | --expect=ctrl-r,ctrl-s \ 421 | --history="$SYSZ_HISTORY" \ 422 | --prompt="Units: " \ 423 | --header '? for keybindings' \ 424 | --bind "?:preview(echo '$(_sysz_keys)')" \ 425 | --bind "ctrl-v:preview('${BASH_SOURCE[0]}' _fzf_cat {})" \ 426 | --preview="'${BASH_SOURCE[0]}' _fzf_preview {}" \ 427 | --preview-window=70% | 428 | readarray -t PICKS 429 | 430 | KEY=${PICKS[0]} 431 | [[ $VERBOSE = true ]] && echo "KEY: $KEY" >&2 432 | UNITS=("${PICKS[@]:1}") 433 | 434 | case $KEY in 435 | ctrl-r) 436 | _sysz_daemon_reload 437 | continue 438 | ;; 439 | ctrl-s) 440 | _sysz_states 441 | continue 442 | ;; 443 | esac 444 | 445 | if [[ ${#UNITS[@]} -eq 0 ]]; then 446 | exit 1 447 | fi 448 | 449 | break 450 | 451 | done 452 | 453 | [[ $VERBOSE = true ]] && printf 'UNIT: %s\n' "${UNITS[@]}" >&2 454 | 455 | declare -a CMDS 456 | if [[ -n $CMD ]]; then 457 | CMDS=("$CMD") 458 | else 459 | 460 | if [[ ${#UNITS[@]} -gt 1 ]]; then 461 | printf -v PREVIEW '%s\n' "${UNITS[@]}" 462 | PREVIEW_CMD="echo -n '$PREVIEW'" 463 | MULTI=true 464 | else 465 | UNIT=${UNITS[0]} 466 | 467 | if [[ $UNIT = *@.* ]]; then 468 | read -r -p "$UNIT requires a parameter: " PARAM || 469 | if [[ -z $PARAM ]]; then 470 | echo "ERROR: $UNIT requires a parameter" 471 | exit 1 472 | fi 473 | 474 | UNIT=${UNIT/\@/\@${PARAM}} 475 | UNITS[0]=$UNIT 476 | fi 477 | 478 | ACTIVE_STATE=$(_sysz_show "$UNIT" ActiveState) 479 | LOAD_STATE=$(_sysz_show "$UNIT" LoadState) 480 | UNIT_FILE_STATE=$(_sysz_show "$UNIT" UnitFileState) 481 | CAN_RELOAD=$(_sysz_show "$UNIT" CanReload) 482 | PREVIEW_CMD="'${BASH_SOURCE[0]}' _fzf_preview '$UNIT'" 483 | fi 484 | 485 | # prompt commands 486 | fzf \ 487 | --multi \ 488 | --ansi \ 489 | --no-info \ 490 | --prompt="Commands: " \ 491 | --preview="$PREVIEW_CMD" \ 492 | --preview-window=80% < <( 493 | # status 494 | echo status "${ARGS[*]}" 495 | # restart 496 | [[ $MULTI = true || $ACTIVE_STATE = active ]] && 497 | printf '\033[0;31m%s\033[0m %s\n' restart "${ARGS[*]}" # red 498 | # start 499 | [[ $MULTI = true || $ACTIVE_STATE != active ]] && 500 | printf '\033[0;32m%s\033[0m %s\n' start "${ARGS[*]}" # green 501 | # stop 502 | [[ $MULTI = true || $ACTIVE_STATE = active ]] && 503 | printf '\033[0;31m%s\033[0m %s\n' stop "${ARGS[*]}" # red 504 | # enable 505 | [[ $MULTI = true || $UNIT_FILE_STATE != enabled ]] && 506 | { 507 | printf '\033[0;32m%s\033[0m %s\n' "enable" "${ARGS[*]}" # green 508 | printf '\033[0;32m%s\033[0m %s\n' "enable" "--now ${ARGS[*]}" 509 | } 510 | # disable 511 | [[ $MULTI = true || $UNIT_FILE_STATE = enabled ]] && 512 | { 513 | printf '\033[0;31m%s\033[0m %s\n' disable "${ARGS[*]}" # red 514 | printf '\033[0;31m%s\033[0m %s\n' disable "--now ${ARGS[*]}" 515 | } 516 | 517 | # journal 518 | echo journal "${ARGS[*]}" 519 | echo follow "${ARGS[*]}" 520 | 521 | # reload 522 | [[ $MULTI = true || $CAN_RELOAD = yes ]] && 523 | printf '\033[0;37m%s\033[0m %s\n' reload "${ARGS[*]}" # green 524 | 525 | # mask 526 | [[ $MULTI = true || ($UNIT_FILE_STATE != masked && $LOAD_STATE != masked) ]] && 527 | printf '\033[0;31m%s\033[0m %s\n' mask "${ARGS[*]}" # red 528 | [[ $MULTI = true || $UNIT_FILE_STATE = masked || $LOAD_STATE = masked ]] && 529 | printf '\033[0;32m%s\033[0m %s\n' unmask "${ARGS[*]}" # green 530 | 531 | # cat 532 | echo cat "${ARGS[*]}" 533 | # edit 534 | echo edit "${ARGS[*]}" 535 | # show 536 | echo show "${ARGS[*]}" 537 | ) | 538 | readarray -t CMDS || exit $? 539 | fi 540 | 541 | if [[ ${#CMDS[@]} -eq 0 ]]; then 542 | exit 1 543 | fi 544 | 545 | for PICK in "${UNITS[@]}"; do 546 | 547 | MANAGER=$(_sysz_manager "$PICK") 548 | UNIT=${PICK##* } 549 | 550 | for CMD in "${CMDS[@]}"; do 551 | case ${CMD%% *} in 552 | journal) 553 | _sysz_journalctl "$MANAGER" "$UNIT" -xe "${ARGS[@]}" 554 | ;; 555 | follow) 556 | _sysz_journalctl "$MANAGER" "$UNIT" -xef "${ARGS[@]}" 557 | ;; 558 | status) 559 | # shellcheck disable=2086 560 | SYSTEMD_COLORS=1 systemctl "$MANAGER" $CMD --no-pager "${ARGS[@]}" -- "$UNIT" 561 | ;; 562 | cat | show) 563 | _sysz_systemctl "$MANAGER" "$CMD" "${ARGS[@]}" -- "$UNIT" || exit $? 564 | ;; 565 | *) 566 | # shellcheck disable=2086 567 | _sysz_systemctl "$MANAGER" $CMD "${ARGS[@]}" -- "$UNIT" || CODE=$? 568 | SYSTEMD_COLORS=1 systemctl "$MANAGER" status --no-pager -- "$UNIT" 569 | if [[ ${#UNITS[@]} -eq 1 ]]; then 570 | exit $CODE 571 | fi 572 | ;; 573 | esac 574 | done 575 | done 576 | --------------------------------------------------------------------------------