This commit is contained in:
ge 2022-10-21 22:29:23 +03:00
parent 423468a50b
commit a3c6d296ab
12 changed files with 753 additions and 50 deletions

View File

@ -31,14 +31,13 @@
PS1='\[\033[0;96m\]$(__git_ps1 "(%s) ")\[\033[0;92m\]\w \[\033[0;31m\]$(__exit_code_ps1 $?)\[\033[0;15m\]\$ ' PS1='\[\033[0;96m\]$(__git_ps1 "(%s) ")\[\033[0;92m\]\w \[\033[0;31m\]$(__exit_code_ps1 $?)\[\033[0;15m\]\$ '
fi fi
На GitHub можно найти много всяких скриптов или даже целых "фремворков" типа `этого`_ На GitHub можно найти много всяких скриптов или даже целых "фремворков" типа
или `отдельные скрипты`_ для Git. А вы знали, что скрипт для PS1 и так `поставляется`_ `этого`_ или `отдельные скрипты`_ для Git. Однако, скрипт для PS1 и так
в пакете Git? Не вижу смысла не использовать его, если только вам не надо `поставляется`_ в пакете Git.
как-то иначе работать с PS1.
Помимо **git-prompt.sh** я использую функцию ``__exit_code_ps1()``, которая просто Помимо **git-prompt.sh** я использую функцию ``__exit_code_ps1()``, которая
печатает число, если оно не равно нулю. Это очень удобно — всегда видишь код выхода просто печатает число, если оно не равно нулю. Это очень удобно — всегда
предыдущей запущенной команды, порой очень помогает при отладке скриптов. видишь код выхода команды. Порой это очень помогает при отладке скриптов.
В итоге всё это дело у меня выглядит вот так: В итоге всё это дело у меня выглядит вот так:

View 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[@]}"
}

View 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
}

View File

@ -8,7 +8,8 @@
* `rSW </rsw/>`_ -- генератор статических сайтов из reStructuredText * `rSW </rsw/>`_ -- генератор статических сайтов из reStructuredText
* tui.sh -- библиотека элементов TUI для POSIX-совместимых оболочек * tui.sh -- библиотека элементов TUI для POSIX-совместимых оболочек
* boring-backup -- Bash фреймворк для скриптов резервного копирования * `boring-backup <https://git.nxhs.cloud/ge/boring_backup>`_ -- Bash
фреймворк для скриптов резервного копирования
* `imgs <https://git.nxhs.cloud/ge/imgs>`_ -- минималистичный хостинг картинок * `imgs <https://git.nxhs.cloud/ge/imgs>`_ -- минималистичный хостинг картинок
* `vk-toot <https://git.nxhs.cloud/ge/vk-toot>`_ -- кросспостер VK -> Mastodon * `vk-toot <https://git.nxhs.cloud/ge/vk-toot>`_ -- кросспостер VK -> Mastodon
* `piglet <https://git.hxhs.cloud/ge/piglet>`_ -- клиент DNS API Porkbun * `piglet <https://git.hxhs.cloud/ge/piglet>`_ -- клиент DNS API Porkbun

253
content/terminal_muxers.rst Normal file
View 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"

View File

@ -15,13 +15,6 @@
</head> </head>
<body> <body>
<header> <header>
<p>
{% if page.template == 'index.jinja2' %}
{{ site.title }}
{% else %}
<a href="/">{{ site.title }}</a> / {{ page.title }}</p>
{% endif %}
</p>
<p> <p>
{% for link in site.links %} {% for link in site.links %}
<a href="{{ link.url }}" target="{{ link.target }}"> <a href="{{ link.url }}" target="{{ link.target }}">

View File

@ -4,10 +4,13 @@
<ul id="posts"> <ul id="posts">
{% for post in aggr.posts %} {% for post in aggr.posts %}
<li> <li>
<a href="{{ post.path }}">{{ post.title }}</a> <a href="{{ post.path }}">{{ post.title }}</a><br>
<span class="meta">{{ post.date }}</span> <span class="meta">{{ post.date }}</span>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
</section> </section>
<section>
{{ html | safe }}
</section>
{% endblock %} {% endblock %}

View File

@ -10,28 +10,30 @@ name = 'Ещё один сайт про cисадмиство и програм
title = 'Nixhacks' title = 'Nixhacks'
datetime_format = "%d %b %y" datetime_format = "%d %b %y"
[[site.links]] [[site.links]]
title = '[home]' title = '[home]'
url = '/' url = '/'
[[site.links]] [[site.links]]
title = '[projects]' title = '[software]'
url = '/projects.html' url = '/projects.html'
[[site.links]] [[site.links]]
title = '[code]' title = '[knowledge base]'
url = 'https://git.nxhs.cloud/ge/' url = '/kb/'
target = '_blank' target = '_blank'
[[site.links]] [[site.links]]
title = '[kb]' title = '[manpages]'
url = '/kb/' url = '/cgi-bin/man.cgi'
target = '_blank' [[site.links]]
[[site.links]] title = '[code]'
title = '[markdown]' url = 'https://git.nxhs.cloud/ge/'
url = '/writing/' target = '_blank'
target = '_blank' [[site.links]]
[[site.links]] title = '[markdown]'
title = '[my ip]' url = '/writing/'
url = '/cgi-bin/ip.cgi' target = '_blank'
target = '_blank' [[site.links]]
title = '[my ip]'
url = '/cgi-bin/ip.cgi'
[site.footer] [site.footer]
text = '' text = ''

View File

@ -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 { :root {
--b: #000; --b: #000;
--w: #fff; --w: #fff;
@ -10,12 +17,17 @@
--a: #0b6adc; --a: #0b6adc;
} }
} }
@media screen and (max-width: 840px) {
img {
width: 100%;
}
}
body { body {
color: var(--b); color: var(--b);
background-color: var(--w); background: var(--w);
padding: 1rem; padding: 1rem;
max-width: 79ch; max-width: 79ch;
font-family: 'Source Code Pro', monospace; font-family: 'Inconsolata-LGC', monospace;
font-size: 16px; font-size: 16px;
line-height: 1.3; line-height: 1.3;
} }
@ -54,19 +66,16 @@ section#posts {
ul#posts li { ul#posts li {
list-style: none; list-style: none;
padding: 0; padding: 0;
margin: .5rem 0; margin: 1rem 0;
} }
ul#posts { ul#posts {
padding: 0; padding: 0;
} }
dt { /* Defenition list title */ dt {
font-weight: bold; font-weight: bold;
} }
span.meta { span.docutils.literal {
font-size: 80%; background: #d0d7de;
}
span.docutils.literal { /* Inline literal */
background-color: #d0d7de;
border-radius: 4px; border-radius: 4px;
padding: 0 4px 2px 4px; padding: 0 4px 2px 4px;
font-family: monospace; font-family: monospace;
@ -89,7 +98,7 @@ pre > code {
} }
blockquote { blockquote {
padding: 0 1em; padding: 0 1em;
border-left: .25em solid #d0d7de; border-left: .25em solid var(--b);
} }
table, th, td { table, th, td {
border: 1px solid; border: 1px solid;
@ -99,7 +108,7 @@ table, th, td {
} }
th { th {
font-weight: 600; font-weight: 600;
background-color: #d0d7de; background: #ebeff2;
} }
td p { td p {
margin: .5rem 0; margin: .5rem 0;
@ -107,12 +116,20 @@ td p {
hr { hr {
color: #d0d7de; color: #d0d7de;
} }
img {
max-width: 79ch;
}
.admonition { .admonition {
padding: .5rem 1rem; padding: .5rem 1rem;
border-radius: 8px;
background: #ebeff2;
} }
.admonition-title { .admonition-title {
font-weight: bold; font-weight: bold;
} }
.admonition-title::before {
content: '🛈 ';
}
.highlight { .highlight {
border-radius: 8px; border-radius: 8px;
} }

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 293 KiB

After

Width:  |  Height:  |  Size: 87 KiB