upd
This commit is contained in:
parent
423468a50b
commit
a3c6d296ab
@ -31,14 +31,13 @@
|
||||
PS1='\[\033[0;96m\]$(__git_ps1 "(%s) ")\[\033[0;92m\]\w \[\033[0;31m\]$(__exit_code_ps1 $?)\[\033[0;15m\]\$ '
|
||||
fi
|
||||
|
||||
На GitHub можно найти много всяких скриптов или даже целых "фремворков" типа `этого`_
|
||||
или `отдельные скрипты`_ для Git. А вы знали, что скрипт для PS1 и так `поставляется`_
|
||||
в пакете Git? Не вижу смысла не использовать его, если только вам не надо
|
||||
как-то иначе работать с PS1.
|
||||
На GitHub можно найти много всяких скриптов или даже целых "фремворков" типа
|
||||
`этого`_ или `отдельные скрипты`_ для Git. Однако, скрипт для PS1 и так
|
||||
`поставляется`_ в пакете Git.
|
||||
|
||||
Помимо **git-prompt.sh** я использую функцию ``__exit_code_ps1()``, которая просто
|
||||
печатает число, если оно не равно нулю. Это очень удобно — всегда видишь код выхода
|
||||
предыдущей запущенной команды, порой очень помогает при отладке скриптов.
|
||||
Помимо **git-prompt.sh** я использую функцию ``__exit_code_ps1()``, которая
|
||||
просто печатает число, если оно не равно нулю. Это очень удобно — всегда
|
||||
видишь код выхода команды. Порой это очень помогает при отладке скриптов.
|
||||
|
||||
В итоге всё это дело у меня выглядит вот так:
|
||||
|
||||
|
246
content/do_boring_backups.rst
Normal file
246
content/do_boring_backups.rst
Normal file
@ -0,0 +1,246 @@
|
||||
:title: Делаем скучные бэкапы
|
||||
:date: 8 Oct 22
|
||||
|
||||
=====================
|
||||
Делаем скучные бэкапы
|
||||
=====================
|
||||
|
||||
Бэкапы, в самом деле, нескучные (:
|
||||
|
||||
Мотивацией для написания очередного велосипеда стало то, что даже для, казалось
|
||||
бы, готовых инструментов приходится писать обвязки на шелле.
|
||||
|
||||
В какой-то момент мне надоело писать баш-портянки для бэкапа и я написал одну
|
||||
большую, которая умеет сразу много всего. Знакомьтесь — **boring_backup**.
|
||||
|
||||
> **Пакеты для Debian/Ubuntu, Arch и сорцы**
|
||||
`здесь <https://git.nxhs.cloud/ge/boring_backup/releases>`_.
|
||||
|
||||
Сначала о том, чем он *НЕ* является:
|
||||
|
||||
* Тупым линейным скриптом с вызовом tar и mysqldump.
|
||||
* Полным аналогом `restic <https://restic.net/>`_ и похожих утилит.
|
||||
* POSIX-совместимым скриптом. Хотелось бы, конечно, но это достаточно больно.
|
||||
Может быть когда-нибудь я перепишу код и оно заработает на dash/ash и прочих
|
||||
шеллах, но это будет уже другой скрипт.
|
||||
|
||||
**boring_backup** — это библиотека функций Bash и интерфейс командной строки
|
||||
для скриптов резервного копирования.
|
||||
|
||||
Вот что в ней есть:
|
||||
|
||||
* Обработка ошибок (wow!)
|
||||
* Логирование в файл или syslog (WOW!)
|
||||
* Парсер URI практически целиком совместимый с RFC 3986!
|
||||
* Готовые функции для запаковки файлов (tar) и снятия SQL-дампов (MySQL/MariaDB
|
||||
и PostgreSQL)
|
||||
|
||||
Логика работы утилиты крайне проста. Есть два массива данных — список URI
|
||||
исходных ресурсов, которые надо бэкапить и список URI хранилищ, куда предстоит
|
||||
бэкапиться.
|
||||
|
||||
Поддерживается определённый набор схем URI, каждую схему обрабатывает свой
|
||||
обработчик.
|
||||
|
||||
Cамый простой скрипт резервного копирования, который можно написать с
|
||||
помощью **boring_backup** выглядит так:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sources=(/home/john)
|
||||
targets=(/var/backups)
|
||||
|
||||
Сохраним это в файл под именем `test`.
|
||||
|
||||
Здесь очевидно дира /home/john будет скопирована в /var/backups, однако cперва
|
||||
она будет запакована в архив tar без сжатия. Запускаем бэкап:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
boring_backup test
|
||||
|
||||
Итого получим файл /var/backups/test_john_2022.10.08-1230.tar. Если мы хотим
|
||||
сжать архив, то добавим компрессию:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sources=(/home/john)
|
||||
targets=(/var/backups)
|
||||
compression=xz
|
||||
|
||||
Здесь значение переменной ``compression`` будет интерпретироваться так же как
|
||||
tar интерпретирует расширение архива с опцией ``--auto-compress``. То есть
|
||||
поддерживаются все утилиты, которыми tar умеет сжимать. Если переменная пуста
|
||||
или содержит что-то не то, то сжатие будет отключено.
|
||||
|
||||
Надо исключить лишние файлы из архива? Легко!
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sources=(/home/john)
|
||||
targets=(/var/backups)
|
||||
compression=xz
|
||||
tar_exclude=(.cache foo bar)
|
||||
|
||||
Для передачи опций для tar есть переменная ``tar_options`` в которой можно
|
||||
переопределить умолчание — ``-acf``.
|
||||
|
||||
Теперь добавим базу данных MySQL в бэкап. Записывается URI в формате, который
|
||||
ещё называют DSN (data source name):
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sources=(
|
||||
/home/john
|
||||
mysql://test_usr:%2C%23s%7BRGqH@localhost/test_db
|
||||
)
|
||||
targets=(/var/backups)
|
||||
compression=xz
|
||||
tar_exclude=(.cache foo bar)
|
||||
|
||||
Обратите внимание, что пароль пользователя закодирован, чтобы не возникло
|
||||
коллизий из-за наличия в нём спецсимволов. Закодировать пароль крайне просто,
|
||||
вот пример на Perl, который можно выполнить прямо в командной строке:
|
||||
|
||||
.. code-block:: perl
|
||||
|
||||
echo ',#s{RGqH' | perl -MURI::Escape -wlne 'print uri_escape $_'
|
||||
|
||||
Теперь в /var/backups окажется два файла — test_john_2022.10.08-1230.tar.xz
|
||||
и test_test_db_2022.10.08-1230.sql.xz. Сваливать все файлы в одну директорию
|
||||
может быть неудобно, поэтому давайте сохранять файлы в папки по датам.
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sources=(
|
||||
/home/john
|
||||
mysql://test_usr:%2C%23s%7BRGqH@localhost/test_db
|
||||
)
|
||||
today=$(date +%Y-%m-%d) # в результате даст дату yyyy-mm-dd
|
||||
make_target_dir=yes
|
||||
targets=(/var/backups/$today)
|
||||
compression=xz
|
||||
tar_exclude=(.cache foo bar)
|
||||
|
||||
Теперь пути до файлов будут выглядеть так:
|
||||
/var/backups/2022-10-08/test_john_2022.10.08-1230.tar.xz.
|
||||
|
||||
Теперь поробуем передать эти файлы ещё и на удалённое хранилище. Допустим на
|
||||
S3-совместимое объектное хранилище. Сперва придётся установить и настроить
|
||||
утилиту s3cmd, затем скрипт примет вид:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sources=(
|
||||
/home/john
|
||||
mysql://test_usr:%2C%23s%7BRGqH@localhost/test_db
|
||||
)
|
||||
today=$(date +%Y-%m-%d)
|
||||
make_target_dir=yes
|
||||
targets=(
|
||||
/var/backups/$today
|
||||
s3://my_bucket/$today
|
||||
)
|
||||
compression=xz
|
||||
tar_exclude=(.cache foo bar)
|
||||
|
||||
**boring_backup** сначала сделает бэкап и сохранит его в /var/backups и только
|
||||
затем передаст файлы в S3-хранилище (или любое другое). Можно указывать любое
|
||||
количество URI в targets и также в sources. Особенностью targets является то,
|
||||
что требуется хотя бы один URI со схемой `file`, куда будут сохраняться
|
||||
локальные бэкапы. Просто путь без указания схемы тоже считается за file.
|
||||
Следующие три записи полностью эквивалентны:
|
||||
|
||||
* /var/backups
|
||||
* file:/var/backups
|
||||
* file:///var/backups
|
||||
|
||||
Пойдём дальше.
|
||||
|
||||
Мы можем обрабатывать ошибки. Допишем в скрипт функцию `on_error`, которая
|
||||
будет нам отправлять письмо с фрагментом лога и текстом ошибки.
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sources=(
|
||||
/home/john
|
||||
mysql://test_usr:%2C%23s%7BRGqH@localhost/test_db
|
||||
)
|
||||
today=$(date +%Y-%m-%d)
|
||||
make_target_dir=yes
|
||||
targets=(
|
||||
/var/backups/$today
|
||||
s3://my_bucket/$today
|
||||
)
|
||||
compression=xz
|
||||
tar_exclude=(.cache foo bar)
|
||||
|
||||
email=me@example.com
|
||||
on_error() {
|
||||
local log_fragment err_message
|
||||
err_message="$*"
|
||||
log_fragment="$(grep -n 'Backup started' "$log_file" | tail -1 |
|
||||
cut -d ':' -f 1 | xargs -I {} tail -n +{} "$log_file")"
|
||||
printf \
|
||||
'Текст ошибки:\n%s\n\nЛог последнего бэкапа:\n%s' \
|
||||
"$err_message" "$log_fragment" |
|
||||
mail -s "$HOSTNAME: Ошибка при выполнении бэкапа" "$email"
|
||||
}
|
||||
|
||||
Также мы можем после копирование в удалённое хранилище удалять локальный
|
||||
бэкап. Список всех созданных за текущее выполнение скрипта бэкапов хранится
|
||||
в массиве ``backups``.
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
finalise() {
|
||||
log -p "\tClean up local backups"
|
||||
log -p "\tFiles to delete: ${backups[@]}"
|
||||
rm -rv -- "${backups[@]}"
|
||||
log -p "\tLocal backups deleted"
|
||||
}
|
||||
|
||||
Это лишь небольшая часть того, что можно реализовать. Полная документация
|
||||
есть `в виде мануала </cgi-bin/man.cgi?q=boring_backup&l=ru>`_.
|
||||
|
||||
Отмечу ещё несколько важных фич:
|
||||
|
||||
* Стандартную логику можно полностью переопределить, в том числе выкинуть
|
||||
необходимость сохранять локальный бэкап. Достаточно описать функцию с
|
||||
именем `backup`.
|
||||
* Можно выполнять действия перед бэкапом в функции `prepare` и после бэкапа
|
||||
— `finalise`.
|
||||
|
||||
Есть и вещи, которые вам не понравятся, но решение которых вероятно появится
|
||||
в слудующих версиях **boring_backup**:
|
||||
|
||||
* Концепция репозитория бэкапов не поддерживается. Файлы просто загружается
|
||||
туда куда вы укажете.
|
||||
* Из коробки можно делать только полные бэкапы.
|
||||
* Нет ротации бэкапов. Её нужно реализовывать самостоятельно.
|
||||
* Из коробки нет шифрования. GPG и кастомная функция `backup` вам в помощь.
|
||||
|
||||
В заключение дам ещё один пример скрипта для бэкапа приложения Gitea.
|
||||
Попробуйте сами разобраться что тут происходит.
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
sources=(.) # just pass validation
|
||||
targets=(.)
|
||||
today="$(date +%d_%b_%Y)"
|
||||
s3cmd_config=~/.s3cfg
|
||||
prepare() {
|
||||
systemctl stop gitea.service
|
||||
sleep 5
|
||||
}
|
||||
backup() {
|
||||
log -p "Dumping Gitea"
|
||||
su -c "/usr/local/bin/gitea dump -c /etc/gitea/app.ini \
|
||||
-f /home/git/.cache/gitea_dump.zip" - git 2>> "$log_file"
|
||||
backups+=(/home/git/.cache/gitea_dump.zip)
|
||||
tgt_s3cmd s3://mybucket/backups/gitea-$today
|
||||
}
|
||||
finalise() {
|
||||
systemctl start gitea.service
|
||||
rm -rv -- "${backups[@]}"
|
||||
}
|
189
content/posixish_functions.rst
Normal file
189
content/posixish_functions.rst
Normal file
@ -0,0 +1,189 @@
|
||||
:title: Несколько полезных функций на Bourne Shell
|
||||
:date: 18 Oct 22
|
||||
|
||||
==========================================
|
||||
Несколько полезных функций на Bourne Shell
|
||||
==========================================
|
||||
|
||||
Функции, описанные здесь можно применять в любой POSIX-совместимой оболочке.
|
||||
Протестировано в **dash**. Примеры на этой странице будут обновляться.
|
||||
|
||||
**1. Парсер аргументов в стиле GNU**
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
optval() {
|
||||
# GNU-style command line options parser
|
||||
#
|
||||
# Usage: optval "$1" "$2"
|
||||
#
|
||||
# Set variables:
|
||||
# OPT - option name
|
||||
# VAL - value
|
||||
# SFT - value for shift command
|
||||
|
||||
OPT="${1%%=*}"; VAL="${1#*=}"; SFT=1
|
||||
|
||||
if [ "$OPT" = "$VAL" ]; then
|
||||
if [ -n "$2" ] && [ "${2#"${2%%?}"}" != "-" ]; then
|
||||
VAL="$2"; SFT=2
|
||||
else
|
||||
unset VAL
|
||||
fi
|
||||
fi
|
||||
if [ -z "$VAL" ]; then
|
||||
echo Missing argument for option "$OPT" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
Эта функция позволит надёжно распарсить опции с обязательным аргументом. Ей
|
||||
по силам:
|
||||
|
||||
* **-o** *value*
|
||||
* **--option**\ =\ *value*
|
||||
* **--option**\ =\ *name=value*
|
||||
* **-option** *value*
|
||||
|
||||
..и вариации.
|
||||
|
||||
Непосредственно в POSIX-оболочке работать с парсером будет тяжко из-за
|
||||
отсутсвия массивов. Единственный доступный массив **$@** здесь занят. NULL в
|
||||
качестве разделителя для списка позиционных аргументов использовать не
|
||||
получится, поэтому ниже пример с двоеточием. В **Bash** таких проблем нет.
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
while [ "$#" -ne 0 ]; do
|
||||
case "$1" in
|
||||
-h|--help)
|
||||
echo "Usage: $0 [-chv] [--] arguments"
|
||||
exit 0
|
||||
;;
|
||||
-v|--version)
|
||||
echo 1.0.0
|
||||
exit 0
|
||||
;;
|
||||
-c|-c=*|--config|--config=*)
|
||||
optval "$1" "$2"
|
||||
config="$VAL"
|
||||
shift "$SFT"
|
||||
;;
|
||||
--) # end of options processing
|
||||
shift
|
||||
break
|
||||
;;
|
||||
-*)
|
||||
echo Unknown option "$1" >&2
|
||||
exit 1
|
||||
;;
|
||||
*) # Save positional arguments
|
||||
# In Bash better use ARGS+=("$1") instead
|
||||
ARGS="$1":"$ARGS"
|
||||
shift
|
||||
esac
|
||||
done
|
||||
|
||||
# Set positional arguments. It is not needed in Bash
|
||||
# In Bash you can use ARGS array directly or use set -- "${ARGS[@]}"
|
||||
OLDIFS="$IFS"; IFS=:
|
||||
# $arg must be unquoted!
|
||||
# shellcheck disable=SC2086
|
||||
for arg in $ARGS; do set -- $arg "$@"; done
|
||||
IFS="$OLDIFS"
|
||||
|
||||
echo Config: "$config"
|
||||
echo Positional arguments:
|
||||
for arg in "$@"; do; echo : "$arg"; done
|
||||
|
||||
**2. Интерактивный диалог**
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
confirm() {
|
||||
# Confirmation interactive dialog
|
||||
#
|
||||
# Usage: confirm "message"
|
||||
|
||||
[ -n "$ASSUME_NO" ] && return 1
|
||||
[ -n "$ASSUME_YES" ] && return 0
|
||||
|
||||
while true; do
|
||||
printf '%s (y/n) ' "$*"
|
||||
read -r REPLY
|
||||
case "$REPLY" in
|
||||
[Yy]|[Yy][Ee][Ss]) return 0;;
|
||||
[Nn]|[Nn][Oo]) return 1;;
|
||||
*) echo Please, answer y or n;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
Здесь предусмотрены переменные ``ASSUME_NO`` и ``ASSUME_YES`` для
|
||||
неинтерактивного режима. Автоматическое "Нет" и "Да" соответственно. Пример:
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
if confirm 'Proceed?'; then
|
||||
echo OK
|
||||
else
|
||||
echo Abort
|
||||
fi
|
||||
|
||||
**3. Версия Python**
|
||||
|
||||
Просто напечатает в STDOUT версию интерпретатора Python.
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
python_version() {
|
||||
# Get Python interpreter version in format 'major.minor.micro' e.g. '3.10.2'
|
||||
# Works with Python 2.x and 3.x.
|
||||
#
|
||||
# Usage: python_version [<path to interpreter>]
|
||||
|
||||
"${1:-python}" -c "import sys; v=sys.version_info; \
|
||||
print('%s.%s.%s' % (v.major, v.minor, v.micro))"
|
||||
}
|
||||
|
||||
**4. Открыть редактор по умолчанию**
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
default_editor() {
|
||||
# Edit file in user's default editor
|
||||
#
|
||||
# Usage: default_editor file
|
||||
|
||||
if [ -n "$EDITOR" ]; then
|
||||
"$EDITOR" "$@"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if hash select-editor >/dev/null 2>&1; then
|
||||
if [ -f ~/.selected_editor ]; then
|
||||
. ~/.selected_editor
|
||||
else
|
||||
select-editor
|
||||
. ~/.selected_editor
|
||||
fi
|
||||
"$SELECTED_EDITOR" "$@"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if hash editor >/dev/null 2>&1; then
|
||||
editor "$@"
|
||||
return 0
|
||||
else
|
||||
vi "$@" # fallback to vi
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
**5. Простой URL-decoder**
|
||||
|
||||
.. code-block:: shell
|
||||
|
||||
unescape_uri() {
|
||||
echo "$1" | sed 's/%/\\\\x/g' | xargs printf
|
||||
}
|
@ -8,7 +8,8 @@
|
||||
|
||||
* `rSW </rsw/>`_ -- генератор статических сайтов из reStructuredText
|
||||
* tui.sh -- библиотека элементов TUI для POSIX-совместимых оболочек
|
||||
* boring-backup -- Bash фреймворк для скриптов резервного копирования
|
||||
* `boring-backup <https://git.nxhs.cloud/ge/boring_backup>`_ -- Bash
|
||||
фреймворк для скриптов резервного копирования
|
||||
* `imgs <https://git.nxhs.cloud/ge/imgs>`_ -- минималистичный хостинг картинок
|
||||
* `vk-toot <https://git.nxhs.cloud/ge/vk-toot>`_ -- кросспостер VK -> Mastodon
|
||||
* `piglet <https://git.hxhs.cloud/ge/piglet>`_ -- клиент DNS API Porkbun
|
||||
|
253
content/terminal_muxers.rst
Normal file
253
content/terminal_muxers.rst
Normal file
@ -0,0 +1,253 @@
|
||||
:title: Терминальные мультиплексоры
|
||||
:date: 19 Oct 22
|
||||
|
||||
===========================
|
||||
Терминальные мультиплексоры
|
||||
===========================
|
||||
|
||||
Терминальный мультиплексор — это утилита, которая позволяет запускать
|
||||
несколько сессий оболочки в рамках одной сессии и также отсоединять
|
||||
собственную сессию от оболочки.
|
||||
|
||||
Для пояснения зачем это нужно я процитирую русскую Википедию:
|
||||
|
||||
Это полезно для работы с несколькими программами из командной строки, а
|
||||
также для запуска программ на удаленном сервере.
|
||||
|
||||
Возможно звучит сложно, но всё проще чем кажется. В этой заметке я попробую
|
||||
наглядно объяснить концепцию и приёмы работы с мультиплексорами терминала.
|
||||
|
||||
Сейчас распространено два мультиплексора терминала:
|
||||
|
||||
* `GNU screen <https://www.gnu.org/software/screen/>`_
|
||||
* `tmux <https://github.com/tmux/tmux/wiki>`_
|
||||
|
||||
Рано или поздно вы с одним из них столкнётесь.
|
||||
|
||||
Матчасть
|
||||
========
|
||||
|
||||
Вот основные концепции, которые использются в упомянутых утилитах.
|
||||
|
||||
Сессия
|
||||
Непосредственно сессия мультиплексора терминала. Сессий может быть
|
||||
множество, их можно создавать, закрывать, отсоединяться от них или
|
||||
присоединяться, задать имя.
|
||||
|
||||
Окно
|
||||
В любой сессии мультиплекcора есть как минимум одно окно. Если закрыть
|
||||
последнее окно, то закроется вся сессия. В каждом окне запускается новая
|
||||
сессия командной оболочки (Bash, Zsh и пр.).
|
||||
|
||||
Окон также может быть множество. К ним применимы те же действия, что и к
|
||||
сессиям, кроме отсоединения.
|
||||
|
||||
Между окнами можно переключаться с помощью горячих клавиш.
|
||||
|
||||
Область экрана (или панель)
|
||||
Каждое окно можно разбить на несколько областей. В каждой области будет
|
||||
запущена новая сессия оболочки.
|
||||
|
||||
Делить окно можно по вертикали и горизонтали. Размеры областей обычно
|
||||
можно отрегулировать. По умолчанию экран делится пополам.
|
||||
|
||||
Здесь надо отметить, что в **screen** понятия окно и область экрана
|
||||
смешаны. То есть область экрана приравнивается к отдельному окну, хотя
|
||||
они и отображаются на одном экране.
|
||||
|
||||
Итак, я подключаюсь к серверу по SSH с целью запустить там какую-то команду.
|
||||
После запуска команды я намерен отключиться от сервера. При этом команда
|
||||
должна продолжить выполняться. Я также должен увидеть результат её выполнения,
|
||||
когда вновь подключусь к серверу.
|
||||
|
||||
**1.** Подключаюсь по SSH:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
localhost server
|
||||
+------------+ +------------------+
|
||||
| ssh_client |----->| ssh_session |
|
||||
+------------+ +------------------+
|
||||
(you are here)----->| shell_session(0) |
|
||||
+------------------+
|
||||
|
||||
После успешного подключения по SSH я окажусь в сессии оболочки — **(0)**.
|
||||
При этом если оборвётся SSH-сессия, то я потеряю всё что в ней было.
|
||||
|
||||
**2.** Как только я запущу **screen** картинка примет следующий вид:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
localhost server
|
||||
+------------+ +------------------+
|
||||
| ssh_client |----->| ssh_session |
|
||||
+------------+ +------------------+
|
||||
| shell_session(0) |
|
||||
+------------------+
|
||||
| screen_session |
|
||||
+------------------+
|
||||
(you are here)----->| shell_session(1) |
|
||||
+------------------+
|
||||
|
||||
**3.** Для примера запущу **vim**:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
localhost server
|
||||
+------------+ +------------------+
|
||||
| ssh_client |----->| ssh_session |
|
||||
+------------+ +------------------+
|
||||
| shell_session(0) |
|
||||
+------------------+
|
||||
| screen_session |
|
||||
+------------------+
|
||||
(you are here)----->| shell_session(1) |
|
||||
+------------------+
|
||||
| vim |
|
||||
+------------------+
|
||||
|
||||
**4.** Теперь я отключусь (detach) от сессии **screen**:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
localhost server
|
||||
+------------+ +------------------+ +------------------+
|
||||
| ssh_client |----->| ssh_session | | screen_session |
|
||||
+------------+ +------------------+ +------------------+
|
||||
(you are here)----->| shell_session(0) | | shell_session(1) |
|
||||
+------------------+ +------------------+
|
||||
| vim |
|
||||
+------------------+
|
||||
|
||||
Сессия **screen** отсоединилась от оболочки **(0)**. При этом оболочка **(1)**
|
||||
продолжает работать. Сейчас можно отключиться от SSH.
|
||||
|
||||
Когда я подключусь к серверу вновь, то окажусть в ситуации как на схеме 4, но
|
||||
после выполнения команды **screen -r** всё вернётся к состоянию как на схеме 3
|
||||
и я смогу продолжить работу в **vim**.
|
||||
|
||||
.. admonition:: Как набирать комбинации клавиш
|
||||
|
||||
Вначале набрается модификатор, например ``Ctrl+a``, затем надо отпустить
|
||||
клавиши и нажать на клавишу команды, например ``?``.
|
||||
|
||||
screen
|
||||
======
|
||||
|
||||
**screen** просто работает. Он минималистичен и прост в использовании.
|
||||
|
||||
Запуск новой сессии:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
screen [-S имя_сессии]
|
||||
|
||||
Подключиться к запущенной сессии:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
screen -r [имя_сессии]
|
||||
|
||||
=============== ============================================================
|
||||
Комбинация Действие
|
||||
=============== ============================================================
|
||||
Ctrl+a ? Показать справку
|
||||
Ctrl+a : Открыть приглашение для ввода команд screen
|
||||
Ctrl+a " Список окон
|
||||
Ctrl+a 0 Открыть окно номер 0
|
||||
Ctrl+a A Переименовать текущее окно
|
||||
Ctrl+a a Отправить комбинацию Ctrl+a в текущее окно
|
||||
Ctrl+a c Создать новое окно (с оболочкой)
|
||||
Ctrl+a S Разделить текущую область по горизонтали
|
||||
Ctrl+a | Разделить текущую область по вертикали
|
||||
Ctrl+a tab Перевести фокус ввода на следующую область
|
||||
Ctrl+a Ctrl+a Переключиться между текущей и предыдущей областью
|
||||
Ctrl+a Esc Перейти в режим копирования (используйте Enter для выделения
|
||||
текста). Также скроллинг терминала.
|
||||
Ctrl+a ] Вставить текст
|
||||
Ctrl+a Q Закрыть все окна, кроме текущего
|
||||
Ctrl+a X Закрыть текущую область
|
||||
Ctrl+a d Отсоединиться от текущей сессии
|
||||
=============== ============================================================
|
||||
|
||||
При разделении экрана на области появится новая пустая область. Нужно
|
||||
переключить на неё фокус и запустить новое окно с оболочкой ``Ctrl+a`` ``c``.
|
||||
|
||||
Конфигурация **screen** хранится в файле **~/.screenrc**. Я для себя написал
|
||||
совсем простой конфиг, который тем не менее показывает всё что действительно
|
||||
нужно.
|
||||
|
||||
.. code-block:: unixconfig
|
||||
|
||||
startup_message off
|
||||
hardstatus alwayslastline
|
||||
hardstatus string '%S: %-w%>(%n %t)%{-}%+w%<'
|
||||
|
||||
.. image:: https://i.nxhs.cloud/Swl.png
|
||||
:alt: Terminal with screen
|
||||
|
||||
Материалы по **screen**:
|
||||
|
||||
* `GNU screen usage <http://gnuscreen.org/>`_
|
||||
* `GNU screen - ArchWiki <https://wiki.archlinux.org/title/GNU_Screen>`_
|
||||
|
||||
tmux
|
||||
====
|
||||
|
||||
**tmux** — это более новороченное решение. Он умеет почти всё то же что
|
||||
**screen** + имеет дополнительные фичи вроде управления мышью.
|
||||
|
||||
Запустить новую сессию:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
tmux [new -s имя_сессии]
|
||||
|
||||
Подключиться к сессии:
|
||||
|
||||
.. code-block:: text
|
||||
|
||||
tmux a [-t имя_сессии]
|
||||
|
||||
=============== ============================================================
|
||||
Комбинация Действие
|
||||
=============== ============================================================
|
||||
Ctrl+b c Создать новое окно
|
||||
Ctrl+b , Переименовать окно
|
||||
Ctrl+b w Список окон
|
||||
Ctrl+b " Разделить текущую область по горизотали
|
||||
ctrl+b % Разделить текущую область по вертикали
|
||||
Ctrl+b x Закрыть текущую панель
|
||||
Ctrl+b [ Перейти в режим копирования текста + скроллинг
|
||||
Ctrl+b d Отсоединиться от сессии
|
||||
=============== ============================================================
|
||||
|
||||
Мой **~/.tmux.conf**:
|
||||
|
||||
.. code-block:: unixconfig
|
||||
|
||||
set -g mouse on
|
||||
set -g history-limit 5000
|
||||
|
||||
# vim style controls
|
||||
bind h select-pane -L
|
||||
bind j select-pane -D
|
||||
bind k select-pane -U
|
||||
bind l select-pane -R
|
||||
|
||||
bind -r H resize-pane -L 10
|
||||
bind -r J resize-pane -D 10
|
||||
bind -r K resize-pane -U 10
|
||||
bind -r L resize-pane -R 10
|
||||
|
||||
# Copy / paste
|
||||
bind b list-buffers
|
||||
bind B show-buffer
|
||||
bind P paste-buffer
|
||||
|
||||
bind-key -T copy-mode-vi v send-keys -X begin-selection
|
||||
bind-key -T copy-mode-vi y send-keys -X copy-selection
|
||||
bind-key -T copy-mode-vi r send-keys -X rectangle-toggle
|
||||
|
||||
bind -T copy-mode-vi Enter send-keys -X copy-pipe-and-cancel \
|
||||
"xclip -i -f -selection primary | xclip -i -selection clipboard"
|
@ -15,13 +15,6 @@
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<p>
|
||||
{% if page.template == 'index.jinja2' %}
|
||||
{{ site.title }}
|
||||
{% else %}
|
||||
<a href="/">{{ site.title }}</a> / {{ page.title }}</p>
|
||||
{% endif %}
|
||||
</p>
|
||||
<p>
|
||||
{% for link in site.links %}
|
||||
<a href="{{ link.url }}" target="{{ link.target }}">
|
||||
|
@ -4,10 +4,13 @@
|
||||
<ul id="posts">
|
||||
{% for post in aggr.posts %}
|
||||
<li>
|
||||
<a href="{{ post.path }}">{{ post.title }}</a>
|
||||
<span class="meta"> — {{ post.date }}</span>
|
||||
<a href="{{ post.path }}">{{ post.title }}</a><br>
|
||||
<span class="meta">{{ post.date }}</span>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</section>
|
||||
<section>
|
||||
{{ html | safe }}
|
||||
</section>
|
||||
{% endblock %}
|
||||
|
@ -10,28 +10,30 @@ name = 'Ещё один сайт про cисадмиство и програм
|
||||
title = 'Nixhacks'
|
||||
datetime_format = "%d %b %y"
|
||||
|
||||
[[site.links]]
|
||||
title = '[home]'
|
||||
url = '/'
|
||||
[[site.links]]
|
||||
title = '[projects]'
|
||||
url = '/projects.html'
|
||||
[[site.links]]
|
||||
title = '[code]'
|
||||
url = 'https://git.nxhs.cloud/ge/'
|
||||
target = '_blank'
|
||||
[[site.links]]
|
||||
title = '[kb]'
|
||||
url = '/kb/'
|
||||
target = '_blank'
|
||||
[[site.links]]
|
||||
title = '[markdown]'
|
||||
url = '/writing/'
|
||||
target = '_blank'
|
||||
[[site.links]]
|
||||
title = '[my ip]'
|
||||
url = '/cgi-bin/ip.cgi'
|
||||
target = '_blank'
|
||||
[[site.links]]
|
||||
title = '[home]'
|
||||
url = '/'
|
||||
[[site.links]]
|
||||
title = '[software]'
|
||||
url = '/projects.html'
|
||||
[[site.links]]
|
||||
title = '[knowledge base]'
|
||||
url = '/kb/'
|
||||
target = '_blank'
|
||||
[[site.links]]
|
||||
title = '[manpages]'
|
||||
url = '/cgi-bin/man.cgi'
|
||||
[[site.links]]
|
||||
title = '[code]'
|
||||
url = 'https://git.nxhs.cloud/ge/'
|
||||
target = '_blank'
|
||||
[[site.links]]
|
||||
title = '[markdown]'
|
||||
url = '/writing/'
|
||||
target = '_blank'
|
||||
[[site.links]]
|
||||
title = '[my ip]'
|
||||
url = '/cgi-bin/ip.cgi'
|
||||
|
||||
[site.footer]
|
||||
text = ''
|
||||
|
@ -1,3 +1,10 @@
|
||||
@font-face {
|
||||
font-family: 'Inconsolata-LGC';
|
||||
src: local('Inconsolata-LGC'),
|
||||
url(/fonts/Inconsolata-LGC.woff2) format('woff2'),
|
||||
url(/fonts/Inconsolata-LGC.woff) format('woff');
|
||||
font-display: swap;
|
||||
}
|
||||
:root {
|
||||
--b: #000;
|
||||
--w: #fff;
|
||||
@ -10,12 +17,17 @@
|
||||
--a: #0b6adc;
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 840px) {
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
body {
|
||||
color: var(--b);
|
||||
background-color: var(--w);
|
||||
background: var(--w);
|
||||
padding: 1rem;
|
||||
max-width: 79ch;
|
||||
font-family: 'Source Code Pro', monospace;
|
||||
font-family: 'Inconsolata-LGC', monospace;
|
||||
font-size: 16px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
@ -54,19 +66,16 @@ section#posts {
|
||||
ul#posts li {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: .5rem 0;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
ul#posts {
|
||||
padding: 0;
|
||||
}
|
||||
dt { /* Defenition list title */
|
||||
dt {
|
||||
font-weight: bold;
|
||||
}
|
||||
span.meta {
|
||||
font-size: 80%;
|
||||
}
|
||||
span.docutils.literal { /* Inline literal */
|
||||
background-color: #d0d7de;
|
||||
span.docutils.literal {
|
||||
background: #d0d7de;
|
||||
border-radius: 4px;
|
||||
padding: 0 4px 2px 4px;
|
||||
font-family: monospace;
|
||||
@ -89,7 +98,7 @@ pre > code {
|
||||
}
|
||||
blockquote {
|
||||
padding: 0 1em;
|
||||
border-left: .25em solid #d0d7de;
|
||||
border-left: .25em solid var(--b);
|
||||
}
|
||||
table, th, td {
|
||||
border: 1px solid;
|
||||
@ -99,7 +108,7 @@ table, th, td {
|
||||
}
|
||||
th {
|
||||
font-weight: 600;
|
||||
background-color: #d0d7de;
|
||||
background: #ebeff2;
|
||||
}
|
||||
td p {
|
||||
margin: .5rem 0;
|
||||
@ -107,12 +116,20 @@ td p {
|
||||
hr {
|
||||
color: #d0d7de;
|
||||
}
|
||||
img {
|
||||
max-width: 79ch;
|
||||
}
|
||||
.admonition {
|
||||
padding: .5rem 1rem;
|
||||
border-radius: 8px;
|
||||
background: #ebeff2;
|
||||
}
|
||||
.admonition-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
.admonition-title::before {
|
||||
content: '🛈 ';
|
||||
}
|
||||
.highlight {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
BIN
static/fonts/Inconsolata-LGC.woff
Normal file
BIN
static/fonts/Inconsolata-LGC.woff
Normal file
Binary file not shown.
BIN
static/fonts/Inconsolata-LGC.woff2
Normal file
BIN
static/fonts/Inconsolata-LGC.woff2
Normal file
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 293 KiB After Width: | Height: | Size: 87 KiB |
Loading…
Reference in New Issue
Block a user