From 9cb6aee0c3c9c86eee1f03fabc9dd018d660e155 Mon Sep 17 00:00:00 2001 From: AnthonyAxenov Date: Wed, 28 Jun 2023 22:15:23 +0800 Subject: [PATCH] update --- README.md | 2 +- cfg/.bash_aliases | 104 ++++ cfg/.gitconfig | 102 +--- cfg/git_aliases | 86 +++ php/BasicTestCase.php | 2 +- php/here.php | 35 ++ shell/dc | 26 + shell/helpers.sh | 607 ++++++++++++++++++++ shell/inotifywait-cp/inotifywait-cp.service | 19 + shell/inotifywait-cp/inotifywait-cp.sh | 59 ++ shell/io.sh | 124 ---- shell/php | 157 +++++ shell/quick-backup.sh | 106 ++++ shell/s3-backup.sh | 341 +++++++++++ shell/stacktrace.sh | 19 - shell/tester.sh | 95 --- shell/vscode-ext.sh | 58 +- shell/ytdlcue.sh | 93 +++ 18 files changed, 1689 insertions(+), 346 deletions(-) create mode 100644 cfg/.bash_aliases create mode 100644 cfg/git_aliases create mode 100644 php/here.php create mode 100644 shell/dc create mode 100644 shell/helpers.sh create mode 100644 shell/inotifywait-cp/inotifywait-cp.service create mode 100644 shell/inotifywait-cp/inotifywait-cp.sh delete mode 100644 shell/io.sh create mode 100644 shell/php create mode 100644 shell/quick-backup.sh create mode 100644 shell/s3-backup.sh delete mode 100644 shell/stacktrace.sh delete mode 100644 shell/tester.sh create mode 100644 shell/ytdlcue.sh diff --git a/README.md b/README.md index 90f5c8f..dd1aee2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Github Gists -Репозиторий предназначен для резервного хранения сниппетов из моего [gist.github.com](https://gist.github.com/anthonyaxenov). +Резервный репозиторий публичных сниппетов из [gist.github.com/anthonyaxenov](https://gist.github.com/anthonyaxenov). Оригинальные сниппеты в первую очередь я изменяю именно в гистах, потом здесь. diff --git a/cfg/.bash_aliases b/cfg/.bash_aliases new file mode 100644 index 0000000..131c785 --- /dev/null +++ b/cfg/.bash_aliases @@ -0,0 +1,104 @@ +#!/bin/bash + +# alias bashrc='source ~/.bashrc' +alias zshrc='source ~/.zshrc' +alias realias='source ~/.bash_aliases' +alias reload='exec ${SHELL} -l' +alias sudo='sudo ' # enable aliases to be sudo’ed +alias g='git' +alias hosts="sudo nano /etc/hosts" +alias shrug="echo '¯\_(ツ)_/¯' | xclip -selection c" + +alias ..='cd ..' # zsh builtin +alias ~='cd ~' # zsh builtin +# alias "--"='cd -' # zsh builtin + +alias chmod='chmod --preserve-root' +alias chown='chown --preserve-root' + +alias free='free -h' +alias duh='du -ha --max-depth=1' +alias sduh='sudo du -ha --max-depth=1' + +alias l='ls -pCFh --color=auto' +alias la='ls -pAFh --color=auto' +alias ll='ls -palFh --color=auto' + +alias mkdir='mkdir -pv' +alias where='whereis' # zsh builtin + +alias ps='ps auxf' +alias psg='ps aux | grep -v grep | grep -i -e VSZ -e' + +alias is='type -a' +alias upgrade='sudo apt update && sudo apt upgrade -y && sudo snap refresh' +alias untargz='tar -czf' +alias mkcd="mkdir -p $1 && cd $1" +alias cl='cd $1 && ll' +alias myip='curl http://ipecho.net/plain; echo' +alias ports='netstat -tulpan' + +alias ssh.pub='cat ~/.ssh/*.pub' +alias gpg.new="gpg --full-generate-key" +alias gpg.pub="gpg --armor --export $@" +alias gpg.list='gpg --list-keys --keyid-format SHORT' + +alias lite-xl="LITE_SCALE=1 lite-xl" +alias wine='LANG=ru_RU.utf8 wine' +alias docker.prune='docker image prune -f; docker network prune -f; docker container prune -f' + +# https://obsproject.com/forum/threads/how-to-start-virtual-camera-without-sudo-privileges.139783/ +alias obscam="sudo modprobe v4l2loopback video_nr=2 card_label='OBS Virtual Camera'" + +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 +} + +# Download music from Youtube or Youtube Music +# and save as top quality flac file without video +# Playlist and video/track URLs are supported +# Usage: $ ytm https://www.youtube.com/watch\?v=dQw4w9WgXcQ +# More info: https://github.com/ytdl-org/youtube-dl +ytm() { + youtube-dl \ + --extract-audio \ + --audio-format flac \ + --audio-quality 0 \ + --format bestaudio \ + --write-info-json \ + --output "${HOME}/Музыка/ytm/%(playlist_title)s/%(channel)s - %(title)s.%(ext)s" \ + $@ +} + +docker.ip() { + 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/cfg/.gitconfig b/cfg/.gitconfig index ce66fdd..2e6da83 100644 --- a/cfg/.gitconfig +++ b/cfg/.gitconfig @@ -1,88 +1,20 @@ -# https://gist.github.com/anthonyaxenov/020b25ea53701d82902a7acfb557866c -# ...прочие настройки -[core] - editor = nano - autocrlf = input +[user] + # ... + # signingkey = + # git config user.signingkey ... -- установить ключ +[commit] + gpgSign = true +[tag] + gpgSign = true +[push] + default = current [pull] default = current rebase = false -[push] - default = current -[alias] - - # общее ----------------------------------------------------------------- - aliases = config --get-regexp '^alias' # показать список доступных алиасов - head = log -1 HEAD # показать последний коммит в текущей ветке - # название текущей ветки или тега при detached head: - dehead = "!BR=$(git branch --show-current); if [ -n \"$BR\" ]; then echo $BR; else git describe --contains --all HEAD; fi;" - - # ветки ------------------------------------------------------------------- - bheads = branch -vv # ветки и их последние коммиты - # br = status -sb # показать название текущей ветки - # branch = branch --list -vv # показать текущую ветку в общем списке локальных веток - #mn = merge --no-ff # слить ветку с принудительным коммитом слияния - brd = branch -D # удалить ветку локально - brod = "!git branch -D "${1}"; git push origin :"${1}";" # удалить ветку локально и на origin - merged = branch --merged # показать список веток, слитых в текущую - ghpr = "!git fetch origin pull/$1/head:pr/$1 && git checkout pr/$1" # github: встать на PR с указанным id - # удалить локальные ветки, слитые в текущую: - trim = "!DEFAULT=master; git branch --merged ${1-$DEFAULT} | grep -v " ${1-$DEFAULT}$" | xargs git branch -d; git remote prune origin;" - - # переключение ------------------------------------------------------------ - co = checkout # переключиться на ветку/тег/коммит - cob = checkout -b # создание новое ветки - master = "!git checkout master && git pull" # переключиться на ветку master и обновить - dev = "!git checkout dev && git pull" # переключиться на ветку dev и обновить - develop = "!git checkout develop && git pull" # переключиться на ветку develop и обновить - - # фиксация изменений ------------------------------------------------------ - c = commit # коммит - ca = commit -a # коммит всех файлов - cm = commit -m # коммит с заданным сообщением - cam = commit -am # коммит всех файлов с заданным сообщением - amend = commit --amend --no-edit # прикрепляет все индексированные файлы к последнему коммиту, используя уже существующее сообщение - # amenda = commit --amend --no-edit -a - amendm = commit --amend -m # прикрепляет все индексированные файлы к последнему коммиту, спрашивает новое сообщение к коммиту - cp = cherry-pick # применить коммит поверх текущего HEAD - diffc = diff --cached # показать дельту staged-файла - - # управление изменениями, сброс состояний, откат -------------------------- - st = status -sb # короткий status - rh = reset --hard # откат коммита с удалением всех изменений на указанный коммит - rhh = reset --hard HEAD # откат коммита с удалением всех изменений на последний коммит - reseth = reset --mixed HEAD # откат коммита с сохранением всех изменений - unstage = reset HEAD # переводит файл staged => unstaged без потери изменений - clear = checkout -- # удаляет изменения в файле - - # алиасы для временной фиксации - # на самом деле, для таких дел надо использовать git stash - # save = !git add -A && git commit -m 'SAVEPOINT' - # wip = commit -am "WIP" - # undo = reset HEAD~1 --mixed - - # работа с remote-репами -------------------------------------------------- - pushf = push --force # отправить ветку принудительно - pusht = push --tags # отправить теги - pushft = push --tags --force # отправить теги принудительно - pullt = pull --tags --force # получить теги - ploh = pull origin HEAD # получить текущую ветку из origin - remotes = remote -v # показать список удалённых репозиториев - #sy = remote update origin --prune # - rso = remote show origin # показать состояние локальных веток относительно удалённых на origin bare - rpo = remote prune origin # удалить все мёртвые ссылки на bare-ветки origin - repush = 'git push origin :$1 && git push origin $1' - - # просмотр логов ---------------------------------------------------------- - heads = log --graph --decorate --simplify-by-decoration --oneline # коммиты, которыми оканчиваются ветки - tree = log --graph --pretty=format:'%C(yellow)%h%C(cyan)%d%Creset %s %C(white)- %an, %ar%Creset' - hist = log --pretty=format:\"%h | %an (%ad) %s%d\" --graph --date=relative - logfull = log --graph --decorate --all - grog = log --graph --decorate --all --pretty=format:'%C(bold red)%h%C(reset) %C(bold blue)%an%C(reset) %C(green)%cr%C(reset) [%aD]%d%n%B' - - # сабмодули --------------------------------------------------------------- - si = submodule init # инициализация сабмодулей - sdi = submodule deinit -f # удаление сабмодуля - sa = submodule add # добавление сабмодуля - sup = submodule update # обновление сабмодуля - sst = submodule status # статус сабмодулей - ss = submodule summary # список сабмодулей \ No newline at end of file +[core] + editor = nano + autocrlf = input +[remote "origin"] + prune = true +[include] + path = path/to/git_aliases diff --git a/cfg/git_aliases b/cfg/git_aliases new file mode 100644 index 0000000..217f5d4 --- /dev/null +++ b/cfg/git_aliases @@ -0,0 +1,86 @@ +[alias] + + # общее ----------------------------------------------------------------- + init = init -q # no blm! + aliases = config --get-regexp '^alias' # показать список доступных алиасов + user = config --local --get-regexp '^user' # локальные настройки пользователя git + guser = config --global --get-regexp '^user' # глобальные настройки пользователя git + user-me = "!git config user.name 'Anthony Axenov'; git config user.email 'anthonyaxenov@gmail.com'; git config user.signingkey 'F7CCD4EC'" + + # ветки ------------------------------------------------------------------- + bheads = branch -vv # ветки и их последние коммиты + branches = branch --list -vv # показать текущую ветку в общем списке локальных веток + # br = status -sb # показать название текущей ветки + brd = branch -D # удалить ветку локально + brod = "!git branch -D "$1"; git push origin :"$1";" # удалить ветку локально и на origin + merged = branch --merged # показать список веток, слитых в текущую + #ghpr = "!git fetch origin pull/$1/head:pr/$1 && git checkout pr/$1" # github: встать на PR с указанным id + # удалить локальные ветки, слитые в текущую: + trim = "!DEFAULT=master; git branch --merged ${1-$DEFAULT} | grep -v " ${1-$DEFAULT}$" | xargs git branch -d; git remote prune origin;" + + # переключение ------------------------------------------------------------ + co = checkout # переключиться на ветку/тег/коммит + cob = checkout -b # создание новое ветки + master = "!git checkout master && git pull" # переключиться на ветку master и обновить + dev = "!git checkout dev && git pull" # переключиться на ветку dev и обновить + develop = "!git checkout develop && git pull" # переключиться на ветку develop и обновить + + # фиксация изменений ------------------------------------------------------ + c = commit # коммит + ca = commit -a # коммит всех файлов + cm = commit -m # коммит с заданным сообщением + cam = commit -am # коммит всех файлов с заданным сообщением + amend = commit --amend --no-edit -a # прикрепляет все индексированные файлы к последнему коммиту, используя уже существующее сообщение + #amenda = commit --amend --no-edit + amendm = commit --amend -m # прикрепляет все индексированные файлы к последнему коммиту, спрашивает новое сообщение к коммиту + cp = cherry-pick # применить коммит поверх текущего HEAD + diffc = diff --cached # показать дельту staged-файла + + # управление изменениями, сброс состояний, откат -------------------------- + # st = status -sb # короткий status + st = status # сокращение + rh = reset --hard # откат коммита с удалением всех изменений на указанный коммит + rhh = reset --hard HEAD # откат коммита с удалением всех изменений на последний коммит + rmh = reset --mixed HEAD # откат коммита с сохранением всех изменений + unstage = reset HEAD # переводит файл staged => unstaged без потери изменений + clear = checkout -- # удаляет изменения в файле + + # алиасы для временной фиксации + # на самом деле, для таких дел надо использовать git stash + # save = !git add -A && git commit -m 'SAVEPOINT' + wip = commit -am "WIP" + wipa = commit --amend -am "WIP" + undo = reset --mixed HEAD~ + + # работа с remote-репами -------------------------------------------------- + pushf = push --force # отправить ветку принудительно + pusht = push --tags # отправить теги + pushft = push --tags --force # отправить теги принудительно + pullf = pull --force # получить ветку принудительно + pullt = pull --tags # получить теги + pullft = pull --tags --force # получить теги + ploh = pull origin HEAD # получить текущую ветку из origin + remotes = remote -v # показать список удалённых репозиториев + #sy = remote update origin --prune # + rso = remote show origin # показать состояние локальных веток относительно удалённых на origin bare + rpo = remote prune origin # удалить все мёртвые ссылки на bare-ветки origin + repush = 'git push origin :$1 && git push origin $1' # замена push --force + + # просмотр логов ---------------------------------------------------------- + head = log -1 HEAD # показать последний коммит в текущей ветке + heads = log --graph --decorate --simplify-by-decoration --oneline # коммиты, которыми оканчиваются ветки + # название текущей ветки или тега при detached head: + dehead = "!BR=$(git branch --show-current); if [ -n \"$BR\" ]; then echo $BR; else git describe --contains --all HEAD; fi;" + tree = log --graph --pretty=format:'%C(yellow)%h%C(cyan)%d%Creset %s %C(white)- %an, %ar%Creset' + hist = log --pretty=format:\"%h | %an (%ad) %s%d\" --graph --date=relative + logfull = log --graph --decorate --all + grog = log --graph --decorate --all --pretty=format:'%C(bold red)%h%C(reset) %C(bold blue)%an%C(reset) %C(green)%cr%C(reset) [%aD]%d%n%B' + + # сабмодули --------------------------------------------------------------- + sub = submodule # сокращение + # si = submodule init # инициализация сабмодулей + # sdi = submodule deinit -f # удаление сабмодуля + # sa = submodule add # добавление сабмодуля + # sup = submodule update # обновление сабмодуля + # sst = submodule status # статус сабмодулей + # ss = submodule summary # список сабмодулей diff --git a/php/BasicTestCase.php b/php/BasicTestCase.php index 49199b2..6326ea9 100644 --- a/php/BasicTestCase.php +++ b/php/BasicTestCase.php @@ -131,4 +131,4 @@ class BasicTestCase extends TestCase $this->assertArrayNotHasKey($key, $array); } } -} \ No newline at end of file +} diff --git a/php/here.php b/php/here.php new file mode 100644 index 0000000..381d408 --- /dev/null +++ b/php/here.php @@ -0,0 +1,35 @@ +:():` + * @return string|array + */ +function here(bool $as_array = false): string|array +{ + $trace = debug_backtrace(!DEBUG_BACKTRACE_PROVIDE_OBJECT | DEBUG_BACKTRACE_IGNORE_ARGS, 2); + return $as_array + ? [ + 'from' => $trace[1]['class'] ?? $trace[0]['file'], + 'function' => $trace[1]['function'], + 'line' => $trace[0]['line'], + ] + : sprintf( + '%s%s%s():%s', + $trace[1]['class'] ?? $trace[0]['file'], + $trace[1]['type'] ?? '::', + $trace[1]['function'], + $trace[0]['line'] + ); +} + +// Usage: +class MyClass { + public function test(): string { + return here(); + } +} +echo (new MyClass)->test(); // MyClass->test():4 diff --git a/shell/dc b/shell/dc new file mode 100644 index 0000000..d64a048 --- /dev/null +++ b/shell/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/shell/helpers.sh b/shell/helpers.sh new file mode 100644 index 0000000..17538d5 --- /dev/null +++ b/shell/helpers.sh @@ -0,0 +1,607 @@ +######################################################################### +# # +# Bunch of helpers for bash scripting # +# # +# This file is compilation from some of my projects. # +# I'm not sure they're all in perfiect condition but I use them # +# time to time in my scripts. # +# # +######################################################################### + +###################################### +# Little handy helpers for scripting +###################################### + +installed() { + command -v "$1" >/dev/null 2>&1 +} + +installed_pkg() { + dpkg --list | grep -qw "ii $1" +} + +apt_install() { + sudo apt install -y --autoremove "$*" +} + +require() { + sw=() + for package in "$@"; do + if ! installed "$package" && ! installed_pkg "$package"; then + sw+=("$package") + fi + done + if [ ${#sw[@]} -gt 0 ]; then + echo "These packages will be installed in your system:\n${sw[*]}" + apt_install "${sw[*]}" + [ $? -gt 0 ] && { + echo "installation cancelled" + exit 201 + } + fi +} + +require_pkg() { + sw=() + for package in "$@"; do + if ! installed "$package" && ! installed_pkg "$package"; then + sw+=("$package") + fi + done + if [ ${#sw[@]} -gt 0 ]; then + echo "These packages must be installed in your system:\n${sw[*]}" + exit 200 + fi +} + +require_dir() { + is_dir "$1" || die "Directory '$1' does not exist!" 1 +} + +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 +} + +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 +} + +######################################################## +# 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" +} + +###################################### +# 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} ${FUNCNAME[1]:-?}():${BASH_LINENO:-?}\t$1 " + else + print "${DIM}${BOLD}${RESET}${DIM}$1 " + fi +} + +var_dump() { + debug "$1 = ${!1}" 0 +} + +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 +} + +######################################################## +# Tests +######################################################## + +# $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 + +######################################################## +# Misc +######################################################## + +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 +} + +######################################################## +# Working with git +######################################################## + +git.is_repo() { + [ "$1" ] || die "Path is not specified" 101 + require_dir "$1/" + check_dir "$1/.git" +} + +git.require_repo() { + git.is_repo "$1" || die "'$1' is not git repository!" 10 +} + +git.cfg() { + [ "$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() { + [ "$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() { + 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() { + git reset --hard HEAD + git clean -fd +} + +git.clone() { + git clone $* 2>&1 +} + +git.co() { + git checkout $* 2>&1 +} + +git.is_it_current_branch() { + [ "$1" ] || die "git.is_it_current_branch: Branch is not specified" 19 + [[ "$(git.current_branch)" = "$1" ]] +} + +git.pull() { + [ "$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() { + git branch --show-current || exit 18 +} + +git.local_branch_exists() { + [ -n "$(git for-each-ref --format='%(refname:short)' refs/heads/$1)" ] +} + +git.update_refs() { + info "Updating local refs..." + git remote update origin --prune 1>/dev/null 2>&1 || exit 18 +} + +git.delete_remote_branch() { + [ "$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() { + 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() { + [ "$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() { + [ "$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() { + [ "$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() { + 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 +} + +######################################################## +# Also +######################################################## + +# https://gist.github.com/anthonyaxenov/d53c4385b7d1466e0affeb56388b1005 +# https://gist.github.com/anthonyaxenov/89c99e09ddb195985707e2b24a57257d +# ...and other my gists with [SHELL] prefix + +######################################################## +# Sources and articles used +######################################################## +# 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 diff --git a/shell/inotifywait-cp/inotifywait-cp.service b/shell/inotifywait-cp/inotifywait-cp.service new file mode 100644 index 0000000..57ab4d6 --- /dev/null +++ b/shell/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/shell/inotifywait-cp/inotifywait-cp.sh b/shell/inotifywait-cp/inotifywait-cp.sh new file mode 100644 index 0000000..b743933 --- /dev/null +++ b/shell/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/shell/io.sh b/shell/io.sh deleted file mode 100644 index 8f30bb0..0000000 --- a/shell/io.sh +++ /dev/null @@ -1,124 +0,0 @@ -#!/bin/bash -# https://gist.github.com/anthonyaxenov/b17c6fbd7895c6049e1ceddc7c54bb5b -# source: https://misc.flogisoft.com/bash/tip_colors_and_formatting - -######################################################## -# Иконки -######################################################## - -IINFO="[ i ]" -INOTE="[ * ]" -IWARN="[ # ]" -IERROR="[ ! ]" -IFATAL="[ @ ]" -ISUCCESS="[ ! ]" -IASK="[ ? ]" - -######################################################## -# Атрибуты текста (форматирование) -######################################################## - -BOLD="\e[1m" # жирный -DIM="\e[2m" # приглушённый -# UNDERL="\e[4m" # подчёркнутый -# BLINK="\e[5m" # мигающий -# INV="\e[7m" # инвертированный -# HIDDEN="\e[8m" # скрытый - -_BOLD="\e[21m" # нежирный -_DIM="\e[22m" # неприглушённый -# _BLINK="\e[25m" # немигающий -# _UNDERL="\e[24m" # неподчёркнутый -# _INV="\e[27m" # неинвертированный -# _HIDDEN="\e[28m" # нескрытый - -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" - -######################################################## -# Функции для вывода текста -######################################################## - -print() { - echo -e "$*${RESET}" -} - -ask() { - IFS= read -rp "$(dt)$(print ${BOLD}${BBLUE}${FWHITE}${IASK}${BRESET}\ ${BOLD}$1 ): " $2 -} - -dbg() { - print "${DIM}$*" -} - -info() { - print "$(dt)${BOLD}${FWHITE}${IINFO}${RESET}${FWHITE} $1 " -} - -note() { - print "$(dt)${BOLD}${DIM}${FWHITE}${INOTE}${RESET} $1 " -} - -success() { - print "$(dt)${BOLD}${BGREEN}${FWHITE}${ISUCCESS}${BRESET}$FGREEN $1 " -} - -warn() { - print "$(dt)${BOLD}${BYELLOW}${FBLACK}${IWARN}${BRESET}${FYELLOW} Warning:${RESET} $1 " >&2 -} - -error() { - print "$(dt)${BOLD}${BLRED}${FWHITE}${IERROR} Error: ${BRESET}${FLRED} $1 " >&2 -} - -fatal() { - print "$(dt)${BOLD}${BRED}${FWHITE}${IFATAL} FATAL: $1 " >&2 -} - -######################################################## -# Тестирование -######################################################## - -# print -# print "print test" -# print -# ask "ask test" test -# dbg "debug test: answer is $test" -# info "info test" -# note "note test" -# success "success test" -# warn "warn test" -# error "error test" -# fatal "fatal test" diff --git a/shell/php b/shell/php new file mode 100644 index 0000000..77c5792 --- /dev/null +++ b/shell/php @@ -0,0 +1,157 @@ +#!/bin/bash + +# Welcome to amusement park! + +[[ "$1" = '--help' ]] || [[ "$1" = '-h' ]] && cat < [--map=:] [PHP_ARGS]