2018-10-27

О desktop

Помните ярлыки в Windows. Файлики с расширением .lnk. Классическим фейлом было скопировать на дискетку ярлык вместо самого вордовского файла, а потом удивляться, почему это оно не открывается на другом компьютере.
В наших юниксах, оказывается, тоже есть ярлыки. Freedesktop.org определяет формат файла .desktop, который определяет то, что называется Desktop Entry. Это и ярлыки на рабочем столе, и пункты меню. И это работает и в GNOME, и в KDE, и во всех прочих юниксовых DE. Очень удобно.
Зачем мне ярлыки понадобились? Потому что, как оказалось, в KDE нельзя просто так запустить Java приложение, каким-нибудь java -jar app.jar, а потом закрепить (pin) его в каком-нибудь доке, чтобы потом быстро запускать. Иконка-то появляется, но запускает она просто /usr/bin/java, без параметров. И pin, в результате, не работает.
Чтобы pin заработал, нужно запускать правильный .desktop файл. Он может называться java-RedmineTimeTracker.RedmineTimeTracker.desktop и выглядеть как-то так:
[Desktop Entry]
Type=Application
Name=Redmine Time Tracker
Icon=java-RedmineTimeTracker.RedmineTimeTracker
Exec=java -XX:MaxRAM=256m -jar "/home/gelin/opt/RedmineTimeTracker/Redmine Time Tracker - 0.3.6.jar"
StartupWMClass=RedmineTimeTracker.RedmineTimeTracker
Terminal=false
Categories=Development
Обычный текстовый ini-подобный файл.
Type — это тип. "Application" — это значит, что это "ярлык" для приложения. Ещё есть "Link" и "Directory".
Name — это имя. Как оно будет называться в меню или искаться в dashboard.
Icon — это путь до файла с иконкой. В формате PNG или SVG. Или же имя зарегистрированной иконки. Об этом чуть позже.
Exec — это команда, которая запускает приложение. В данном случае — обычный java -jar, но с ограничением по памяти.
StartupWMClass — это то, ради чего мы вообще этот .desktop файл создавали. В X Window, также как и в Windows, у окна есть класс. Строка. И всякие доки определяют уникальную кнопочку для приложения как раз по этому классу. Из-за отсутствия класса и возникает проблема с Java приложениями. Поэтому мы здесь имя класса и указываем явно.
Есть небольшая проблемка, как определить класс окна для Java приложения. В этом нам поможет программка xprop. Запускаем нужное нам приложение. Запускаем xprop в консоли. Тыкаем по интересующему нас окну. Видим в консоли что-то вроде:
$ xprop | grep WM_CLASS
WM_CLASS(STRING) = "RedmineTimeTracker.RedmineTimeTracker", "RedmineTimeTracker.RedmineTimeTracker"
В данном случае класс совпадает с именем главного класса Java приложения. Я не нашёл, является ли это нормой для десктопных Java приложений. В любом случае, просто прописываем, что обнаружили, в StartupWMClass.
Terminal — флаг, нужно ли запускать приложение в терминале. В данном случае — нет.
Categories — список категорий, через точку с запятой. Это — стандартные категории, на которые делится главное меню. В данном случае это приложение связано с разработкой.
Иконка. Иконка — это ресурс, который можно зарегистрировать в системе, назначить ему имя, и использовать это имя в .desktop файле.
Регистрация производится с помощью программки xdg-icon-resource. XDG — это "X Desktop Group", так раньше назывался freedesktop.org. Сама эта программка — даже не программка, а здоровенный шелловый скрипт, внутри которого есть функции с интересными названиями вроде detectDE(), find_gtk_update_icon_cache() или need_kde_icon_path(). Этот скриптик очень много знает про всякие разные desktop environment.
$ xdg-icon-resource install \
--size 128 \
redmine_logo_green.png \
"java-RedmineTimeTracker.RedmineTimeTracker"
Обязательно нужно указать размер иконки. Больше — лучше. Но не все размеры ваша DE сможет съесть. Хорошо работает 128 и 256 пикселей (128x128 и 256x256).
Последний параметр — имя иконки. Оно обязательно должно состоять из двух слов. Первое, до минуса — это вендор. В данном случае это Java приложение, так что пусть будет "java". Второе, после минуса — собственно идентификатор иконки, для данного вендора. Так как нужен уникальный идентификатор, удобно использовать тут имя класса окна.
На самом деле xdg-icon-resource копирует иконку в каталог вроде ~/.local/share/hicolor/128x128/apps/, или другой, специфичный для вашей DE, и делает магию по обновлению кэша иконок DE.
Сам .desktop файл "устанавливается" тоже своей командой xdg-desktop-menu. Это тоже здоровенный шелловый скрипт.
$ xdg-desktop-menu install \
"java-RedmineTimeTracker.RedmineTimeTracker.desktop"
Имя файла тоже должно содержать префикс вендора, то есть "java-" в данном случае. Скрипт копирует ваш .desktop файл куда-нибудь в ~/.local/share/applications/ и магическим образом обновляет список приложений в DE.
Берём xprop, определяем класс окна. Берём иконку и засовываем её как системный ресурс с помощью xdg-icon-resource. Пишем .desktop файл и добавляем его в систему с помощью xdg-desktop-menu. Всё. Теперь мы умеем создавать "ярлыки" для любых приложений и засовать их в доки и меню.
freedesktop.org logo
И тут выходит Chrome 70. Где отломали одну очень полезную фичу, которой я интенсивно пользовался.
Вы знали, что в Хроме можно было сделать: "Open Main menu -> More Tools -> Add to Desktop"? В результате создавался тот самый .desktop файл, который можно закрепить в доке, и который запускал тот самый сайт, который вы таким образом добавили на рабочий стол, в отдельном окне, без табов и адресной строки.
То есть любой сайт можно "запускать" как обычное приложение. С отдельной кнопкой на панели задач. С нормальным переключением между окнами по Alt+Tab. И, так как технически это просто вкладка того же Хрома, только запущенная в отдельном окне, это жрёт заметно меньше памяти, чем Electron приложение того же Slack (потому что в каждом Электроне — своя копия движка браузера). У меня так запущены кучка Слаков, Gmail, DevDocs и прочие. Мне — очень удобно.
Так вот, в Chrome 70 этот пункт меню убрали. А у сохранившихся ярлыков какого-то чёрта поломалось оформление окон. Теперь они отображаются с хромовыми заголовками окон, что категорически не совпадает по стилю и расположению кнопок с окнами моего KDE.
Но мы же теперь умеем делать .desktop файлы. А у Chrome всё еще остался ключик командной строки --app, который делает то, что нам нужно. То есть запускает указанный url в отдельном окне без табов и адресной строки, и с правильным оформлением самого окна.
Так что берём своё счастье в свои руки. Качаем иконку сайта и засовываем её в систему с помощью xdg-icon-resource. Берите только иконку 256x256, а не 192x192, не все размеры DE съест.
Пишем .desktop файл. Например, chrome-devdocs.io.desktop:
[Desktop Entry]
Type=Application
Name=DevDocs API Documentation
Icon=chrome-devdocs.io
Exec=/opt/google/chrome/google-chrome --profile-directory=Default --app=https://devdocs.io
StartupWMClass=devdocs.io
Terminal=false
Categories=Network
В качестве вендора я тут взял "chrome-", по аналогии со старыми ярлыками. В качестве класса окна Хром выставляет доменное имя из указанного url. Остальное, думаю, понятно.
Устанавливаем файл с помощью xdg-desktop-menu. И наслаждаемся новым удобным приложением, которое на самом деле сайт.
P.S. Я это дело даже немного автоматизировал.

2018-10-14

Об ssh

Об эс-эс-эйч. Которая SSH. Которая Secure SHell. Не все, оказывается, толком представляют, что это такое. И уж тем более не подозревают обо всей мощи SSH.
Впервые я узнал об этой аббревиатуре, "SSH", из журнала "Byte" из 1990-х. В рамках курса английского в универе мы переводили эти исторические статьи. И вот в одной из них встретилась фраза вроде "he sshed to the remote server". Да, "to ssh" — это ещё и глагол.
SSH
SSH появился в 1995 усилиями ещё одного талантливого финна. Его звали Tatu Ylönen.
SSH — это сетевой протокол, работающий поверх TCP. Соответственно, у нас есть сервер, демон sshd, который слушает порт 22, и клиент ssh. У 22 порта есть своя история. Интернет тогда был маленький, и застолбить свой красивый привилегированный порт тогда было довольно просто.
Первая буква "S" в "SSH" действительно означает "Security". SSH возник именно как замена rlogin, rsh и telnet. Все они тоже позволяли подключиться к удалённому серверу и выполнять там команды, но весь трафик там передавался открытым текстом. А SSH — шифрует (и сжимает).
SSH возник примерно в одно время с первыми вариантами SSL (позднее известный как TLS). Поэтому они используют несколько разные подходы. Как минимум, SSH — это не SSL/TLS. А SSL/TLS — это универсальный способ добавить "S" в кучу других протоколов поверх TCP. Самый известный вариант: HTTPS.
В SSL/TLS публичный ключ сервера (и, опциально, клиента) представлен в виде сертификата, подписанного третьей доверенной стороной. И клиент доверяет серверу, если он доверяет этой третьей стороне. В SSH клиент доверяет каждому серверу индивидуально. При первом подключении пользователю явно задаётся вопрос: а доверяем ли мы этому хосту?
$ ssh [email protected]
The authenticity of host 'host.example.net (1.2.3.4)' can't be established.
ECDSA key fingerprint is SHA256:Xocqb7ZPtfsEmXnTCUOeRaCRMfzH1nbTxDVKp0YJBCA.
Are you sure you want to continue connecting (yes/no)?
Если ответить yes, то отпечаток ключа хоста сохранится в ~/.ssh/known_hosts. И при повторном подключении, если ключ сервера не изменился, вопросов уже не будет. А если изменился, то подключения не будет, потому что это уже другой сервер, если у него другой ключ. Простая и эффективная схема доверия.
Если по данному адресу действительно расположился новый сервер, с новым ключом, вам придётся забыть предыдущий ключ. Такой вот командой:
$ ssh-keygen -R "host.example.net"
# Host host.example.net found: line 497
/home/gelin/.ssh/known_hosts updated.
Original contents retained as /home/gelin/.ssh/known_hosts.old
В SSL/TLS клиент, чаще всего, анонимен. Хотя и может предъявить свой сертификат и ключ. В SSH — никакой анонимности. Клиент обязан сказать, от имени какого пользователя (локального пользователя данного сервера) он собирается действовать, предъявить ключ, а там, возможно, ещё и ввести пароль этого самого пользователя.
В простейшем случае мы подключаемся от root и вводим пароль root.
$ ssh [email protected]
[email protected]'s password:
Конечно, не надо так. Подключаться прямо к root, конечно, удобнее. Но подход Ubuntu, когда у root вообще нет пароля, и им вообще нельзя войти в систему, немного безопаснее. Сначала надо подключиться/войти под каким-нибудь "ubuntu". А потом всякие гадости можно делать через sudo. При этом понадобится ввести пароль этого самого "ubuntu".
Ну и, конечно же, не надо подключаться через SSH по паролю. Это ещё хуже, чем прямой root доступ. Потому что пароль можно подобрать. А 4096-битный RSA ключ, ну, почти нельзя. SSH предоставляет несколько методов аутентификации. Кроме "password" есть ещё весьма удобный и вполне безопасный "publickey".
У клиента всегда есть ключ. Без ключа нельзя подключиться. Публичный ключ клиента можно увидеть в файле ~/.ssh/id_rsa.pub.
$ cat ~/.ssh/id_rsa.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCcwgyvek9Oh5wUNbNUTiEtz1jeRn6WnF1mw3duhPC7B3N9HAvCVtYesu5xLy6wylcKYUdKPLo8Q6hEh8kTcHwL21xPQB4/Uq/PjLUvt+MS5mUfYKwWL/M9h37nztA/scK6ItYWMxP6hsX1/zhOkcw1VsLD0+tHRYVqmAm+qO2VQxJ4Gc0dJWeHIGPq1gLLLlJx1QJHUMbHFewtH8z18ood3w/Q07QIKy2kMxFTK/y6Kv1Ij0rxO1KnnzulJHOiNIffeec7nvjcwe0nLYW7y28sT9cxCBIxNu2Dzir8pqgM1TB+mAw/nsYft9CGYBWc+AaodERHXPIbm2wwUUFWnEAN [email protected]
Файл не обязательно будет называться id_rsa.pub. Например, сейчас модно переходить на криптографию на эллиптических кривых. Тогда файлик будет называться id_ed25519.pub. Главное, смотрите файлики, чьё имя заканчивается на .pub. Это — публичные ключи. А без .pub — это приватные ключи. Берегите приватные ключи как зеницу ока и никому их не давайте даже под страхом смерти. И задавайте хороший пароль при генерации ключей.
$ ssh-keygen -o -a 100 -t ed25519 -f ~/.ssh/id_ed25519 -C "[email protected]"
Generating public/private ed25519 key pair.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/gelin/.ssh/id_ed25519.
Your public key has been saved in /home/gelin/.ssh/id_ed25519.pub.
The key fingerprint is:
SHA256:FOjHEnorwa9eAb4Jhnqeh4rZ5zzsG/S8LzkBBm+DVCc [email protected]
The key's randomart image is:
+--[ED25519 256]--+
|   .E ...        |
|  o  oo  .       |
| . =.o o.        |
| ...X.+.o        |
|. ooo*.+S        |
|.. o.=+.         |
|. .o+o+o         |
|.=.o*o+.         |
|+.+**o.+.        |
+----[SHA256]-----+
А публичные ключи можно раздавать налево и направо. Что я тут и делаю :) Всё равно, всё, что вы сможете с ними сделать, это дать мне доступ к вашим серверам :)
Чтобы аутентификация по публичному ключу заработала, ключ клиента (публичный!) нужно добавить в файл ~/.ssh/authorized_keys на сервере. В домашнем каталоге того пользователя, под кем мы подключаемся. Соответственно, домашний каталог у этого пользователя должен быть.
Добавить ключ в authorized_keys можно либо руками. Просто дописываете содержимое id_rsa.pub (одну строчку) в конец этого файла. Не забудьте пустую строку в конце. Либо вам может помочь команда ssh-copy-id.
$ ssh-copy-id -i ~/.ssh/id_ed25519 [email protected]
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/gelin/.ssh/id_ed25519.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys

Number of key(s) added: 1

Now try logging into the machine, with:   "ssh '[email protected]le.net'"
and check to make sure that only the key(s) you wanted were added.
Но мы же хотели ещё запретить подключение с паролем и прямой root доступ. Надо настроить sshd демона. Обычно его конфигурация лежит в файлике /etc/ssh/sshd_config. Нас интересуют несколько параметров.
PermitRootLogin no
PubkeyAuthentication yes    # не путать с RSAAuthentication
PasswordAuthentication no
После изменений в sshd_config делайте service ssh reload, не restart. И тренируйтесь в переподключении через SSH в другой консоли. Запускайте ssh с ключиком -v, чтобы видеть, как клиент с сервером договариваются. А эту сессию, где вы конфиг правили, не закрывайте. Тогда ваше подключение останется живым, даже если вы отломали SSH доступ. Крайне неприятно терять связь с удалённым сервером, знаете ли.
У клиента тоже есть конфиг. В файле ~/.ssh/config. Туда очень удобно прописывать имена юзеров и нестандартные порты подключения к конкретным серверам. Или даже какие-то особые SSH ключи, раз уж их вам выдали.
Host host.example.net
User root
Port 2222
IdentityFile ~/.ssh/example_net_key
Как вы знаете, Git и Mercurial тоже работают через SSH. Вот такой хитрой конфигурацией можно ходить на какой-нибудь Bitbucket из-под разных аккаунтов (и с разными ключами):
Host bitbucket.org
# обычный bitbucket, который использует ключ id_rsa

Host work.bitbucket.org
# отдельный bitbucket с отдельным ключом
HostName bitbucket.org
# но на самом деле мы всё равно подключаемся к bitbucket.org
User git
# юзером git
IdentityFile ~/.ssh/[email protected]
# и используем этот специальный ключ
IdentitiesOnly yes
# и только его
~/.ssh/config разбит на секции по директивам Host. Host — это то, что вы пишите в командной строке.
$ git clone [email protected]:user/repo.git
Это не обязательно должен быть реально существующий хост, настоящий адрес указывается в HostName. Все последующие директивы относятся в предыдущему (последнему) Host.
По умолчанию SSH запускает shell пользователя на сервере. Но на самом деле SSH может выполнить совершенно любую команду на удалённом хосте. И stdin/stdout на клиенте станут stdin/stdout этой удалённой команды. В своё время мы так собирали статистику с роутеров, запуская удалённый скрипт на Perl, который плевался XML.
$ ssh [email protected] whoami
root
Через SSH можно передавать файлы. Это значительно безопаснее, чем какой-нибудь FTP, куда шифрование толком так и не прикрутили. И это даже может быть быстрее.
Простой способ: scp. Secure CoPy. Можно скопировать локальный файл на удалённый сервер, или наоборот.
$ scp ssh.md [email protected]:/tmp/
ssh.md                  100%   13KB  74.6KB/s   00:00
Параметр, указывающий файл или каталог на удалённом хосте, состоит из двух частей. До двоеточия: имя пользователя и хост, так же как в обычном SSH клиенте. После двоеточия: абсолютный или относительный (относительно домашнего каталога) путь на сервере.
Но scp — туп. Он всегда копирует файл целиком. А вот если файл большой, или файлов много, нужно использовать rsync.
rsync — умный. Он умеет делить файлы на куски и проверять каждый кусок на идентичность, и докачивать только недостающие или отличающиеся куски. Он может сжимать файлы перед отправкой. Он может, как оно следует из названия, синхронизировать целые каталоги. С его помощью легко можно почти полностью склонировать почти любую Unix систему. Нужно только SSH на удалённый сервер, и установленный там rsync.
$ rsync -avz --progress . [email protected]:/tmp/test
sending incremental file list
created directory /tmp/test
./
00 URIs.png
        100,554 100%    4.31MB/s    0:00:00 (xfr#1, to-chk=2/4)
01 URI & URL
         38,506 100%    1.84MB/s    0:00:00 (xfr#2, to-chk=1/4)
url.md
          8,761 100%  371.99kB/s    0:00:00 (xfr#3, to-chk=0/4)

sent 139,314 bytes  received 108 bytes  39,834.86 bytes/sec
total size is 147,821  speedup is 1.06
Будьте осторожнее с параметрами rsync. Он по-разному работает с файлами и каталогами. Если вам нужно синхронизировать содержимое двух уже существующих каталогов, и не нужно создавать новые подкаталоги там, куда вы синхронизируете, оба параметра-каталога должны заканчиваться на /.
Кстати, rsync прекрасно работает и без SSH, на localhost.
Усложняем. Допустим, у нас есть какой-то Continuous Integration, который собирает некий статический сайт. И мы хотим, чтобы по окончанию сборки обновлённые файлы сами заливались бы на сервер. rsync для этого идеально подходит. Но мы не настолько доверяем нашему CI, чтобы давать ему root доступ. Заводим отдельного пользователя для синхронизации. Но хочется этого пользователя ещё сильнее ограничить, чтобы он мог делать только rsync только конкретного каталога. (Ну а в моём случае просто не было возможности завести ещё одного пользователя, ибо это shared hosting.) И в SSH это можно.
В файле authorized_keys перед ключом можно указать ещё и команду, которая будет разрешена к выполнению клиенту, предъявившему этот ключ. И больше никакая другая. И ещё кучу опций. А ещё есть специальный скрипт на Perl, под названием rrsync, который ограничивает доступ rsync к одному каталогу.
command="$HOME/bin/rrsync ~/limited/path/",no-agent-forwarding,no-port-forwarding,no-pty,no-user-rc,no-X11-forwarding ssh-rsa ...
Как видите, SSH много чего может. И это много чего тут явно запрещается.
Примерно так, через хитрые command для каждого ключа, и работают публичные Git репозитории вроде GitHub или GitLab. Хотите поднять что-то подобное (ограничиваясь только Git, безо всяких трекеров задач) у себя локально, смотрите в сторону gitolite.
У SSH есть агент. Он может хранить в памяти расшифрованные ключи, чтобы вам не приходилось вводить пароль для ключа при каждом подключении. А ещё он может пробрасывать загруженные на клиенте ключи так, чтобы они были доступны на сервере (это и называется agent forwarding). Это очень удобно, если вам нужно с сервера делать SSH на следующий сервер. Так бывает, да.
Добавить ключ в память агента можно так:
$ ssh-add ~/.ssh/special_key.pem
Identity added: /home/gelin/.ssh/special_key.pem (/home/gelin/.ssh/special_key.pem)
Через SSH можно пробросить порты (port forwarding). То есть сделать так, чтобы, например, подключения на локальный TCP порт 8080 перенаправлялись на порт 80 некоторого процесса на сервере. При этом прямого подключения к 80 порту на сервере у вас нет. Но у вас есть SSH доступ на сервер.
$ ssh -L 8080:localhost:80 -N -f [email protected]
Это называется local forwarding. В данном случае слушается локальный порт 8080 клиента. На сервере происходит подключение к localhost:80 (можно перенаправить подключение и к другому хосту, доступному с сервера). SSH будет только пробрасывать порт, но не будет запускать shell на сервере: -N. SSH клиент уйдёт работать в фон: -f.
Local Forwarding
Можно и наоборот. Открыть на сервере порт для подключения, и все подключения на этот порт будут проброшены на другой хост и порт, доступные на клиенте. Это называется remote forwarding. Синтаксис такой же, но нужен ключик -R.
Remote Forwarding
SSH может пробрасывать и сессии X11. Если помните, протокол X, который всё ещё доминирует на наших юниксах как средство отрисовки гуя, обладает сетевой прозрачностью. Собственно, в Linux когда-то TCP/IP запихнули в спешке, лишь бы графические окошки рисовались. А значит, имея запущенный X server на SSH клиенте, вполне можно запустить гуёвое приложение где-нибудь на мощном сервере так, чтобы рисовало окошки оно на клиенте. И всё, что вам для этого нужно, это xlib на сервере и SSH доступ туда. (Запутались, кто клиент, а кто сервер? Правильно, в иксах это нормально.)
SSH может не просто пробрасывать порты на конкретные хосты, но и выступать в роли SOCKS сервера.
$ ssh -D 1080 -f -C -q -N [email protected]
Здесь у вас на локальном порту 1080 клиента появится SOCKS сервер, который может подключаться к любому хосту, доступному с сервера. Все подключения будут проброшены через SSH. Данные будут сжиматься: -C.
Конечно, полноценные VPN туннели работают быстрее и лучше. Но и SSH, как видите, вполне достаточно.
SOCKS Proxy
Ну и, конечно же, Ansible работает через SSH. Он загружает и выполняет свои скрипты на Python через SSH.
Делайте "ssh". Это очень мощная и полезная штука.