diff --git a/README.md b/README.md index e884355..d290b53 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,28 @@ -# My Ubuntu environment +# My shell environment -`make`-ready bunch of scripts for easily installation of different software. +`make`-ready bunch of scripts for easily (de)installation of different software and bunch of useful handy functions for custom scripting. ## Requirements * Ubuntu >= 20.04 (not tested with version < 20) * `bash`, `zsh` or other `sh`-compatible shell * `make` (optional but recommended) -* `wget` (necessary for some scripts) -* `git` (necessary for some scripts) +* `wget` (required for some scripts) +* `git` (required for some scripts) -If some dependecies are missed for some of these scripts it is enougth to run `./install/apt` in most cases. +If some dependecies are missed for some of these scripts it is enougth to run `./install/apt` in most cases, otherwise script will suggest (or even install) them. ## Usage -### Clone this repo (recommended) - ```shell -# if git is installed -git clone git@git.axenov.dev:anthony/my-env.git --depth=1 +# with git +git clone git@git.axenov.dev:anthony/my-env.git --depth=1 --single-branch -# if git is not installed +# without git wget -qO - https://git.axenov.dev/anthony/my-env/archive/master.tar.gz | tar -zxf - -# switch to repo dir -cd my-env - -# generate fresh ./Makefile and get full list of `make` goals -./gen-makefile - # get full list of `make` goals -make -``` - -### Selective straightforward installation - -```shell -# from remote file (you can meet interaction bugs this way!) -wget -qO - https://git.axenov.dev/anthony/my-env/raw/branch/master/install/apt | bash - -# from locally cloned repo (except scripts from ./packs) -./install/apt +cd my-env && make ``` ## How to add my script? @@ -78,17 +60,32 @@ mypackX: goalA goalB ``` where: -* `mypack*` is the pack name +* `mypack*` is the pack name of your choice * `goal*` are script names in `./install` -## TODO +## Useful links and sources used -* build: [flameshot](https://github.com/flameshot-org/flameshot#compilation) -* build: [rustdesk](https://github.com/rustdesk/rustdesk#build) -* [JB mono](https://www.jetbrains.com/ru-ru/lp/mono/#how-to-install) ([2](https://fonts.google.com/specimen/JetBrains+Mono)) -* update scripts (when possible) -* uninstall scripts (when possible) +* https://gist.github.com/anthonyaxenov/d53c4385b7d1466e0affeb56388b1005 +* https://gist.github.com/anthonyaxenov/89c99e09ddb195985707e2b24a57257d +* ...and other my [gists](https://gist.github.com/anthonyaxenov/) with [SHELL] prefix +* https://github.com/nvie/gitflow/blob/develop/gitflow-common (BSD License) +* https://github.com/petervanderdoes/gitflow-avh/blob/develop/gitflow-common (FreeBSD License) +* https://github.com/vaniacer/bash_color/blob/master/color +* https://misc.flogisoft.com/bash/tip_colors_and_formatting +* https://www-users.york.ac.uk/~mijp1/teaching/2nd_year_Comp_Lab/guides/grep_awk_sed.pdf +* https://www.galago-project.org/specs/notification/ +* https://laurvas.ru/bash-trap/ +* https://stackoverflow.com/a/52674277 +* https://rtfm.co.ua/bash-funkciya-getopts-ispolzuem-opcii-v-skriptax/ +* https://gist.github.com/jacknlliu/7c51e0ee8b51881dc8fb2183c481992e +* https://gist.github.com/anthonyaxenov/d53c4385b7d1466e0affeb56388b1005 +* https://github.com/nvie/gitflow/blob/develop/gitflow-common +* https://github.com/petervanderdoes/gitflow-avh/blob/develop/gitflow-common +* https://gitlab.com/kyb/autorsync/-/blob/master/ +* https://lug.fh-swf.de/vim/vim-bash/StyleGuideShell.en.pdf +* https://www.thegeekstuff.com/2010/06/bash-array-tutorial/ +* https://www.distributednetworks.com/linux-network-admin/module4/ephemeral-reserved-portNumbers.php ## License -[WTFPLv2](LICENSE) +[WTFPLv2](LICENSE) but other licences are also possible. diff --git a/TODO.md b/TODO.md index 91bb068..8ebcab0 100644 --- a/TODO.md +++ b/TODO.md @@ -1,6 +1,11 @@ # Todo list -* [ ] tdesktop (https://desktop.telegram.org) -* [ ] spoofdpi (https://git.axenov.dev/mirrors/SpoofDPI/tags) -* [ ] lazynvim (https://www.lazyvim.org) -* [ ] ... +* tdesktop (https://desktop.telegram.org) +* spoofdpi (https://git.axenov.dev/mirrors/SpoofDPI/tags) +* lazynvim (https://www.lazyvim.org) +* build: [flameshot](https://github.com/flameshot-org/flameshot#compilation) +* build: [rustdesk](https://github.com/rustdesk/rustdesk#build) +* update scripts (when possible) +* uninstall scripts (when possible) +* ... + diff --git a/dotfiles/etc/docker/daemon.json b/dotfiles/etc/docker/daemon.json index 9f808e8..9f371e6 100644 --- a/dotfiles/etc/docker/daemon.json +++ b/dotfiles/etc/docker/daemon.json @@ -6,6 +6,10 @@ "https://daocloud.io", "https://c.163.com/", "https://registry.docker-cn.com", - "https://huecker.io" + "https://huecker.io", + "https://public.ecr.aws", + "https://quay.io", + "https://registry.access.redhat.com", + "https://registry.redhat.io" ] } diff --git a/helpers b/helpers deleted file mode 100644 index f834a3f..0000000 --- a/helpers +++ /dev/null @@ -1,224 +0,0 @@ -#!/bin/bash - -installed() { - command -v "$1" >/dev/null 2>&1 -} - -installed2() { - dpkg --list | grep -qw "ii $1" -} - -apt_install() { - sudo apt install -y --autoremove $* -} - -require() { - sw=() - for package in "$@"; do - if ! installed "$package" && ! installed2 "$package"; then - sw+=("$package") - fi - done - if [ ${#sw[@]} -gt 0 ]; then - info "This packages will be installed in your system:\n${sw[*]}" - apt_install ${sw[*]} - [ $? -gt 0 ] && die "installation cancelled" 201 - fi -} - -require2() { - sw=() - for package in "$@"; do - if ! installed "$package" && ! installed2 "$package"; then - sw+=("$package") - fi - done - if [ ${#sw[@]} -gt 0 ]; then - die "This packages must be installed in your system:\n${sw[*]}" 200 - fi -} - -title() { - [ "$1" ] && title="$1" || title="$(grep -m 1 -oP "(?<=^##makedesc:\s).*$" ${BASH_SOURCE[1]})" - info - info "===============================================" - info "$title" - info "===============================================" - info -} - -unpak_targz() { - require tar - tar -xzf "$1" -C "$2" -} - -symlink() { - ln -sf "$1" "$2" -} - -download() { - require wget - wget "$1" -O "$2" -} - -clone() { - require git - git clone $* -} - -clone_quick() { - require git - git clone $* --depth=1 --single-branch -} - -abspath() { - echo $(realpath -q "${1/#\~/$HOME}") -} - -is_writable() { - [ -w "$(abspath $1)" ] -} - -is_dir() { - [ -d "$(abspath $1)" ] -} - -is_file() { - [ -f "$(abspath $1)" ] -} - -is_function() { - declare -F "$1" > /dev/null -} - -regex_match() { - printf "%s" "$1" | grep -qP "$2" -} - -in_array() { - local find=$1 - shift - for e in "$@"; do - [[ "$e" == "$find" ]] && return 0 - done - return 1 -} - -implode() { - local d=${1-} - local f=${2-} - if shift 2; then - printf %s "$f" "${@/#/$d}" - fi -} - -IINFO="( i )" -INOTE="( * )" -IWARN="( # )" -IERROR="( ! )" -IFATAL="( @ )" -ISUCCESS="( ! )" -IASK="( ? )" -IDEBUG="(DBG)" -IVRB="( + )" - -BOLD="\e[1m" -DIM="\e[2m" -NOTBOLD="\e[22m" # sometimes \e[21m -NOTDIM="\e[22m" -NORMAL="\e[20m" -RESET="\e[0m" - -FRESET="\e[39m" -FBLACK="\e[30m" -FWHITE="\e[97m" -FRED="\e[31m" -FGREEN="\e[32m" -FYELLOW="\e[33m" -FBLUE="\e[34m" -FLRED="\e[91m" -FLGREEN="\e[92m" -FLYELLOW="\e[93m" -FLBLUE="\e[94m" - -BRESET="\e[49m" -BBLACK="\e[40m" -BWHITE="\e[107m" -BRED="\e[41m" -BGREEN="\e[42m" -BYELLOW="\e[43m" -BBLUE="\e[44m" -BLRED="\e[101m" -BLGREEN="\e[102m" -BLYELLOW="\e[103m" -BLBLUE="\e[104m" - -dt() { - echo "[$(date +'%H:%M:%S')] " -} - -ask() { - IFS= read -rp "$(print ${BOLD}${BBLUE}${FWHITE}${IASK}${BRESET}\ ${BOLD}$1 ): " $2 -} - -print() { - echo -e "$*${RESET}" -} - -debug() { - if [ "$2" ]; then - print "${DIM}${BOLD}${RESET}${DIM}${FUNCNAME[1]:-?}():${BASH_LINENO:-?}\t$1 " - else - print "${DIM}${BOLD}${RESET}${DIM}$1 " - fi -} - -verbose() { - print "${BOLD}${IVRB}${RESET}${FYELLOW} $1 " -} - -info() { - print "${BOLD}${FWHITE}${BLBLUE}${IINFO}${RESET}${FWHITE} $1 " -} - -note() { - print "${BOLD}${DIM}${FWHITE}${INOTE}${RESET} $1 " -} - -success() { - print "${BOLD}${BGREEN}${FWHITE}${ISUCCESS}${BRESET}$FGREEN $1 " -} - -warn() { - print "${BOLD}${BYELLOW}${FBLACK}${IWARN}${BRESET}${FYELLOW} Warning:${RESET} $1 " -} - -error() { - print "${BOLD}${BLRED}${FWHITE}${IERROR} Error: ${BRESET}${FLRED} $1 " >&2 -} - -fatal() { - print "${BOLD}${BRED}${FWHITE}${IFATAL} FATAL: $1 " >&2 - print_stacktrace -} - -die() { - error "${1:-halted}" - exit ${2:-100} -} - -print_stacktrace() { - STACK="" - local i - local stack_size=${#FUNCNAME[@]} - debug "Callstack:" - # for (( i=$stack_size-1; i>=1; i-- )); do - for (( i=1; i<$stack_size; i++ )); do - local func="${FUNCNAME[$i]}" - [ x$func = x ] && func=MAIN - local linen="${BASH_LINENO[$(( i - 1 ))]}" - local src="${BASH_SOURCE[$i]}" - [ x"$src" = x ] && src=non_file_source - debug " at $func $src:$linen" - done -} diff --git a/helpers.sh b/helpers.sh new file mode 100644 index 0000000..46bb446 --- /dev/null +++ b/helpers.sh @@ -0,0 +1,12 @@ +#!/bin/bash +source $(dirname $0)/helpers/io.sh || exit 255 +source $(dirname $0)/helpers/basic.sh || exit 255 + +title() { + [ "$1" ] && title="$1" || title="$(grep -m 1 -oP "(?<=^##makedesc:\s).*$" ${BASH_SOURCE[1]})" + info + info "===============================================" + info "$title" + info "===============================================" + info +} diff --git a/helpers/arg-parser/README.md b/helpers/arg-parser/README.md new file mode 100644 index 0000000..612f5a9 --- /dev/null +++ b/helpers/arg-parser/README.md @@ -0,0 +1,29 @@ +# Argument parser for bash scripts + +More info: +* 🇷🇺 [axenov.dev/bash-args](https://axenov.dev/bash-args/) +* 🇺🇸 [axenov.dev/en/bash-processing-arguments-in-a-script-when-called-from-the-shell/](https://axenov.dev/en/bash-processing-arguments-in-a-script-when-called-from-the-shell) + +Tested in Ubuntu 20.04.2 LTS in: + +``` +bash 5.0.17(1)-release (x86_64-pc-linux-gnu) +zsh 5.8 (x86_64-ubuntu-linux-gnu) +``` + +## Version history + +``` +v1.0 - initial +v1.1 - arg(): improved skipping uninteresting args + - arg(): check next arg to be valid value +v1.2 - removed all 'return' statements + - arg(): error message corrected + - new examples +v1.3 - argl(): improved flag check + - some text corrections +v1.4 - new function argn() + - some text corrections +v1.5 - arg(), grep_match(): fixed searching for -e argument + - grep_match(): redirect output into /dev/null +``` diff --git a/helpers/arg-parser/args.sh b/helpers/arg-parser/args.sh new file mode 100644 index 0000000..0b717eb --- /dev/null +++ b/helpers/arg-parser/args.sh @@ -0,0 +1,268 @@ +#!/bin/bash +######################################################################### +# # +# Argument parser for bash scripts # +# # +# Author: Anthony Axenov (Антон Аксенов) # +# Version: 1.5 # +# License: MIT # +# # +######################################################################### +# # +# With 'getopt' you cannot combine different # +# arguments for different nested functions. # +# # +# 'getopts' does not support long arguments with # +# values (like '--foo=bar'). # +# # +# These functions supports different arguments and # +# their combinations: # +# -a -b -c # +# -a avalue -b bvalue -c cvalue # +# -cab bvalue # +# --arg # +# --arg=value -ab -c cvalue --foo # +# # +# Tested in Ubuntu 20.04.2 LTS in: # +# bash 5.0.17(1)-release (x86_64-pc-linux-gnu) # +# zsh 5.8 (x86_64-ubuntu-linux-gnu) # +# # +######################################################################### + +#purpose Little helper to check if string matches PCRE +#argument $1 - some string +#argument $2 - regex +#exitcode 0 - string valid +#exitcode 1 - string is not valid +grep_match() { + printf "%s" "$1" | grep -qP "$2" >/dev/null +} + +#purpose Find short argument or its value +#argument $1 - (string) argument (without leading dashes; only first letter will be processed) +#argument $2 - (number) is it flag? 1 if is, otherwise 0 or nothing +#argument $3 - (string) variable to return value into +# (if not specified then it will be echo'ed in stdout) +#returns (string) 1 (if $2 == 1), value (if correct and if $2 != 1) or nothing +#usage To get value into var: arg v 0 myvar or myvalue=$(arg 'v') +#usage To find flag into var: arg f 1 myvar or flag=$(arg 'f') +#usage To echo value: arg v +#usage To echo 1 if flag exists: arg f +arg() { + local need=${1:0:1} # argument to find (only first letter) + [ $need ] || { + echo "Argument is not specified!" >&2 + exit 1 + } + local isflag=$2 || 0 # should we find the value or just the presence of the $need? + local retvar=$3 || 0 # var to return value into (if 0 then value will be echo'ed in stdout) + local args=(${__MAIN_ARGS[0]}) # args we need are stored in 1st element of __MAIN_ARGS + for ((idx=0; idx<${#args[@]}; ++idx)) do # going through args + local arg=${args[$idx]} # current argument + # skip $arg if it starts with '--', letter or digit + grep_match "$arg" "^(\w{1}|-{2})" && continue + # clear $arg from special and duplicate characters + # e.g. 'fas-)dfs' will become 'fasd' + local chars="$(printf "%s" "${arg}" | tr -s [${arg}] | tr -d "[:punct:][:blank:]")" + # now we can check if $need is one of $chars + if grep_match "-$need" "^-[$chars]$"; then # if it is + if [[ $isflag = 1 ]]; then # and we expect it as a flag + # then return '1' back into $3 (if exists) or echo in stdout + [ $retvar ] && eval "$retvar='1'" || echo "1" + else # but if $arg is not a flag + # then get next argument as value of current one + local value="${args[$idx+1]}" + # check if it is valid value + if grep_match "$value" "^\w+$"; then + # and return it back back into $3 (if exists) or echo in stdout + [ $retvar ] && eval "$retvar='$value'" || echo "$value" + break + else # otherwise throw error message into stderr (just in case) + echo "Argument '$arg' must have a correct value!" >&2 + break + fi + fi + fi + done +} + +#purpose Find long argument or its value +#argument $1 - argument (without leading dashes) +#argument $2 - is it flag? 1 if is, otherwise 0 or nothing +#argument $3 - variable to return value into +# (if not specified then it will be echo'ed in stdout) +#returns (string) 1 (if $2 == 1), value (if correct and if $2 != 1) or nothing +#usage To get value into var: arg v 0 myvar or myvalue=$(arg 'v') +#usage To find flag into var: arg f 1 myvar or flag=$(arg 'f') +#usage To echo value: arg v +#usage To echo 1 if flag exists: arg f +argl() { + local need=$1 # argument to find + [ $need ] || { + echo "Argument is not specified!" >&2 + exit 1 + } + local isflag=$2 || 0 # should we find the value or just the presence of the $need? + local retvar=$3 || 0 # var to return value into (if 0 then value will be echo'ed in stdout) + local args=(${__MAIN_ARGS[0]}) # args we need are stored in 1st element of __MAIN_ARGS + for ((idx=0; idx<${#args[@]}; ++idx)) do + local arg=${args[$idx]} # current argument + # if we expect $arg as a flag + if [[ $isflag = 1 ]]; then + # and if $arg has correct format (like '--flag') + if grep_match "$arg" "^--$need"; then + # then return '1' back into $3 (if exists) or echo in stdout + [ ! $retvar = 0 ] && eval "$retvar=1" || echo "1" + break + fi + else # but if $arg is not a flag + # check if $arg has correct format (like '--foo=bar') + if grep_match "$arg" "^--$need=.+$"; then # if it is + # then return part from '=' to arg's end as value back into $3 (if exists) or echo in stdout + [ ! $retvar = 0 ] && eval "$retvar=${arg#*=}" || echo "${arg#*=}" + break + fi + fi + done +} + +#purpose Get argument by its index +#argument $1 - (number) arg index +#argument $2 - (string) variable to return arg's name into +# (if not specified then it will be echo'ed in stdout) +#returns (string) arg name or nothing +#usage To get arg into var: argn 1 myvar or arg=$(argn 1) +#usage To echo in stdout: argn 1 +argn() { + local idx=$1 # argument index + [ $idx ] || { + error "Argument index is not specified!" + exit 1 + } + local retvar=$2 || 0 # var to return value into (if 0 then value will be echo'ed in stdout) + local args=(${__MAIN_ARGS[0]}) # args we need are stored in 1st element of __MAIN_ARGS + local arg=${args[$idx]} # current argument + if [ $arg ]; then + [ ! $retvar = 0 ] && eval "$retvar=$arg" || echo "$arg" + fi +} + +# Keep in mind: +# 1. Short arguments can be specified contiguously or separately +# and their order does not matter, but before each of them +# (or the first of them) one leading dash must be specified. +# Valid combinations: '-a -b -c', '-cba', '-b -ac' +# 2. Short arguments can have values and if are - value must go +# next to argument itself. +# Valid combinations: '-ab avalue', '-ba avalue', '-a avalue -b' +# 3. Long arguments cannot be combined like short ones and each +# of them must be specified separately with two leading dashes. +# Valid combinations: '--foo --bar', '--bar --foo' +# 4. Long arguments can have a value which must be specified after '='. +# Valid combinations: '--foo=value --bar', '--bar --foo=value' +# 5. Values cannot contain spaces even in quotes both for short and +# long args, otherwise first word will return as value. +# 6. You can use arg() or argl() to check presence of any arg, no matter +# if it has value or not. + +### USAGE ### +# This is simple examples which you can play around with. + +# first we must save the original arguments passed +# to the script when it was called: +__MAIN_ARGS=$@ + +echo -e "\n1. Short args (vars):" +arg a 1 a # -a +arg v 0 v # -v v_value +arg c 1 c # -c +arg z 1 z # -z (not exists) +echo "1.1 a=$a" +echo "1.2 v=$v" +echo "1.3 c=$c" +echo "1.4 z=$z" + +echo -e "\n2. Short args (echo):" +echo "2.1 a=$(arg a 1)" +echo "2.2 v=$(arg v 0)" +echo "2.3 c=$(arg c 1)" +echo "2.4 z=$(arg z 1)" + +echo -e "\n3. Long args (vars):" +argl flag 1 flag # --flag +argl param1 0 param1 # --param1=test +argl param2 0 param2 # --param2=password +argl bar 1 bar # --bar (not exists) +echo "3.1 flag=$flag" +echo "3.2 param1=$param1" +echo "3.3 param2=$param2" +echo "3.4 bar=$bar" + +echo -e "\n4. Long args (echo):" +echo "4.1 flag=$(argl flag 1)" +echo "4.2 param1=$(argl param1 0)" +echo "4.3 param2=$(argl param2 0)" +echo "4.4 bar=$(argl bar 1)" + +echo -e "\n5. Args by index:" +argn 1 first +echo "5.1 arg[1]=$first" +echo "5.2 arg[3]=$(argn 3)" + +# Well, now we will try to get global args inside different functions + +food() { + echo -e "\n=== food() ===" + arg f 0 food + argl 'food' 0 food + [ $food ] && echo "Om nom nom! $food is very tasty" || echo "Uh oh" >&2 +} + +hello() { + echo -e "\n=== hello() ===" + arg n 0 name + argl name 0 name + [ $name ] && echo "Hi, $name! How u r doin?" || echo "Hello, stranger..." >&2 +} + +hello +food + +### OUTPUT ### + +# Command to run: +# bash args.sh -va asdf --flag --param1=paramvalue1 -c --param2="somevalue2 sdf" --name="John" -f Seafood + +# 1. Short args (vars): +# 1.1 a=1 +# 1.2 v=v_value +# 1.3 c=1 +# 1.4 z= +# +# 2. Short args (echo): +# 2.1 a=1 +# 2.2 v=v_value +# 2.3 c=1 +# 2.4 z= +# +# 3. Long args (vars): +# 3.1 longflag=1 +# 3.2 param1=test +# 3.3 param2=password +# 3.4 barflag= +# +# 4. Long args (echo): +# 4.1 longflag=1 +# 4.2 param1=test +# 4.3 param2=password +# 4.4 barflag= +# +# 5. Args by index: +# 5.1 arg[1]=asdf +# 5.2 arg[3]=--param1=paramvalue1 +# +# === hello() === +# Hi, John! How u r doin? +# +# === food() === +# Om nom nom! Seafood is very tasty diff --git a/helpers/basic.sh b/helpers/basic.sh new file mode 100644 index 0000000..0c3f820 --- /dev/null +++ b/helpers/basic.sh @@ -0,0 +1,95 @@ +#!/bin/bash +source $(dirname $0)/io.sh || exit 255 + +######################################################## +# Little handy helpers for scripting +######################################################## + +# convert relative path $1 to full one +abspath() { + echo $(realpath -q "${1/#\~/$HOME}") +} + +# check if path $1 is writable +is_writable() { + [ -w "$(abspath $1)" ] +} + +# check if path $1 is a directory +is_dir() { + [ -d "$(abspath $1)" ] +} + +# check if path $1 is a file +is_file() { + [ -f "$(abspath $1)" ] +} + +# check if an argument is a shell function +is_function() { + declare -F "$1" > /dev/null +} + +# check if string $1 matches regex $2 +regex_match() { + printf "%s" "$1" | grep -qP "$2" +} + +# check if array $2 contains string $1 +in_array() { + local find=$1 + shift + for e in "$@"; do + [[ "$e" == "$find" ]] && return 0 + done + return 1 +} + +# join all elements of array $2 with delimiter $1 +implode() { + local d=${1-} + local f=${2-} + if shift 2; then + printf %s "$f" "${@/#/$d}" + fi +} + +# open url $1 in system web-browser +open_url() { + if which xdg-open > /dev/null; then + xdg-open "$1" /dev/null 2>&1 & disown + elif which gnome-open > /dev/null; then + gnome-open "$1" /dev/null 2>&1 & disown + fi +} + +# unpack .tar.gz file $1 into path $2 +unpack_targz() { + require tar + tar -xzf "$1" -C "$2" +} + +# make soft symbolic link of path $1 to path $2 +symlink() { + ln -sf "$1" "$2" +} + +# download file $1 into path $2 using wget +download() { + require wget + wget "$1" -O "$2" +} + + + + + + + + + + + + + + diff --git a/helpers/debug.sh b/helpers/debug.sh new file mode 100644 index 0000000..4232ccf --- /dev/null +++ b/helpers/debug.sh @@ -0,0 +1,26 @@ +#!/bin/bash +source $(dirname $0)/io.sh || exit 255 + +######################################################## +# Functions to debug scripts +######################################################## + +var_dump() { + debug "$1 = ${!1}" +} + +print_stacktrace() { + STACK="" + local i + local stack_size=${#FUNCNAME[@]} + debug "Callstack:" + # for (( i=$stack_size-1; i>=1; i-- )); do + for (( i=1; i<$stack_size; i++ )); do + local func="${FUNCNAME[$i]}" + [ x$func = x ] && func=MAIN + local linen="${BASH_LINENO[$(( i - 1 ))]}" + local src="${BASH_SOURCE[$i]}" + [ x"$src" = x ] && src=non_file_source + debug " at $func $src:$linen" + done +} diff --git a/helpers/git.sh b/helpers/git.sh new file mode 100644 index 0000000..f064501 --- /dev/null +++ b/helpers/git.sh @@ -0,0 +1,177 @@ +#!/bin/bash +source $(dirname $0)/io.sh || exit 255 +source $(dirname $0)/basic.sh || exit 255 +source $(dirname $0)/pkg.sh || exit 255 + +######################################################## +# Shorthands for git +######################################################## + +git.clone_quick() { + require git + git clone --depth=1 --single-branch $* +} + +git.is_repo() { + require git + [ "$1" ] || die "Path is not specified" 101 + require_dir "$1/" + check_dir "$1/.git" +} + +git.require_repo() { + require git + git.is_repo "$1" || die "'$1' is not git repository!" 10 +} + +git.cfg() { + require git + [ "$1" ] || die "Key is not specified" 101 + if [[ "$2" ]]; then + git config --global --replace-all "$1" "$2" + else + echo $(git config --global --get-all "$1") + fi +} + +git.set_user() { + require git + [ "$1" ] || die "git.set_user: Repo is not specified" 100 + git.cfg "$1" "user.name" "$2" + git.cfg "$1" "user.email" "$3" + success "User set to '$name <$email>' in ${FWHITE}$1" +} + +git.fetch() { + require git + if [ "$1" ]; then + if git.remote_branch_exists "origin/$1"; then + git fetch origin "refs/heads/$1:refs/remotes/origin/$1" --progress --prune --quiet 2>&1 || die "Could not fetch $1 from origin" 12 + else + warn "Tried to fetch branch 'origin/$1' but it does not exist." + fi + else + git fetch origin --progress --prune --quiet 2>&1 || exit 12 + fi +} + +git.reset() { + require git + git reset --hard HEAD + git clean -fd +} + +git.clone() { + require git + git clone $* 2>&1 +} + +git.co() { + require git + git checkout $* 2>&1 +} + +git.is_it_current_branch() { + require git + [ "$1" ] || die "git.is_it_current_branch: Branch is not specified" 19 + [[ "$(git.current_branch)" = "$1" ]] +} + +git.pull() { + require git + [ "$1" ] && BRANCH=$1 || BRANCH=$(git.current_branch) + # note "Updating branch $BRANCH..." + git pull origin "refs/heads/$BRANCH:refs/remotes/origin/$BRANCH" --prune --force --quiet 2>&1 || exit 13 + git pull origin --tags --force --quiet 2>&1 || exit 13 + # [ "$1" ] || die "git.pull: Branch is not specified" 19 + # if [ "$1" ]; then + # note "Updating branch $1..." + # git pull origin "refs/heads/$1:refs/remotes/origin/$1" --prune --force --quiet 2>&1 || exit 13 + # else + # note "Updating current branch..." + # git pull + # fi +} + +git.current_branch() { + require git + git branch --show-current || exit 18 +} + +git.local_branch_exists() { + require git + [ -n "$(git for-each-ref --format='%(refname:short)' refs/heads/$1)" ] +} + +git.update_refs() { + require git + info "Updating local refs..." + git remote update origin --prune 1>/dev/null 2>&1 || exit 18 +} + +git.delete_remote_branch() { + require git + [ "$1" ] || die "git.remote_branch_exists: Branch is not specified" 19 + if git.remote_branch_exists "origin/$1"; then + git push origin :"$1" # || die "Could not delete the remote $1 in $ORIGIN" + return 0 + else + warn "Trying to delete the remote branch $1, but it does not exists in origin" + return 1 + fi +} + +git.is_clean_worktree() { + require git + git rev-parse --verify HEAD >/dev/null || exit 18 + git update-index -q --ignore-submodules --refresh + git diff-files --quiet --ignore-submodules || return 1 + git diff-index --quiet --ignore-submodules --cached HEAD -- || return 2 + return 0 +} + +git.is_branch_merged_into() { + require git + [ "$1" ] || die "git.remote_branch_exists: Branch1 is not specified" 19 + [ "$2" ] || die "git.remote_branch_exists: Branch2 is not specified" 19 + git.update_refs + local merge_hash=$(git merge-base "$1"^{} "$2"^{}) + local base_hash=$(git rev-parse "$1"^{}) + [ "$merge_hash" = "$base_hash" ] +} + +git.remote_branch_exists() { + require git + [ "$1" ] || die "git.remote_branch_exists: Branch is not specified" 19 + git.update_refs + [ -n "$(git for-each-ref --format='%(refname:short)' refs/remotes/$1)" ] +} + +git.new_branch() { + require git + [ "$1" ] || die "git.new_branch: Branch is not specified" 19 + if [ "$2" ] && ! git.local_branch_exists "$2" && git.remote_branch_exists "origin/$2"; then + git.co -b "$1" origin/"$2" + else + git.co -b "$1" "$2" + fi +} + +git.require_clean_worktree() { + require git + if ! git.is_clean_worktree; then + warn "Your working tree is dirty! Look at this:" + git status -bs + _T="What should you do now?\n" + _T="${_T}\t${BOLD}${FWHITE}0.${RESET} try to continue as is\t- errors may occur!\n" + _T="${_T}\t${BOLD}${FWHITE}1.${RESET} hard reset\t\t\t- clear current changes and new files\n" + _T="${_T}\t${BOLD}${FWHITE}2.${RESET} stash changes (default)\t- save all changes in safe to apply them later via 'git stash pop'\n" + _T="${_T}\t${BOLD}${FWHITE}3.${RESET} cancel\n" + ask "${_T}${BOLD}${FWHITE}Your choice [0-3]" reset_answer + case $reset_answer in + 1 ) warn "Clearing your work..." && git.reset ;; + 3 ) exit ;; + * ) git stash -a -u -m "WIP before switch to $branch_task" ;; + esac + fi +} diff --git a/helpers/io.sh b/helpers/io.sh new file mode 100644 index 0000000..36b742d --- /dev/null +++ b/helpers/io.sh @@ -0,0 +1,112 @@ +#!/bin/bash + +######################################################## +# Simple and fancy input & output +######################################################## + +IINFO="( i )" +INOTE="( * )" +IWARN="( # )" +IERROR="( ! )" +IFATAL="( @ )" +ISUCCESS="( ! )" +IASK="( ? )" +IDEBUG="(DBG)" +IVRB="( + )" + +BOLD="\e[1m" +DIM="\e[2m" +NOTBOLD="\e[22m" # sometimes \e[21m +NOTDIM="\e[22m" +NORMAL="\e[20m" +RESET="\e[0m" + +FRESET="\e[39m" +FBLACK="\e[30m" +FWHITE="\e[97m" +FRED="\e[31m" +FGREEN="\e[32m" +FYELLOW="\e[33m" +FBLUE="\e[34m" +FLRED="\e[91m" +FLGREEN="\e[92m" +FLYELLOW="\e[93m" +FLBLUE="\e[94m" + +BRESET="\e[49m" +BBLACK="\e[40m" +BWHITE="\e[107m" +BRED="\e[41m" +BGREEN="\e[42m" +BYELLOW="\e[43m" +BBLUE="\e[44m" +BLRED="\e[101m" +BLGREEN="\e[102m" +BLYELLOW="\e[103m" +BLBLUE="\e[104m" + +dt() { + echo "[$(date +'%H:%M:%S')] " +} + +ask() { + IFS= read -rp "$(print ${BOLD}${BBLUE}${FWHITE}${IASK}${BRESET}\ ${BOLD}$1 ): " $2 +} + +print() { + echo -e "$*${RESET}" +} + +debug() { + if [ "$2" ]; then + print "${DIM}${BOLD}${RESET}${DIM}$(dt)${IDEBUG} ${FUNCNAME[1]:-?}():${BASH_LINENO:-?}\t$1 " >&2 + else + print "${DIM}${BOLD}${RESET}${DIM}$(dt)${IDEBUG} $1 " >&2 + fi +} + +verbose() { + print "${BOLD}$(dt)${IVRB}${RESET}${FYELLOW} $1 " +} + +info() { + print "${BOLD}$(dt)${FWHITE}${BLBLUE}${IINFO}${RESET}${FWHITE} $1 " +} + +note() { + print "${BOLD}$(dt)${DIM}${FWHITE}${INOTE}${RESET} $1 " +} + +success() { + print "${BOLD}$(dt)${BGREEN}${FWHITE}${ISUCCESS}${BRESET}$FGREEN $1 " +} + +warn() { + print "${BOLD}$(dt)${BYELLOW}${FBLACK}${IWARN}${BRESET}${FYELLOW} Warning:${RESET} $1 " +} + +error() { + print "${BOLD}$(dt)${BLRED}${FWHITE}${IERROR} Error: ${BRESET}${FLRED} $1 " >&2 +} + +fatal() { + print "${BOLD}$(dt)${BRED}${FWHITE}${IFATAL} FATAL: $1 " >&2 + print_stacktrace +} + +die() { + error "${1:-halted}" + exit ${2:-255} +} + +# var='test var_dump' +# var_dump var +# debug 'test debug' +# verbose 'test verbose' +# info 'test info' +# note 'test note' +# success 'test success' +# warn 'test warn' +# error 'test error' +# fatal 'test fatal' +# die 'test die' diff --git a/helpers/log.sh b/helpers/log.sh new file mode 100644 index 0000000..a1d9c6e --- /dev/null +++ b/helpers/log.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +######################################################## +# Logging functions +######################################################## + +# write some message $1 in log file and stdout with timestamp +log_path="/home/$USER/logs" +log() { + [ ! -d "$log_path" ] && log_path="./log" + [ ! -d "$log_path" ] && mkdir -p "$log_path" + echo -e "[$(date '+%H:%M:%S')] $*" | tee -a "$log_path/$(date '+%Y%m%d').log" +} diff --git a/helpers/misc.sh b/helpers/misc.sh new file mode 100644 index 0000000..649d592 --- /dev/null +++ b/helpers/misc.sh @@ -0,0 +1,66 @@ +#!/bin/bash + +######################################################## +# Misc +######################################################## + +# https://askubuntu.com/a/30414 +is_full_screen() { + local WINDOW=$(echo $(xwininfo -id $(xdotool getactivewindow) -stats | \ + egrep '(Width|Height):' | \ + awk '{print $NF}') | \ + sed -e 's/ /x/') + local SCREEN=$(xdpyinfo | grep -m1 dimensions | awk '{print $2}') + if [ "$WINDOW" = "$SCREEN" ]; then + return 0 + fi + return 1 +} + +curltime() { + curl -w @- -o /dev/null -s "$@" <<'EOF' + time_namelookup: %{time_namelookup} sec\n + time_connect: %{time_connect} sec\n + time_appconnect: %{time_appconnect} sec\n + time_pretransfer: %{time_pretransfer} sec\n + time_redirect: %{time_redirect} sec\n + time_starttransfer: %{time_starttransfer} sec\n + ---------------\n + time_total: %{time_total} sec\n +EOF +} + +ytm() { + youtube-dl \ + --extract-audio \ + --audio-format flac \ + --audio-quality 0 \ + --format bestaudio \ + --write-info-json \ + --output "$HOME/Downloads/ytm/%(playlist_title)s/%(channel)s - %(title)s.%(ext)s" \ + $* +} + +docker.ip() { # not finished + if [ "$1" ]; then + if [ "$1" = "-a" ]; then + docker ps -aq \ + | xargs -n 1 docker inspect --format '{{.Name}}{{range .NetworkSettings.Networks}} {{.IPAddress}}{{end}}' \ + | sed -e 's#^/##' \ + | column -t + elif [ "$1" = "-c" ]; then + docker-compose ps -q \ + | xargs -n 1 docker inspect --format '{{.Name}}{{range .NetworkSettings.Networks}} {{.IPAddress}}{{end}}' \ + | sed -e 's#^/##' \ + | column -t + else + docker inspect --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' "$1" + docker port "$1" + fi + else + docker ps -q \ + | xargs -n 1 docker inspect --format '{{.Name}}{{range .NetworkSettings.Networks}} {{.IPAddress}}{{end}}' \ + | sed -e 's#^/##' \ + | column -t + fi +} diff --git a/helpers/notifications.sh b/helpers/notifications.sh new file mode 100644 index 0000000..ec34f43 --- /dev/null +++ b/helpers/notifications.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +######################################################## +# Desktop notifications +######################################################## + +notify () { + require "notify-send" + [ -n "$1" ] && local title="$1" || local title="My notification" + local text="$2" + local level="$3" + local icon="$4" + case $level in + "critical") local timeout=0 ;; + "low") local timeout=5000 ;; + *) local timeout=10000 ;; + esac + notify-send "$title" "$text" -a "MyScript" -u "$level" -i "$icon" -t $timeout +} + +notify_error() { + notify "Error" "$1" "critical" "dialog-error" +} + +notify_warning() { + notify "Warning" "$1" "normal" "dialog-warning" +} + +notify_info() { + notify "" "$1" "low" "dialog-information" +} diff --git a/helpers/packages.sh b/helpers/packages.sh new file mode 100644 index 0000000..b73c75c --- /dev/null +++ b/helpers/packages.sh @@ -0,0 +1,44 @@ +#!/bin/bash +source $(dirname $0)/io.sh || exit 255 + +######################################################## +# Functions to control system packages +######################################################## + +installed() { + command -v "$1" >/dev/null 2>&1 +} + +installed_deb() { + dpkg --list | grep -qw "ii $1" +} + +apt_install() { + sudo apt install -y --autoremove $* +} + +require() { + sw=() + for package in "$@"; do + if ! installed "$package" && ! installed_deb "$package"; then + sw+=("$package") + fi + done + if [ ${#sw[@]} -gt 0 ]; then + info "These packages will be installed in your system:\n${sw[*]}" + apt_install ${sw[*]} + [ $? -gt 0 ] && die "installation cancelled" 201 + fi +} + +require_pkg() { + sw=() + for package in "$@"; do + if ! installed "$package" && ! installed_deb "$package"; then + sw+=("$package") + fi + done + if [ ${#sw[@]} -gt 0 ]; then + die "These packages must be installed in your system:\n${sw[*]}" 200 + fi +} diff --git a/helpers/testing.sh b/helpers/testing.sh new file mode 100644 index 0000000..a0bed0a --- /dev/null +++ b/helpers/testing.sh @@ -0,0 +1,89 @@ +#!/bin/bash +source $(dirname $0)/io.sh || exit 255 + +######################################################## +# Testing functions +######################################################## + +# $1 - command to exec +assert_exec() { + [ "$1" ] || exit 1 + local prefix="$(dt)${BOLD}${FWHITE}[TEST EXEC]" + if $($1 1>/dev/null 2>&1); then + local text="${BGREEN} PASSED" + else + local text="${BLRED} FAILED" + fi + print "${prefix} ${text} ${BRESET} ($?):${RESET} $1" +} +# usage: + +# func1() { +# return 0 +# } +# func2() { +# return 1 +# } +# assert_exec "func1" # PASSED +# assert_exec "func2" # FAILED +# assert_exec "whoami" # PASSED + + +# $1 - command to exec +# $2 - expected output +assert_output() { + [ "$1" ] || exit 1 + [ "$2" ] && local expected="$2" || local expected='' + local prefix="$(dt)${BOLD}${FWHITE}[TEST OUTP]" + local output=$($1 2>&1) + local code=$? + if [[ "$output" == *"$expected"* ]]; then + local text="${BGREEN} PASSED" + else + local text="${BLRED} FAILED" + fi + print "${prefix} ${text} ${BRESET} (${code}|${expected}):${RESET} $1" + # print "\tOutput > $output" +} +# usage: + +# func1() { +# echo "some string" +# } +# func2() { +# echo "another string" +# } +# expect_output "func1" "string" # PASSED +# expect_output "func2" "some" # FAILED +# expect_output "func2" "string" # PASSED + + +# $1 - command to exec +# $2 - expected exit-code +assert_code() { + [ "$1" ] || exit 1 + [ "$2" ] && local expected=$2 || local expected=0 + local prefix="$(dt)${BOLD}${FWHITE}[TEST CODE]" + $($1 1>/dev/null 2>&1) + local code=$? + if [[ $code -eq $expected ]]; then + local text="${BGREEN} PASSED" + else + local text="${BLRED} FAILED" + fi + print "${prefix} ${text} ${BRESET} (${code}|${expected}):${RESET} $1" +} +# usage: + +# func1() { + # # exit 0 + # return 0 +# } +# func2() { + # # exit 1 + # return 1 +# } +# expect_code "func1" 0 # PASSED +# expect_code "func1" 1 # FAILED +# expect_code "func2" 0 # FAILED +# expect_code "func2" 1 # PASSED diff --git a/install/omz-fancy b/install/omz-fancy index 98fb0fb..cf807fb 100755 --- a/install/omz-fancy +++ b/install/omz-fancy @@ -1,6 +1,15 @@ #!/bin/bash ##makedesc: Install omz fancy (powerline10k + MesloLGS font) +# https://gist.github.com/anthonyaxenov/b8460935d06b9f0da72def03d0f26515 + +# Based on: +# https://github.com/Powerlevel9k/powerlevel9k/wiki/Install-Instructions +# https://github.com/ohmyzsh/ohmyzsh +# https://powerline.readthedocs.io/en/latest/installation/linux.html#fonts-installation +# https://gist.github.com/dogrocker/1efb8fd9427779c827058f873b94df95 +# https://linuxhint.com/install_zsh_shell_ubuntu_1804/ + echo echo "===============================================" echo "Installing omz fancy: powerline10k + MesloLGS font..." diff --git a/install/openvpn b/install/openvpn index f52a536..253a77a 100755 --- a/install/openvpn +++ b/install/openvpn @@ -15,7 +15,7 @@ require libssl-dev \ mkdir -p "$HOME/install/" download "https://swupdate.openvpn.org/community/releases/openvpn-${OVPNVER}.tar.gz" "$HOME/install/openvpn-${OVPNVER}.tar.gz" && \ - unpak_targz "$HOME/install/openvpn-${OVPNVER}.tar.gz" "$HOME/install/" && \ + unpack_targz "$HOME/install/openvpn-${OVPNVER}.tar.gz" "$HOME/install/" && \ cd "$HOME/install/openvpn-${OVPNVER}" && \ sudo ./configure && \ sudo make && \ diff --git a/install/postman b/install/postman index 999db63..63762cb 100755 --- a/install/postman +++ b/install/postman @@ -13,7 +13,7 @@ mkdir -p "$HOME/install" && \ "$HOME/.local/share/applications" download "https://dl.pstmn.io/download/latest/linux64" "$HOME/install/postman.tar.gz" && \ - unpak_targz "$HOME/install/postman.tar.gz" "$HOME/install" && \ + unpack_targz "$HOME/install/postman.tar.gz" "$HOME/install" && \ symlink "$HOME/install/Postman/Postman" "$HOME/.local/bin/postman" && \ cat << EOF > "$HOME/.local/share/applications/Postman.desktop" && sudo update-desktop-database [Desktop Entry] diff --git a/tools/README.md b/tools/README.md new file mode 100644 index 0000000..99dd29a --- /dev/null +++ b/tools/README.md @@ -0,0 +1,19 @@ +# Shell scripts + +## Russian + +Эти скрипты я писал в разное время для решения разных задач. +Чтобы они не растерялись по репозиториям и носителям, я решил собрать их здесь в одну кучу. + +Я всегда использую Ubuntu в качестве своих настольных и серверных ОС, поэтому все эти скрипты писались и использовались в этих средах с версий 18.*. + +Многие скрипты зависимы от [io.sh](/io.sh). + +## English + +These scripts were written at different times to solve different my own problems. +I decided to collect them here in a heap so that they are not lost in repositories and media. + +I always use Ubuntu as my desktop and server OS, so all these scripts has been written and used in these environments since version 18.*. + +Many scripts depending on [io.sh](/io.sh). diff --git a/tools/basic-ubuntu-lemp.sh b/tools/basic-ubuntu-lemp.sh new file mode 100644 index 0000000..833ca26 --- /dev/null +++ b/tools/basic-ubuntu-lemp.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +apt update && apt upgrade -y --autoremove +apt install -y \ + apt-transport-https \ + build-essential \ + ca-certificates \ + cmake \ + curl \ + dialog \ + gettext \ + gnupg \ + htop \ + libaio1 \ + libcurl4-gnutls-dev \ + libexpat1-dev \ + libghc-zlib-dev \ + libssl-dev \ + make \ + mc \ + nano \ + net-tools \ + nmap \ + p7zip-full \ + software-properties-common \ + unzip \ + inotify-tools \ + git \ + mariadb-server \ + mariadb-client \ + nginx \ + certbot diff --git a/tools/dc b/tools/dc new file mode 100644 index 0000000..d64a048 --- /dev/null +++ b/tools/dc @@ -0,0 +1,26 @@ +#!/bin/bash +CONTAINER="my-container" # the name of the container in which to 'exec' something +CONFIG="$(dirname $([ -L $0 ] && readlink -f $0 || echo $0))/docker-compose.yml" # path to compose yml file +CMD="docker-compose -f $CONFIG" # docker-compose command +APP_URL='http://localhost:8000/' + +open_browser() { + if which xdg-open > /dev/null; then + xdg-open "$1" /dev/null 2>&1 & disown + elif which gnome-open > /dev/null; then + gnome-open "$1" /dev/null 2>&1 & disown + fi +} + +case "$1" in + '' | 'help' ) echo -e "Provide one of operations: \t start, stop, up, down, restart, rebuild, open"; + echo "Otherwise all args will be passed to 'docker exec -ti $CONTAINER ...'" ;; + 'open' ) open_browser $APP_URL ;; + 'up' ) $CMD up -d --build ;; # build and start containers + 'down' ) $CMD down --remove-orphans ;; # stop and remove containers + 'start' ) $CMD start ;; # start containers + 'stop' ) $CMD stop ;; # stop containers + 'restart' ) $CMD stop && $CMD start ;; # restart containers + 'rebuild' ) $CMD down --remove-orphans && $CMD up -d --build ;; # rebuild containers + * ) docker exec -ti $CONTAINER $@ # exec anything in container +esac diff --git a/tools/second-mon.sh b/tools/display-resolution.sh similarity index 54% rename from tools/second-mon.sh rename to tools/display-resolution.sh index 4d66537..8ec6120 100755 --- a/tools/second-mon.sh +++ b/tools/display-resolution.sh @@ -1,54 +1,55 @@ #!/bin/bash +# https://gist.github.com/anthonyaxenov/c16e1181d4b8a8644c57ec8a1f6cf21c ######################################################################### # # -# Set display resolution # +# Set output resolution # # # # Author: Anthony Axenov (Антон Аксенов) # # Version: 1.0 # -# License: WTFPL # +# License: WTFPLv2 # # # ######################################################################### # # -# Using this script you can change your display resolution # +# Using this script you can change your output resolution # # to any one you need. Just adjust some vars below and run script # # (chmod +x needed). # # # ######################################################################### -# https://gist.github.com/anthonyaxenov/c16e1181d4b8a8644c57ec8a1f6cf21c - -# Set display name to work with. You can get it via 'xrandr --listactivemonitors' -display="HDMI-2" -# Set width of this display in px -width=1600 -# Set height of this display in px -height=900 +# Set output name to work with. You can get it via 'xrandr --listactivemonitors' +output="HDMI-3" +# Set width of this output in px +width=1920 +# Set height of this output in px +height=1080 +# Set refresh rate in Hz of this output in px +refresh=120 # Sometimes cvt and gtf generates different modelines. # You can play around and look which of them gives best result: -modeline=$(cvt ${width} ${height} | grep "Modeline") -# modeline=$(gtf ${width} ${height} 60 | grep "Modeline") +modeline=$(cvt ${width} ${height} ${refresh} | grep "Modeline") +# modeline=$(gtf ${width} ${height} ${refresh} | grep "Modeline") # Some important data needed to xrandr: -modename="${width}x${height}_my" +modename="${width}x${height}@${refresh}_my" params=$(echo "$modeline" | sed "s|^\s*Modeline\s*\"[0-9x_.]*\"\s*||") -echo "Set resolution ${width}x${height} on display $display:" +echo "Set resolution ${width}x${height}@${refresh} on output $output:" echo "$modename $params" # Simple logic: -# 1. Switch display to safe mode which always exists (I believe) to avoid errors -xrandr --output $display --mode 640x480 -# 2. If display aready have our mode -- we must delete it to avoid errors +# 1. Switch output to safe mode which always exists (I believe) to avoid errors +xrandr --output $output --mode 640x480 --verbose +# 2. If output aready have our mode -- we must delete it to avoid errors if $(xrandr | grep -q "$modename"); then - # 2.1. Detach mode from display - xrandr --delmode $display $modename + # 2.1. Detach mode from output + xrandr --delmode $output $modename # 2.2. Remove mode itself xrandr --rmmode $modename fi # 3. Create new mode with freshly generated parameters -xrandr --newmode $modename $params -# 4. Attach mode to our display -xrandr --addmode $display $modename -# 5. Switch display to this mode immidiately -xrandr --output $display --mode $modename +xrandr --newmode $modename $params --verbose +# 4. Attach mode to our output +xrandr --addmode $output $modename --verbose +# 5. Switch output to this mode immidiately +xrandr --output $output --mode $modename --refresh $refresh --verbose diff --git a/tools/free-space.sh b/tools/free-space.sh old mode 100644 new mode 100755 diff --git a/tools/init-home-mediasrv.sh b/tools/init-home-mediasrv.sh new file mode 100644 index 0000000..f07520f --- /dev/null +++ b/tools/init-home-mediasrv.sh @@ -0,0 +1,121 @@ +#!/bin/bash + +sudo apt update && sudo apt upgrade -y --autoremove +sudo apt install -y \ + alien \ + apt-transport-https \ + build-essential \ + ca-certificates \ + cmake \ + curl \ + dconf-editor \ + default-jdk \ + dialog \ + gettext \ + gnupg \ + gparted \ + hardinfo \ + htop \ + libaio1 \ + libcurl4-gnutls-dev \ + libexpat1-dev \ + libghc-zlib-dev \ + libssl-dev \ + lsb-release \ + lsp-plugins \ + make \ + mc \ + nano \ + neofetch \ + net-tools \ + nmap \ + p7zip-full \ + easyeffects \ + software-properties-common \ + ubuntu-restricted-extras \ + unzip \ + vlc \ + ffmpeg \ + xclip \ + inotify-tools \ + notify-osd \ + fonts-open-sans \ + libnotify-bin \ + gnome-software \ + gnome-software-plugin-flatpak \ + gnome-software-plugin-snap \ + terminator \ + geoclue-2.0 \ + redshift \ + redshift-gtk \ + samba \ + dkms + + +# https://selectel.ru/blog/tutorials/how-to-install-and-configure-samba-on-ubuntu-20-04/ +# https://linuxconfig.org/how-to-configure-samba-server-share-on-ubuntu-22-04-jammy-jellyfish-linux +# https://phoenixnap.com/kb/ubuntu-samba +# https://computingforgeeks.com/install-and-configure-samba-server-share-on-ubuntu/ +# https://linux.how2shout.com/how-to-install-samba-on-ubuntu-22-04-lts-jammy-linux/ +sudo cp /etc/samba/smb.conf /etc/samba/smb.conf.bak +sudo bash -c 'grep -v -E "^#|^;" /etc/samba/smb.conf.bak | grep . > /etc/samba/smb.conf' +sudo systemctl enable --now smbd +sudo usermod -aG sambashare $USER +sudo smbpasswd -a $USER + + + + +sudo add-apt-repository -y ppa:agornostal/ulauncher && \ + sudo apt install -y --autoremove ulauncher + +curl -L https://yt-dl.org/downloads/latest/youtube-dl -o "${HOME}/.local/bin/youtube-dl" && \ + sudo chmod +rx "${HOME}/.local/bin/youtube-dl" + +wget "https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb" && \ + sudo dpkg -i google-chrome-stable_current_amd64.deb + +git clone https://github.com/aircrack-ng/rtl8812au.git && \ + cd rtl8812au && \ + sudo make dkms_install + +sudo curl -s -o /usr/share/keyrings/syncthing-archive-keyring.gpg https://syncthing.net/release-key.gpg && \ + echo "deb [signed-by=/usr/share/keyrings/syncthing-archive-keyring.gpg] https://apt.syncthing.net/ syncthing stable" | sudo tee /etc/apt/sources.list.d/syncthing.list && \ + echo "deb [signed-by=/usr/share/keyrings/syncthing-archive-keyring.gpg] https://apt.syncthing.net/ syncthing candidate" | sudo tee /etc/apt/sources.list.d/syncthing.list && \ + sudo apt update && sudo apt install -y --autoremove syncthing && \ + wget "https://raw.githubusercontent.com/syncthing/syncthing/main/etc/linux-desktop/syncthing-start.desktop" -O $HOME/.local/share/applications/syncthing-start.desktop && \ + wget "https://raw.githubusercontent.com/syncthing/syncthing/main/etc/linux-desktop/syncthing-ui.desktop" -O $HOME/.local/share/applications/syncthing-ui.desktop && \ + ln -sf $HOME/.local/share/applications/syncthing-start.desktop $HOME/.config/autostart/syncthing-start.desktop + + + +##################################################################### + +sudo apt install -y kodi kodi-pvr-iptvsimple + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tools/inotifywait-cp/README.md b/tools/inotifywait-cp/README.md new file mode 100644 index 0000000..a4d8bfe --- /dev/null +++ b/tools/inotifywait-cp/README.md @@ -0,0 +1,5 @@ +# Backing up photos from Syncthing + +More info: +* 🇷🇺 [axenov.dev/резервное-копирование-фотографий-со](https://axenov.dev/резервное-копирование-фотографий-со/) +* 🇺🇸 (planned to translate) diff --git a/tools/inotifywait-cp/inotifywait-cp.service b/tools/inotifywait-cp/inotifywait-cp.service new file mode 100644 index 0000000..57ab4d6 --- /dev/null +++ b/tools/inotifywait-cp/inotifywait-cp.service @@ -0,0 +1,19 @@ +# Daemon file +# Place or symlink it to /etc/systemd/system/inotifywait-cp.service +# Enable and start: sudo systemctl enable --now inotifywait-cp +# Check it: sudo systemctl status inotifywait-cp + +[Unit] +Description=Photosync from android + +[Service] +Type=simple +Restart=always +# correct these parameters as needed: +User=user +WorkingDirectory=/home/user +ExecStart=bash /home/user/.local/bin/photosync-a53.sh + + +[Install] +WantedBy=network.target diff --git a/tools/inotifywait-cp/inotifywait-cp.sh b/tools/inotifywait-cp/inotifywait-cp.sh new file mode 100644 index 0000000..b743933 --- /dev/null +++ b/tools/inotifywait-cp/inotifywait-cp.sh @@ -0,0 +1,59 @@ +#!/bin/bash +# My use case: +# syncthing synchronizes ALL changes in DCIM directory on my android to PC. +# I wanted files to be copied somewhere else on my PC to stay forever, so I +# could sort them later and safely free some space on mobile without loss. +# Also I wish to have some stupid log with history of such events. + +# inotify-tools package must be installed! + +# CHANGE THIS PARAMETERS to ones you needed +dir_src="$HOME/Syncthing/Mobile/Camera" +dir_dest="$HOME/some/safe/place" +dir_logs="$HOME/inotifywait-cp-logs" +regexp="[0-9]{8}_[0-9]{6}.*\.(jpg|mp4|gif)" + +print() { + echo -e "[`date '+%H:%M:%S'`] $*" \ + | tee -a "$dir_logs/`date '+%Y%m%d'`.log" +} + +copy () { + mkdir -p "$dir_src" "$dir_dest" "$dir_logs" + if [ -f "$dir_dest/$1" ]; then + print "SKIPPED:\t$dir_dest/$1" + else + cp "$dir_src/$1" "$dir_dest/$1" + print "COPIED:\t$dir_src/$1 => $dir_dest/$1" + fi +} + +mkdir -p "$dir_src" "$dir_dest" "$dir_logs" + +print "START\t=========================" + +# First, try to backup files synced since last exec of this script +ls -1 "$dir_src" \ +| grep -E "^$regexp$" \ +| while read filename; do copy "$filename"; done + +# Next, run inotifywait against source directory with args: +# --quiet -- print less (only print events) +# --monitor -- don't stop after first event (like infinite loop) +# --event -- first syncthing creates hidden file to write data into +# then renames it according to source file name, so here +# we listen to MOVED_TO event to catch final filename +# --format %f -- print only filename +# --include -- filename regexp to catch event from, ensure your $regexp +# is correct or remove line 56 to catch synced ALL files + +inotifywait \ + --quiet \ + --monitor \ + --event moved_to \ + --format %f \ + --include "$regexp" \ + "$dir_src" \ + | while read filename; do copy "$filename"; done + +print "FINISH\t=========================" diff --git a/tools/netbeans-php-wrapper/php b/tools/netbeans-php-wrapper/php new file mode 100644 index 0000000..77c5792 --- /dev/null +++ b/tools/netbeans-php-wrapper/php @@ -0,0 +1,157 @@ +#!/bin/bash + +# Welcome to amusement park! + +[[ "$1" = '--help' ]] || [[ "$1" = '-h' ]] && cat < [--map=:] [PHP_ARGS]