update
This commit is contained in:
parent
ed013d9321
commit
9cb6aee0c3
@ -1,6 +1,6 @@
|
||||
# Github Gists
|
||||
|
||||
Репозиторий предназначен для резервного хранения сниппетов из моего [gist.github.com](https://gist.github.com/anthonyaxenov).
|
||||
Резервный репозиторий публичных сниппетов из [gist.github.com/anthonyaxenov](https://gist.github.com/anthonyaxenov).
|
||||
|
||||
Оригинальные сниппеты в первую очередь я изменяю именно в гистах, потом здесь.
|
||||
|
||||
|
104
cfg/.bash_aliases
Normal file
104
cfg/.bash_aliases
Normal file
@ -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
|
||||
}
|
102
cfg/.gitconfig
102
cfg/.gitconfig
@ -1,88 +1,20 @@
|
||||
# https://gist.github.com/anthonyaxenov/020b25ea53701d82902a7acfb557866c
|
||||
# ...прочие настройки
|
||||
[core]
|
||||
editor = nano
|
||||
autocrlf = input
|
||||
[user]
|
||||
# ...
|
||||
# signingkey = <key>
|
||||
# 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 # список сабмодулей
|
||||
[core]
|
||||
editor = nano
|
||||
autocrlf = input
|
||||
[remote "origin"]
|
||||
prune = true
|
||||
[include]
|
||||
path = path/to/git_aliases
|
||||
|
86
cfg/git_aliases
Normal file
86
cfg/git_aliases
Normal file
@ -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 # список сабмодулей
|
@ -131,4 +131,4 @@ class BasicTestCase extends TestCase
|
||||
$this->assertArrayNotHasKey($key, $array);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
35
php/here.php
Normal file
35
php/here.php
Normal file
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Returns caller class/file name, function and line where current
|
||||
*
|
||||
* Potentially doesn't cover all cases, but is simple and pretty handy for use in frameworks.
|
||||
*
|
||||
* @param bool $as_array result as array or string in this format: `<file|class>:<func>():<line>`
|
||||
* @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
|
26
shell/dc
Normal file
26
shell/dc
Normal file
@ -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 >/dev/null 2>&1 & disown
|
||||
elif which gnome-open > /dev/null; then
|
||||
gnome-open "$1" </dev/null >/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
|
607
shell/helpers.sh
Normal file
607
shell/helpers.sh
Normal file
@ -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 >/dev/null 2>&1 & disown
|
||||
elif which gnome-open > /dev/null; then
|
||||
gnome-open "$1" </dev/null >/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
|
19
shell/inotifywait-cp/inotifywait-cp.service
Normal file
19
shell/inotifywait-cp/inotifywait-cp.service
Normal file
@ -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
|
59
shell/inotifywait-cp/inotifywait-cp.sh
Normal file
59
shell/inotifywait-cp/inotifywait-cp.sh
Normal file
@ -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========================="
|
124
shell/io.sh
124
shell/io.sh
@ -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"
|
157
shell/php
Normal file
157
shell/php
Normal file
@ -0,0 +1,157 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Welcome to amusement park!
|
||||
|
||||
[[ "$1" = '--help' ]] || [[ "$1" = '-h' ]] && cat <<EOF && exit
|
||||
NetBeans docker wrapper for php
|
||||
===============================
|
||||
Anthony Axenov (c) 2023, The MIT License
|
||||
https://axenov.dev
|
||||
https://opensource.org/license/mit
|
||||
Replacement host php interpreter with dockerized one to run & debug cli php scripts.
|
||||
Usage:
|
||||
./$(basename $0) --container=<NAME> [--map=<PATH1>:<PATH2>] [PHP_ARGS] <SCRIPT> [SCRIPT_ARGS]
|
||||
Arguments:
|
||||
--container : docker container where your SCRIPT is located. Required.
|
||||
--map : sources path mapped from the host to container. Not required.
|
||||
PATH1 is an absolute path to php sources directory on the host.
|
||||
PATH2 is an absolute path of the same directory inside of container.
|
||||
Delimiter ':' is required. If PATH1, PATH2 or delimiter is missed
|
||||
or value is empty then error will be thrown.
|
||||
PHP_ARGS : arguments you can pass to real php interpreter according to its --help.
|
||||
Not required.
|
||||
SCRIPT : a path to script file (.php) to be executed in container. Required.
|
||||
Note that this file must exist inside or be available from that container.
|
||||
SCRIPT_ARGS : arguments to call your script with. They will be passed to script as is.
|
||||
Not required.
|
||||
Read this article to know how to set this helper as interpreter for NetBeans:
|
||||
ru: https://axenov.dev/netbeans-php-docker-xdebug-cli
|
||||
en: https://axenov.dev/en/netbeans-php-docker-xdebug-cli-en
|
||||
EOF
|
||||
|
||||
pwd=$(pwd) # current working directory
|
||||
cmdline=($@) # copy currently called command line to array
|
||||
collect_php_args=1 # should we collect php args or script ones?
|
||||
quiet=0 # should we print some useful data before executing?
|
||||
|
||||
# find a path where this wrapper is located
|
||||
wrapper_dir="$(dirname $0)"
|
||||
|
||||
# find a path where project is probably located
|
||||
project_dir="$(dirname $wrapper_dir)"
|
||||
|
||||
# here we check if this wrapper is global or local
|
||||
# but if it is set as global from nbproject dir of
|
||||
# current project then it is not detected as global
|
||||
# anyway behavior will be correct
|
||||
nbproject="$(basename $wrapper_dir)"
|
||||
[ "$nbproject" = 'nbproject' ] && is_global=0 || is_global=1
|
||||
|
||||
# prepare new array to collect php args
|
||||
declare -a php_cmd=("docker" "exec")
|
||||
|
||||
# and another one for script args
|
||||
declare -a script_args=()
|
||||
|
||||
# and one more for directory mapping
|
||||
declare -a map_arr=()
|
||||
|
||||
# iterate over arguments we received from netbeans
|
||||
for arg in "${cmdline[@]}"; do
|
||||
|
||||
# if this is a container name
|
||||
if [ "${arg::11}" = '--container' ]; then
|
||||
container="${arg:12}" # save it
|
||||
php_cmd+=("$container" 'php') # add php itself
|
||||
continue # jump to next iteration
|
||||
fi
|
||||
|
||||
# if this is a path map
|
||||
if [ "${arg::5}" = '--map' ]; then
|
||||
map="${arg:6}" # save it
|
||||
map_arr=(${map//:/ }) # split it and check if it is correct
|
||||
if [ -z "${map_arr[0]}" ] || [ -z "${map_arr[1]}" ]; then
|
||||
echo "ERROR: directory map is incorrect!"
|
||||
echo "Use $0 --help to get info about how to use this wrapper."
|
||||
echo "Exit code 3."
|
||||
exit 3
|
||||
fi
|
||||
continue # jump to next iteration
|
||||
fi
|
||||
|
||||
# if this is a container name
|
||||
if [ "${arg::7}" = '--quiet' ]; then
|
||||
quiet=1
|
||||
continue # jump to next iteration
|
||||
fi
|
||||
|
||||
# if this is an absolute path to a script file
|
||||
if [ -f "$arg" ]; then
|
||||
# make its path correct for container
|
||||
if [ "$map" ]; then # when paths are mapped
|
||||
# remove first part of map from an absolute filepath and append result to second map part
|
||||
filepath="${map_arr[1]}${arg##${map_arr[0]}}"
|
||||
else # when paths are NOT mapped
|
||||
# remove project path from absolute filepath
|
||||
filepath="${arg##$project_dir/}"
|
||||
fi
|
||||
php_cmd+=("$filepath") # append php args with filepath
|
||||
collect_php_args=0 # now we need to collect script args
|
||||
continue # jump to next iteration
|
||||
fi
|
||||
|
||||
if [ "$collect_php_args" = 1 ]; then # if we collect php args
|
||||
php_cmd+=("$arg") # add current arg to php args as is
|
||||
continue # jump to next iteration
|
||||
fi
|
||||
|
||||
script_args+=("$arg") # otherwise add current arg to script args as is
|
||||
done
|
||||
|
||||
# docker container name is required so we must halt here if there is no one
|
||||
if [ -z "$container" ]; then
|
||||
echo "ERROR: no docker container is specified!" >&2
|
||||
echo "Use $0 --help to get info about how to use this wrapper." >&2
|
||||
echo "Exit code 1." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# path to php script is also required so we must halt here too if there is no one
|
||||
if [ -z "$filepath" ]; then
|
||||
echo "ERROR: no script filepath is specified!" >&2
|
||||
echo "Use $0 --help to get info about how to use this wrapper." >&2
|
||||
echo "Exit code 2." >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
cmdline="${php_cmd[*]} ${script_args[*]}" # make a command to execute
|
||||
|
||||
# print some important data collected above
|
||||
if [ "$quiet" = 0 ]; then
|
||||
echo "NetBeans docker wrapper for php"
|
||||
echo "==============================="
|
||||
echo -e "Container name: $container"
|
||||
echo -e "Script path: $filepath"
|
||||
echo -e "Directory mapping: ${map:-(none)}"
|
||||
echo -e "Command line:\n$cmdline\n"
|
||||
fi
|
||||
|
||||
# some debug output
|
||||
# echo "=== some debug output ========="
|
||||
# cat <<EOF | column -t
|
||||
# is_global $is_global
|
||||
# container $container
|
||||
# pwd $pwd
|
||||
# wrapper_dir $wrapper_dir
|
||||
# nbproject $nbproject
|
||||
# project_dir $project_dir
|
||||
# map $map
|
||||
# map_arr[0] ${map_arr[0]}
|
||||
# map_arr[1] ${map_arr[1]}
|
||||
# filepath $filepath
|
||||
# EOF
|
||||
# echo "==============================="
|
||||
|
||||
$cmdline # execute
|
||||
|
||||
# that's folks!
|
106
shell/quick-backup.sh
Normal file
106
shell/quick-backup.sh
Normal file
@ -0,0 +1,106 @@
|
||||
#!/bin/bash
|
||||
#####################################################################
|
||||
# #
|
||||
# Stupidly simple backup script for own projects #
|
||||
# #
|
||||
# Author: Anthony Axenov (Антон Аксенов) #
|
||||
# Version: 1.0 #
|
||||
# License: WTFPLv2 More info: https://axenov.dev/?p=1423 #
|
||||
# #
|
||||
#####################################################################
|
||||
|
||||
# database credentials ==============================================
|
||||
|
||||
DBUSER=
|
||||
DBPASS=
|
||||
DBNAME=
|
||||
DBCHARSET="utf8"
|
||||
|
||||
# date formats ======================================================
|
||||
|
||||
FMT_DT_DIR="%Y.%m.%d" # 2021.03.19
|
||||
FMT_DT_FILE="%H.%M" # 08.24
|
||||
FMT_DT_LOG="%H:%M:%S" # 08:24:15.168149413
|
||||
|
||||
# local storage =====================================================
|
||||
|
||||
LOCAL_BAK_DIR="/backup/$(date +$FMT_DT_DIR)"
|
||||
|
||||
# database backup file
|
||||
LOCAL_SQL_FILE="$(date +$FMT_DT_FILE)-db.sql.gz"
|
||||
LOCAL_SQL_PATH="$LOCAL_BAK_DIR/$LOCAL_SQL_FILE"
|
||||
|
||||
# project path and backup file
|
||||
LOCAL_SRC_DIR="/var/www/"
|
||||
LOCAL_SRC_FILE="$(date +$FMT_DT_FILE)-src.tar.gz"
|
||||
LOCAL_SRC_PATH="$LOCAL_BAK_DIR/$LOCAL_SRC_FILE"
|
||||
|
||||
# log file
|
||||
LOG_FILE="$(date +$FMT_DT_FILE).log"
|
||||
LOG_PATH="$LOCAL_BAK_DIR/$LOG_FILE"
|
||||
|
||||
log() {
|
||||
echo -e "[$(date +$FMT_DT_LOG)] $*" | tee -a "$LOG_PATH"
|
||||
}
|
||||
|
||||
# remote storage ====================================================
|
||||
|
||||
REMOTE_HOST="user@example.com"
|
||||
REMOTE_BAK_DIR="/backup/$(date +$FMT_DT_DIR)"
|
||||
REMOTE_SQL_PATH="$REMOTE_BAK_DIR/$LOCAL_SQL_FILE"
|
||||
REMOTE_SRC_PATH="$REMOTE_BAK_DIR/$LOCAL_SRC_FILE"
|
||||
REMOTE_LOG_PATH="$REMOTE_BAK_DIR/$LOG_FILE"
|
||||
|
||||
# start =============================================================
|
||||
|
||||
echo
|
||||
log "Start ----------------------------------------------------------------"
|
||||
log "Initialized parameters:"
|
||||
log "\tDB_USER\t\t= $DB_USER"
|
||||
log "\tDB_NAME\t\t= $DB_NAME"
|
||||
log "\tDB_CHARSET\t= $DB_CHARSET"
|
||||
log "\tLOCAL_SRC_DIR\t= $LOCAL_SRC_DIR"
|
||||
log "\tLOCAL_SRC_PATH\t= $LOCAL_SRC_PATH"
|
||||
log "\tLOCAL_SQL_PATH\t= $LOCAL_SQL_PATH"
|
||||
log "\tLOG_PATH\t= $LOG_PATH"
|
||||
log "\tREMOTE_HOST\t= $REMOTE_HOST"
|
||||
log "\tREMOTE_SQL_PATH\t= $REMOTE_SQL_PATH"
|
||||
log "\tREMOTE_SRC_PATH\t= $REMOTE_SRC_PATH"
|
||||
log "\tREMOTE_LOG_PATH\t= $REMOTE_LOG_PATH"
|
||||
|
||||
mkdir -p $LOCAL_BAK_DIR
|
||||
log "Created local dir: $LOCAL_BAK_DIR"
|
||||
|
||||
ssh $REMOTE_HOST mkdir -p $REMOTE_BAK_DIR
|
||||
log "Created remote dir: $REMOTE_BAK_DIR"
|
||||
|
||||
log "1/4 Dumping DB: $DBNAME..."
|
||||
mysqldump \
|
||||
--user="$DBUSER" \
|
||||
--password="$DBPASS" \
|
||||
--default-character-set="$DBCHARSET" \
|
||||
--opt \
|
||||
--quick \
|
||||
"$DBNAME" | gzip > "$LOCAL_SQL_PATH"
|
||||
# --opt Same as --add-drop-table, --add-locks, --create-options,
|
||||
# --quick, --extended-insert, --lock-tables, --set-charset,
|
||||
# and --disable-keys
|
||||
[ $? -gt 0 ] && log "ERROR: failed to create dump. Exit-code: $?" || log "\t- OK"
|
||||
|
||||
log "2/4 Sending database backup to $REMOTE_HOST..."
|
||||
rsync --progress "$LOCAL_SQL_PATH" "$REMOTE_HOST:$REMOTE_SQL_PATH"
|
||||
[ $? -gt 0 ] && log "ERROR: failed to send database backup. Exit-code: $?" || log "\t- OK"
|
||||
|
||||
log "3/4 Compressing project dir: $LOCAL_SRC_DIR..."
|
||||
tar -zcf "$LOCAL_SRC_PATH" "$LOCAL_SRC_DIR"
|
||||
[ $? -gt 0 ] && log "ERROR: failed to compress project. Exit-code: $?" || log "\t- OK"
|
||||
|
||||
log "4/4 Sending project backup to ${REMOTE_HOST}..."
|
||||
rsync --progress "$LOCAL_SRC_PATH" "$REMOTE_HOST:$REMOTE_SRC_PATH"
|
||||
[ $? -gt 0 ] && log "ERROR: failed to send project backup. Exit-code: $?" || log "\t- OK"
|
||||
|
||||
rsync --progress "$LOG_PATH" "$REMOTE_HOST:$REMOTE_LOG_PATH"
|
||||
|
||||
log "Finish!"
|
||||
log "Used space: $(du -h "$LOCAL_BAK_DIR" | tail -n1)"
|
||||
log "Free space: $(df -h | tail -n1 | awk '{print $4}')"
|
341
shell/s3-backup.sh
Normal file
341
shell/s3-backup.sh
Normal file
@ -0,0 +1,341 @@
|
||||
#!/bin/bash
|
||||
#####################################################################
|
||||
# #
|
||||
# Stupidly simple backup script for own projects #
|
||||
# #
|
||||
# Author: Anthony Axenov (Антон Аксенов) #
|
||||
# Version: 1.2 #
|
||||
# License: WTFPLv2 More info (RU): https://axenov.dev/?p=1272 #
|
||||
# #
|
||||
#####################################################################
|
||||
|
||||
# use remote storages ===============================================
|
||||
|
||||
USE_SSH=1
|
||||
USE_S3=1
|
||||
|
||||
# database credentials ==============================================
|
||||
|
||||
DBUSER=
|
||||
DBPASS=
|
||||
DBNAME=
|
||||
DBCHARSET="utf8"
|
||||
|
||||
# dates for file structure ==========================================
|
||||
|
||||
TODAY_DIR="$(date +%Y.%m.%d)"
|
||||
TODAY_FILE="$(date +%H.%M)"
|
||||
|
||||
# local storage =====================================================
|
||||
|
||||
LOCAL_BAK_DIR="/backup"
|
||||
LOCAL_BAK_PATH="$LOCAL_BAK_DIR/$TODAY_DIR"
|
||||
|
||||
# database backup file
|
||||
LOCAL_SQL_FILE="$TODAY_FILE-db.sql.gz"
|
||||
LOCAL_SQL_PATH="$LOCAL_BAK_PATH/$LOCAL_SQL_FILE"
|
||||
|
||||
# project path and backup file
|
||||
LOCAL_SRC_DIR="/var/www/html"
|
||||
LOCAL_SRC_FILE="$TODAY_FILE-src.tar.gz"
|
||||
LOCAL_SRC_PATH="$LOCAL_BAK_PATH/$LOCAL_SRC_FILE"
|
||||
|
||||
# log file
|
||||
LOG_FILE="$TODAY_FILE.log"
|
||||
LOG_PATH="$LOCAL_BAK_PATH/$LOG_FILE"
|
||||
|
||||
# remote storages ===================================================
|
||||
|
||||
SSH_HOST="user@example.com"
|
||||
SSH_BAK_DIR="/backup"
|
||||
SSH_BAK_PATH="$SSH_BAK_DIR/$TODAY_DIR"
|
||||
SSH_SQL_FILE="$SSH_BAK_PATH/$LOCAL_SQL_FILE"
|
||||
SSH_SRC_FILE="$SSH_BAK_PATH/$LOCAL_SRC_FILE"
|
||||
SSH_LOG_FILE="$SSH_BAK_PATH/$LOG_FILE"
|
||||
|
||||
S3_BUCKET="s3://my.bucket"
|
||||
S3_DIR="$S3_BUCKET/$TODAY_DIR"
|
||||
S3_SQL_FILE="$S3_DIR/$LOCAL_SQL_FILE"
|
||||
S3_SRC_FILE="$S3_DIR/$LOCAL_SRC_FILE"
|
||||
S3_LOG_FILE="$S3_DIR/$LOG_FILE"
|
||||
|
||||
# autoremove ========================================================
|
||||
|
||||
# time to live on different storages
|
||||
TTL_LOCAL=3
|
||||
TTL_SSH=7
|
||||
TTL_S3=60
|
||||
|
||||
# autoremove flags
|
||||
CLEAR_SSH=1
|
||||
CLEAR_S3=1
|
||||
|
||||
# notifications =====================================================
|
||||
|
||||
USE_NTFY=1
|
||||
NTFY_TITLE="Backup script"
|
||||
NTFY_CHANNEL=
|
||||
|
||||
#====================================================================
|
||||
#
|
||||
# Functions used for the whole backup flow
|
||||
#
|
||||
#====================================================================
|
||||
|
||||
# prints arguments to stdout and into log file
|
||||
log() {
|
||||
echo -e "[$(date +%H:%M:%S)] $*" | tee -a "$LOG_PATH"
|
||||
}
|
||||
|
||||
# sends notification with information
|
||||
ntfy_info() {
|
||||
[ $USE_NTFY == 1 ] && ntfy send \
|
||||
--title "$NTFY_TITLE" \
|
||||
--message "$1" \
|
||||
--priority 1 \
|
||||
"$NTFY_CHANNEL"
|
||||
}
|
||||
|
||||
# sends notification with warning
|
||||
ntfy_warn() {
|
||||
[ $USE_NTFY == 1 ] && ntfy send \
|
||||
--title "$NTFY_TITLE" \
|
||||
--tags "warning" \
|
||||
--message "$1" \
|
||||
--priority 5 \
|
||||
"$NTFY_CHANNEL"
|
||||
}
|
||||
|
||||
# prints initialized parameters
|
||||
show_params() {
|
||||
log "Initialized parameters:"
|
||||
|
||||
log "├ [ Remotes ]"
|
||||
log "│\t├ USE_SSH = $USE_SSH"
|
||||
[ $USE_SSH == 1 ] && log "│\t├ SSH_HOST = $SSH_HOST"
|
||||
log "│\t├ USE_S3 = $USE_S3"
|
||||
[ $USE_S3 == 1 ] && log "│\t├ S3_BUCKET = $S3_BUCKET"
|
||||
|
||||
log "├ [ Database ]"
|
||||
log "│\t├ DBUSER = $DBUSER"
|
||||
log "│\t├ DBNAME = $DBNAME"
|
||||
log "│\t├ DBCHARSET = $DBCHARSET"
|
||||
log "│\t├ LOCAL_SQL_PATH = $LOCAL_SQL_PATH"
|
||||
[ $USE_SSH == 1 ] && log "│\t├ SSH_SQL_FILE = $SSH_SQL_FILE"
|
||||
[ $USE_S3 == 1 ] && log "│\t├ S3_SQL_FILE = $S3_SQL_FILE"
|
||||
|
||||
log "├ [ Sources ]"
|
||||
log "│\t├ LOCAL_SRC_DIR = $LOCAL_SRC_DIR"
|
||||
log "│\t├ LOCAL_SRC_PATH = $LOCAL_SRC_PATH"
|
||||
[ $USE_SSH == 1 ] && log "│\t├ SSH_SRC_FILE = $SSH_SRC_FILE"
|
||||
[ $USE_S3 == 1 ] && log "│\t├ S3_SRC_FILE = $S3_SRC_FILE"
|
||||
|
||||
log "├ [ Log ]"
|
||||
log "│\t├ LOG_PATH = $LOG_PATH"
|
||||
[ $USE_SSH == 1 ] && log "│\t├ SSH_LOG_FILE = $SSH_LOG_FILE"
|
||||
[ $USE_S3 == 1 ] && log "│\t├ S3_LOG_FILE = $S3_LOG_FILE"
|
||||
|
||||
log "├ [ Autoclear ]"
|
||||
log "│\t├ TTL_LOCAL = $TTL_LOCAL"
|
||||
[ $USE_SSH == 1 ] && {
|
||||
log "│\t├ CLEAR_SSH = $CLEAR_SSH"
|
||||
log "│\t├ TTL_SSH = $TTL_SSH"
|
||||
}
|
||||
[ $USE_S3 == 1 ] && {
|
||||
log "│\t├ CLEAR_S3 = $CLEAR_S3"
|
||||
log "│\t├ TTL_S3 = $TTL_S3"
|
||||
}
|
||||
|
||||
log "└ [ ntfy ]"
|
||||
log "\t├ USE_NTFY = $USE_NTFY"
|
||||
[ $USE_NTFY == 1 ] && log "\t├ NTFY_TITLE = $NTFY_TITLE"
|
||||
[ $USE_NTFY == 1 ] && log "\t└ NTFY_CHANNEL = $NTFY_CHANNEL"
|
||||
}
|
||||
|
||||
# initializes directories for backup
|
||||
init_dirs() {
|
||||
if [ ! -d "$LOCAL_BAK_PATH" ]; then
|
||||
mkdir -p $LOCAL_BAK_PATH
|
||||
fi
|
||||
[ $USE_SSH == 1 ] && ssh $SSH_HOST "mkdir -p $SSH_BAK_PATH"
|
||||
}
|
||||
|
||||
# clears old local backups
|
||||
clear_local_backups() {
|
||||
log "\tLocal:"
|
||||
log $(find "$LOCAL_BAK_DIR" -type d -mtime +"$TTL_LOCAL" | sort)
|
||||
find "$LOCAL_BAK_DIR" -type d -mtime +"$TTL_LOCAL" | xargs rm -rf
|
||||
}
|
||||
|
||||
# clears old backups on remote ssh storage
|
||||
clear_ssh_backups() {
|
||||
if [ $USE_SSH == 1 ] && [ $CLEAR_SSH == 1 ]; then
|
||||
log "\tSSH:"
|
||||
log $(ssh "$SSH_HOST" "find $SSH_BAK_DIR -type d -mtime +$TTL_SSH" | sort)
|
||||
ssh "$SSH_HOST" "find $SSH_BAK_DIR -type d -mtime +$TTL_SSH | xargs rm -rf"
|
||||
else
|
||||
log "\tSSH: disabled (\$USE_SSH, \$CLEAR_SSH)"
|
||||
fi
|
||||
}
|
||||
|
||||
# clears backups on remote s3 storage
|
||||
clear_s3_backups() {
|
||||
# https://gist.github.com/JProffitt71/9044744?permalink_comment_id=3539681#gistcomment-3539681
|
||||
if [ $USE_S3 == 1 ] && [ $CLEAR_S3 == 1 ]; then
|
||||
log "\tS3:"
|
||||
OLDER_THAN=$(date -d "$TTL_S3 days ago" "+%s")
|
||||
s3cmd ls -r $S3_DIR | while read -r line; do
|
||||
FILETIME=$(echo "$line" | awk {'print $1" "$2'})
|
||||
FILETIME=$(date -d "$FILETIME" "+%s")
|
||||
if [[ $FILETIME -le $OLDER_THAN ]]; then
|
||||
FILEPATH=$(echo "$line" | awk {'print $4'})
|
||||
if [ $FILEPATH != "" ]; then
|
||||
log "$line"
|
||||
s3cmd del $FILEPATH
|
||||
fi
|
||||
fi
|
||||
done
|
||||
else
|
||||
log "\tS3: disabled (\$USE_S3 + \$CLEAR_S3)"
|
||||
fi
|
||||
}
|
||||
|
||||
# clears old backups
|
||||
clear_backups() {
|
||||
echo
|
||||
log "1/7 Removing old backups..."
|
||||
clear_local_backups
|
||||
clear_ssh_backups
|
||||
clear_s3_backups
|
||||
}
|
||||
|
||||
# makes archive with database dump
|
||||
backup_db() {
|
||||
echo
|
||||
log "2/7 Dumping DB: $DBNAME..."
|
||||
mysqldump \
|
||||
--user=$DBUSER \
|
||||
--password=$DBPASS \
|
||||
--opt \
|
||||
--default-character-set=$DBCHARSET \
|
||||
--quick \
|
||||
$DBNAME | gzip > $LOCAL_SQL_PATH
|
||||
if [ $? == 0 ]; then
|
||||
log "\t- OK"
|
||||
send_db_ssh
|
||||
send_db_s3
|
||||
else
|
||||
log "\t- ERROR: failed to create dump. Exit-code: $?"
|
||||
ntfy_warn "ERROR: failed to create dump"
|
||||
log "3/7 Sending database backup to $SSH_HOST... skipped"
|
||||
log "4/7 Sending database backup to $S3_DIR... skipped"
|
||||
fi
|
||||
}
|
||||
|
||||
# sends database archive into ssh remote storage
|
||||
send_db_ssh() {
|
||||
echo
|
||||
log "3/7 Sending database backup to $SSH_HOST..."
|
||||
if [ $USE_SSH == 1 ]; then
|
||||
rsync --progress "$LOCAL_SQL_PATH" "$SSH_HOST:$SSH_SQL_FILE"
|
||||
if [ $? == 0 ]; then
|
||||
log "\t- OK"
|
||||
else
|
||||
log "\t- ERROR: failed to send DB backup to $SSH_HOST. Exit-code: $?"
|
||||
ntfy_warn "ERROR: failed to send DB backup to $SSH_HOST"
|
||||
fi
|
||||
else
|
||||
log "\t- disabled (\$USE_SSH)"
|
||||
fi
|
||||
}
|
||||
|
||||
# sends database archive into s3 remote storage
|
||||
send_db_s3() {
|
||||
echo
|
||||
log "4/7 Sending database backup to $S3_DIR..."
|
||||
if [ $USE_S3 == 1 ]; then
|
||||
s3cmd put "$LOCAL_SQL_PATH" "$S3_SQL_FILE"
|
||||
if [ $? == 0 ]; then
|
||||
log "\t- OK"
|
||||
else
|
||||
log "\t- ERROR: failed to send DB backup to $S3_DIR. Exit-code: $?"
|
||||
ntfy_warn "ERROR: failed to send DB backup to $S3_DIR"
|
||||
fi
|
||||
else
|
||||
log "\t- disabled (\$USE_SSH)"
|
||||
fi
|
||||
}
|
||||
|
||||
# makes archive with project sources
|
||||
backup_src() {
|
||||
echo
|
||||
log "5/7 Compressing project dir: $LOCAL_SRC_DIR..."
|
||||
tar -zcf "$LOCAL_SRC_PATH" "$LOCAL_SRC_DIR"
|
||||
if [ $? == 0 ]; then
|
||||
log "\t- OK"
|
||||
send_src_ssh
|
||||
send_src_s3
|
||||
else
|
||||
log "\t- ERROR: failed to compress project. Exit-code: $?"
|
||||
ntfy_warn "ERROR: failed to compress project"
|
||||
log "6/7 Sending project backup to $SSH_HOST... skipped"
|
||||
log "7/7 Sending project backup to $S3_DIR... skipped"
|
||||
fi
|
||||
}
|
||||
|
||||
# sends sources archive into ssh remote storage
|
||||
send_src_ssh() {
|
||||
echo
|
||||
log "6/7 Sending project backup to $SSH_HOST..."
|
||||
if [ $USE_SSH == 1 ]; then
|
||||
rsync --progress "$LOCAL_SRC_PATH" "$SSH_HOST:$SSH_SRC_FILE"
|
||||
if [ $? == 0 ]; then
|
||||
log "\t- OK"
|
||||
else
|
||||
log "\t- ERROR: failed to send project backup to $SSH_HOST. Exit-code: $?"
|
||||
ntfy_warn "ERROR: failed to send project backup to $SSH_HOST"
|
||||
fi
|
||||
else
|
||||
log "\t- disabled"
|
||||
fi
|
||||
}
|
||||
|
||||
# sends sources archive into s3 remote storage
|
||||
send_src_s3() {
|
||||
echo
|
||||
log "7/7 Sending project backup to $S3_DIR..."
|
||||
s3cmd put "$LOCAL_SRC_PATH" "$S3_SRC_FILE"
|
||||
if [ $? == 0 ]; then
|
||||
log "\t- OK"
|
||||
else
|
||||
log "\t- ERROR: failed to send database backup to $S3_DIR. Exit-code: $?"
|
||||
ntfy_warn "ERROR: failed to send project backup to $S3_DIR"
|
||||
fi
|
||||
}
|
||||
|
||||
# prints used/free space on local storage
|
||||
show_finish() {
|
||||
echo
|
||||
log "Finish!"
|
||||
log "Used space: $(du -h "$LOCAL_BAK_PATH" | tail -n1)" # вывод размера папки с бэкапами за текущий день
|
||||
log "Free space: $(df -h "$LOCAL_BAK_PATH" | tail -n1 | awk '{print $4}')" # вывод свободного места на локальном диске
|
||||
echo
|
||||
}
|
||||
|
||||
# sends log file into both remote storage
|
||||
send_log() {
|
||||
[ $USE_SSH == 1 ] && rsync --progress "$LOG_PATH" "$SSH_HOST:$SSH_LOG_FILE"
|
||||
[ $USE_S3 == 1 ] && s3cmd put "$LOG_PATH" "$S3_LOG_FILE"
|
||||
}
|
||||
|
||||
# main flow =========================================================
|
||||
|
||||
log "Start ----------------------------------------------------------"
|
||||
show_params
|
||||
init_dirs
|
||||
clear_backups
|
||||
backup_db
|
||||
backup_src
|
||||
show_finish
|
||||
send_log
|
||||
ntfy_info "Finish!"
|
@ -1,19 +0,0 @@
|
||||
#!/bin/bash
|
||||
# https://gist.github.com/anthonyaxenov/925e2db217730a49f20600520b748039
|
||||
# Original: https://gist.github.com/akostadinov/33bb2606afe1b334169dfbf202991d36
|
||||
# The difference is that this func outputs stacktrace in reverse order (from top level to lower ones)
|
||||
function print_stacktrace () {
|
||||
STACK=""
|
||||
local i
|
||||
local stack_size=${#FUNCNAME[@]}
|
||||
echo "Stacktrace:"
|
||||
# skip this function and "MAIN non_file_source:0"
|
||||
for (( i=$stack_size-1; i>=1; 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
|
||||
echo -e "\n at $func $src:$linen"
|
||||
done
|
||||
}
|
@ -1,95 +0,0 @@
|
||||
#!/bin/bash
|
||||
# https://gist.github.com/anthonyaxenov/b17c6fbd7895c6049e1ceddc7c54bb5b
|
||||
. ./io.sh
|
||||
|
||||
########################################################
|
||||
# Тестировочные функции
|
||||
# Позволяют проверять результаты выполнения команд
|
||||
########################################################
|
||||
|
||||
# тестирует выполнение команды с указанными параметрами
|
||||
# $1 - команда для тестирования (обяз)
|
||||
expect_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"
|
||||
}
|
||||
|
||||
# использование:
|
||||
|
||||
# func1() {
|
||||
# return 0
|
||||
# }
|
||||
# func2() {
|
||||
# return 1
|
||||
# }
|
||||
# expect_exec "func1" # PASSED
|
||||
# expect_exec "func2" # FAILED
|
||||
# expect_exec "whoami" # PASSED
|
||||
|
||||
# тестирует выполнение команды с указанными параметрами и проверяет вывод
|
||||
# $1 - команда для тестирования (обяз)
|
||||
# $2 - ожидаемый вывод
|
||||
expect_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"
|
||||
}
|
||||
|
||||
# использование:
|
||||
|
||||
# func1() {
|
||||
# echo "some string"
|
||||
# }
|
||||
# func2() {
|
||||
# echo "another string"
|
||||
# }
|
||||
# expect_output "func1" "string" # PASSED
|
||||
# expect_output "func2" "some" # FAILED
|
||||
# expect_output "func2" "string" # PASSED
|
||||
|
||||
# тестирует выполнение команды с указанными параметрами и проверяет код выхода
|
||||
# $1 - команда для тестирования (обяз)
|
||||
# $2 - ожидаемый код выхода
|
||||
expect_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"
|
||||
}
|
||||
|
||||
# использование:
|
||||
|
||||
# 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
|
@ -1,50 +1,66 @@
|
||||
#!/bin/bash
|
||||
# Установка расширений vscode
|
||||
# https://gist.github.com/anthonyaxenov/7ba8d648d80fdaca95c4a5b579d214dd
|
||||
declare -a exts=(
|
||||
'af4jm.vscode-m3u'
|
||||
'ahmadalli.vscode-nginx-conf'
|
||||
'akamud.vscode-theme-onedark'
|
||||
'Anjali.clipboard-history'
|
||||
'AndrewButson.vscode-jwt-decoder'
|
||||
'anweber.statusbar-commands'
|
||||
'bmalehorn.shell-syntax'
|
||||
'Avetis.nord-palette'
|
||||
'baincd.mini-command-palettes'
|
||||
'bungcip.better-toml'
|
||||
'codezombiech.gitignore'
|
||||
'cweijan.vscode-redis-client'
|
||||
'deitry.apt-source-list-syntax'
|
||||
'DEVSENSE.composer-php-vscode'
|
||||
'DEVSENSE.phptools-vscode'
|
||||
'DEVSENSE.profiler-php-vscode'
|
||||
'DotJoshJohnson.xml'
|
||||
'dunstontc.vscode-go-syntax'
|
||||
'dustypomerleau.rust-syntax'
|
||||
'eamodio.gitlens'
|
||||
'Equinusocio.vsc-community-material-theme'
|
||||
'Equinusocio.vsc-material-theme'
|
||||
'equinusocio.vsc-material-theme-icons'
|
||||
'EditorConfig.EditorConfig'
|
||||
'esbenp.prettier-vscode'
|
||||
'formulahendry.auto-rename-tag'
|
||||
'formulahendry.vscode-mysql'
|
||||
'golang.go'
|
||||
'GrapeCity.gc-excelviewer'
|
||||
'HookyQR.beautify'
|
||||
'humao.rest-client'
|
||||
'jakebathman.mysql-syntax'
|
||||
'IronGeek.vscode-env'
|
||||
'jebbs.plantuml'
|
||||
'jeff-hykin.better-go-syntax'
|
||||
'jinsihou.diff-tool'
|
||||
'kenhowardpdx.vscode-gist'
|
||||
'mads-hartmann.bash-ide-vscode'
|
||||
'mamoru.vscode-fish-text'
|
||||
'mhutchie.git-graph'
|
||||
'mp.vscode-oracle-format'
|
||||
'mrmlnc.vscode-apache'
|
||||
'ms-azuretools.vscode-docker'
|
||||
'MS-CEINTL.vscode-language-pack-ru'
|
||||
'ms-python.python'
|
||||
'neilbrayfield.php-docblocker'
|
||||
'ms-vscode.hexeditor'
|
||||
'ms-vscode.makefile-tools'
|
||||
'neonxp.gotools'
|
||||
'nickdemayo.vscode-json-editor'
|
||||
'nico-castell.linux-desktop-file'
|
||||
'OPEN-RPC.OPEN-RPC'
|
||||
'PKief.material-icon-theme'
|
||||
'pranaygp.vscode-css-peek'
|
||||
'qcz.text-power-tools'
|
||||
'rangav.vscode-thunder-client'
|
||||
'rogalmic.bash-debug'
|
||||
'rogalmic.zsh-debug'
|
||||
'RomanPeshkov.vscode-text-tables'
|
||||
'rust-lang.rust-analyzer'
|
||||
'ryu1kn.partial-diff'
|
||||
'suntobright.vscode-sftp'
|
||||
'WallabyJs.quokka-vscode'
|
||||
'whatwedo.twig'
|
||||
'william-voyek.vscode-nginx'
|
||||
'serayuzgur.crates'
|
||||
'srmeyers.git-prefix'
|
||||
'sumneko.lua'
|
||||
'Syler.ignore'
|
||||
'Tyriar.lorem-ipsum'
|
||||
'vitorsalgado.vscode-redis'
|
||||
'waderyan.gitblame'
|
||||
'wayou.vscode-todo-highlight'
|
||||
'xyz.plsql-language'
|
||||
'yinfei.luahelper'
|
||||
'Yog.yog-plantuml-highlight'
|
||||
'yves.schema-tree'
|
||||
'yzane.markdown-pdf'
|
||||
'yzhang.markdown-all-in-one'
|
||||
'zgm.cuesheet'
|
||||
)
|
||||
for ext in "$exts[@]"; do
|
||||
code --install-extension $ext
|
||||
|
93
shell/ytdlcue.sh
Normal file
93
shell/ytdlcue.sh
Normal file
@ -0,0 +1,93 @@
|
||||
#!/bin/bash
|
||||
# CUE-sheet generator for youtube-dl
|
||||
|
||||
# Usage:
|
||||
# 0. Install 'jq' utility
|
||||
# 1. Download any audio file with metadata from YouTube or Youtube Music, e.g.
|
||||
# $ youtube-dl \
|
||||
# --extract-audio \
|
||||
# --audio-format flac \
|
||||
# --audio-quality 0 \
|
||||
# --format bestaudio \
|
||||
# --write-info-json \
|
||||
# --output "/tmp/ytm/%(playlist_title)s/%(channel)s - %(title)s.%(ext)s" \
|
||||
# https://www.youtube.com/watch?v=lVpDQnXz34M
|
||||
#
|
||||
# If audio file is already downloaded earlier then just fetch only its metadata:
|
||||
# $ youtube-dl \
|
||||
# --write-info-json \
|
||||
# --skip-download \
|
||||
# --output "/tmp/ytm/%(playlist_title)s/%(channel)s - %(title)s.%(ext)s" \
|
||||
# https://www.youtube.com/watch?v=lVpDQnXz34M
|
||||
#
|
||||
# 2. Audio and metadata files MUST be named exactly similar (except extenstion),
|
||||
# but it is not necessary to keep original names. Also they MUST be placed in
|
||||
# the same directory. Example:
|
||||
# /tmp/ytm/ABGT496.flac
|
||||
# /tmp/ytm/ABGT496.info.json
|
||||
#
|
||||
# 3. To create CUE file run ytdlcue with a path to audio file:
|
||||
# $ ytdlcue.sh /tmp/ytm/ABGT496.flac
|
||||
#
|
||||
# A new file will be created in the same directory:
|
||||
# /tmp/ytm/ABGT496.cue
|
||||
|
||||
installed() {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
! installed 'jq' && {
|
||||
echo "ERROR: you need to install jq!"
|
||||
exit 1
|
||||
}
|
||||
|
||||
audio_path="$1" # path to audiofile
|
||||
audio_file=`basename "$audio_path"` # audiofile name with extension
|
||||
audio_name=${audio_file%.*} # audiofile name without extension
|
||||
audio_ext=${audio_file##*.} # audiofile name extension
|
||||
path="`dirname "$audio_path"`/$audio_name" # path to audiofile and its name without ext
|
||||
json_path="$path.info.json" # path to json file with metadata created by youtube-dl
|
||||
cue_path="$path.cue" # path to cue sheet to be generated
|
||||
|
||||
# echo -e "audio_path:\t$audio_path"
|
||||
# echo -e "audio_file:\t$audio_file"
|
||||
# echo -e "audio_name:\t$audio_name"
|
||||
# echo -e "audio_ext:\t$audio_ext"
|
||||
# echo -e "path:\t\t$path"
|
||||
# echo -e "json_path:\t$json_path"
|
||||
# echo -e "cue_path:\t$cue_path"
|
||||
|
||||
[ ! -f "$audio_path" ] && {
|
||||
echo "ERROR: File not found: $audio_path"
|
||||
exit 2
|
||||
}
|
||||
[ ! -f "$json_path" ] && {
|
||||
echo "ERROR: File not found: $json_path"
|
||||
exit 3
|
||||
}
|
||||
|
||||
echo "PERFORMER `cat "$json_path" | jq -Mc '.channel'`" > "$cue_path"
|
||||
echo "TITLE `cat "$json_path" | jq -Mc '.title'`" >> "$cue_path"
|
||||
echo "FILE \"$audio_file\" ${audio_ext^^}" >> "$cue_path"
|
||||
|
||||
counter=1 # track counter (works only inside loop!)
|
||||
cat "$json_path" | jq -Mc '.chapters[]' \
|
||||
| while read chapter; do
|
||||
number=`printf %0.2d $counter` # pad current counter with zeros
|
||||
time=`echo "$chapter" | jq -Mc '.start_time'` # get initial start time in seconds
|
||||
time=`printf '%0.2d:%0.2d:00' $((time/60)) $((time%60))` # convert start time to minutes:seconds
|
||||
title=`echo "$chapter" | jq -Mc '.title' | sed -r "s#[\"]##g"` # get initial chapter title
|
||||
performer=`echo "$title" | cut -d "-" -f 1 | sed 's#^[[:space:]]*##g' | sed 's# *$##g'` # get and trim chapter's performer (before '-')
|
||||
title2=`echo "$title" | cut -d "-" -f 2 | sed 's#^[[:space:]]*##g' | sed 's# *$##g'` # get and trim chapter's title (after '-')
|
||||
#TODO: what if dash is not delimiter between performer and title?
|
||||
#TODO: take $title2 if $performer and (or?) $title2 are empty
|
||||
|
||||
printf "%-2sTRACK $number AUDIO\n" >> "$cue_path"
|
||||
printf "%-4sPERFORMER \"$performer\"\n" >> "$cue_path"
|
||||
printf "%-4sTITLE \"$title2\"\n" >> "$cue_path"
|
||||
printf "%-4sINDEX 01 $time\n" >> "$cue_path"
|
||||
|
||||
counter=`expr $counter + 1` # increase counter
|
||||
done
|
||||
echo "Done! Cue file:"
|
||||
echo "$cue_path"
|
Loading…
Reference in New Issue
Block a user