Общие фишки

Настройка HTTP-аутентификации в Nginx

Чтобы настроить веб-аутентификацию в Nginx нужно сделать всего пару вещей:

 В Ubuntu это делается довольно просто:

apt-get install apache2-utils -y
# С указанием имени пользователя:
htpasswd -c /etc/nginx/.htpasswd username

Для безопасности, лучше сразу установить права и владельца для этого файла:

sudo chmod 640 /etc/nginx/.htpasswd
sudo chown root:www-data /etc/nginx/.htpasswd
server {
    # ...
    location /protected  {
        # Сама аутентификация: заголовок запроса пароля и файл с доступным УЗ
        auth_basic "Restricted Area";
        auth_basic_user_file /etc/nginx/.htpasswd;
        # А дальше - произвольная конфигурация location
    }
    # ...
}

Обратите внимание: аутентификация работает только на конкретный location (и все, что за ним), а не на весь возможный пул 

После всех манипуляций, нужно перезапустить Nginx:

nginx -t && nginx -s reload

При переходе на эндпоинт /protected (и все, что находится за ним), вам будет предложено ввести логин и пароль, который вы сгенерировали ранее

Опционально, вы можете отключить аутентификацию для каких-то вложенных путей:

# Здесь есть аутентификация
location / {
    auth_basic "Restricted Area";
    auth_basic_user_file /etc/nginx/.htpasswd;
    # ...
}

# А эта часть - без аутентификации
location /public-page {
    auth_basic off;
    # ...
}

                                                                                                     ВАЖНО!                                                                                                     

HTTPS обязателен для Basic Auth, так как логин/пароль передаются в Base64. Любой, кто перехватит запрос, получит логин и пароль в открытом виде

Настройка SSH-подключения по ключам

Шаг 1. Генерация приватного и публичного ключа

Для генерации приватного и публичного ключа выполним команду:

ssh-keygen -a 1000 -b 4096 -o -t rsa

Далее необходимо:
1.    Ввести путь, куда будут сохранены ключи – по-умолчанию это ~/.ssh/id_rsa 
2.    Ввести парольную фразу для расшифровки приватного ключа. Если оставить поле пустым, при его использовании пароль не будет запрашиваться
Так же важно добавить публичный ключ в один из конфигурационных файлов – authorized_keys:

cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys

Пояснение
SSH-ключ будет в файле id_rsa, в файле id_rsa.pub будет связанным с ним публичный ключ. Файл authorized_keys будет хранить в себе список из отпечатков (публичный ключей) для каждого приватного ключа, доступного для этого пользователя.

Шаг 2. Настройка SSHD

Изменим файл /etc/ssh/sshd_config, задав (изменив) ряд опций. Если закрытый ключ генерировался для любых пользователей, но root не должен иметь права входить на сервер:

# Запрещаем вход пользователю root
PermitRootLogin no
# Разрешаем использование ключей
PubkeyAuthentication yes
# Запрещаем использование паролей
PasswordAuthentication no
# Запрещаем аутентификацию пользователей через модуль PAM 
# (встроенная аутентификация в Linux)
UsePAM no

Если закрытый ключ генерировался для пользователя root, изменяем всего 1 параметр:

# Разрешаем вход пользователю root
PermitRootLogin yes

По желанию, можно разрешить вход каким-то конкретным пользователям:

AllowUsers root

Шаг 3. Перезапуск демона SSH и проверка работоспособности

Для применения изменений выполним перезапуск сервера SSH:

systemctl restart ssh

Далее, попробуем войти на этот же сервер с нашим закрытым ключом:

ssh username@localhost -i /путь/к/приватному/ключу

Если ошибок в конфигурации нет, SSH предложит нам добавить хост в список доверенных (только при первом входе) и разрешит вход. Если на этапе задания пароля для приватного ключа вы установили пароль, он будет запрошен у вас для дешифровки.  

Примечание

Возможно, понадобится отредактировать файлы в Include-директории. Обычно, это:

/etc/ssh/sshd_config.d/*.conf

В этих файлах хранятся настройки, которые могут перезаписать значения из основного конфигурационного файла SSHD, поэтому допустимо такое решение:

rm -f /etc/ssh/sshd_config.d/*.conf

                                                                                                      ВАЖНО!                                                                                                    
Эта команда удалит все содержимое с расширением *.conf из директории /etc/ssh/sshd_config.d !!!

Разрешение нескольких RDP-подключений на Windows

Зачем эта инструкция?

Удаленные пользователи могут подключаться к своим компьютерам Windows 10 или 11 через службу удаленных рабочих столов (RDP). Достаточно разрешить RDP доступ и подключиться к компьютеру с помощью любого клиента. Но есть ограничение на количество одновременных RDP сессии – допускается одновременная работа только одного удаленного пользователя. Если вы попробуете открыть вторую RDP сессию, появится предупреждение с запросом отключить сеанс первого пользователя.

Ограничение на количество одновременных RDP подключений в Windows является не техническим, но программным и лицензионным. Таким образом Microsoft запрещает создавать терминальный RDP сервер на базе рабочей станции для одновременной работы нескольких пользователей. Логика Microsoft проста: если вам нужен терминальный сервер — купите лицензию Windows Server, лицензии RDS CAL, установите и настройте роль Remote Desktop Session Host (RDSH).

Технически, любая редакция Windows при наличии достаточного количества оперативной памяти может обслуживать одновременную работу нескольких десятков удаленных пользователей. В среднем на одну RDP сессию пользователя без учета запускаемых приложений требуется 150-200 Мб памяти. Т.е. максимальное количество одновременных RDP сессий в теории ограничивается только ресурсами компьютера.

Решение проблемы

Мы будем использовать PowerShell скрипт для автоматического патчинга файла. Данный скрипт написан для версии Windows PowerShell, и не работает на PowerShell Core. Скрипт универсальный и может использоваться для внесения изменений в файл termsrv.dll на всех версиях Windows 10 и Windows 11.

# PowerShell скрипт модифицирует файл termsrv.dll, разрешая множественные RDP
# подключения к рабочим станциям на базе Windows 10 (1809 и выше) и Windows 11
# Подробности https://winitpro.ru/index.php/2015/09/02/neskolko-rdp-sessij-vwindows-10/
# Остановить службу, сделать копию файл и изменить разрешения
Stop-Service UmRdpService -Force
Stop-Service TermService -Force
$termsrv_dll_acl = Get-Acl c:\windows\system32\termsrv.dll
Copy-Item c:\windows\system32\termsrv.dll c:\windows\system32\termsrv.dll.copy
takeown /f c:\windows\system32\termsrv.dll
$new_termsrv_dll_owner = (Get-Acl c:\windows\system32\termsrv.dll).owner
cmd /c "icacls c:\windows\system32\termsrv.dll /Grant $($new_termsrv_dll_owner):F
/C"
# поиск шаблона в файле termsrv.dll
$dll_as_bytes = Get-Content c:\windows\system32\termsrv.dll -Raw -Encoding byte
$dll_as_text = $dll_as_bytes.forEach('ToString', 'X2') -join ' '
$patternregex = ([regex]'39 81 3C 06 00 00(\s\S\S){6}')
$patch = 'B8 00 01 00 00 89 81 38 06 00 00 90'
$checkPattern=Select-String -Pattern $patternregex -InputObject $dll_as_text
If ($checkPattern -ne $null) {
$dll_as_text_replaced = $dll_as_text -replace $patternregex, $patch
}
Elseif (Select-String -Pattern $patch -InputObject $dll_as_text) {
Write-Output 'The termsrv.dll file is already patch, exitting'
Exit
}
else {
Write-Output "Pattern not found "
}
# модификация файла termsrv.dll
[byte[]] $dll_as_bytes_replaced = -split $dll_as_text_replaced -replace '^', '0x'
Set-Content c:\windows\system32\termsrv.dll.patched -Encoding Byte -Value
$dll_as_bytes_replaced
# Сравним два файла
fc.exe /b c:\windows\system32\termsrv.dll.patched c:\windows\system32\termsrv.dll
# замена оригинального файла
Copy-Item c:\windows\system32\termsrv.dll.patched c:\windows\system32\termsrv.dll
-Force
Set-Acl c:\windows\system32\termsrv.dll $termsrv_dll_acl
Start-Service UmRdpService
Start-Service TermService

Чтобы выполнить скрипт, скачайте его на свой компьютер. Далее измените настройки политики запуска скриптов PowerShell:

Set-ExecutionPolicy Bypass -Scope Process -Force

Затем запускаем сам скрипт в PowerShell от имени администратора:

C:\users\root\desktop\rdp_patch.ps1

Скрипт нужно запускать после установки обновлений Windows, чтобы немедленно внести изменения в файл termsrv.dll (защищает вас от ручной правки файла после каждой установки обновлений). Скрипт подходит для всех версий Windows 10 старше 1809 и для Windows 11. 

Преимущество этого способа включения нескольких RDP сессий в Windows в том, что он работает путем замены файла части файла оригинальной системной библиотеки termsrv.dll. Как следствие, на этот подход не реагируют антивирусы.


                                                           ВНИМАНИЕ!!! У ЭТОГО ПОДХОДА ЕСТЬ ОСОБЕННОСТЬ!                                                           
При каждом обновлении Windows 10 (или при обновлении версии файла termsrv.dll в рамках других обновлений) придется снова запускать скрипт, т.к. оригинальная библиотека вернется в свое прежнее состояние (до патча)!

Ссылки

 

 

 

Копирование данных с помощью RSync

Строение команды

Команда для копирования выглядит вот так:

rsync -aHAX --numeric-ids \
--info=progress2 --human-readable \
--partial --partial-dir=.rsync-partial --inplace --preallocate \
--compress --compress-choice=zstd --zl=3 \
/SOURCE/ user@DEST:/TARGET/

Пояснения к ключам:

--info=progress2 - отображает общий прогресс, скорость и ETA
--partial --partial-dir=.rsync-partial - позволяет докачивать большие файлы (например, 100 ГБ).
--inplace --preallocate - резервирование места и меньше фрагментации на приёмной стороне
-aHAX --numeric-ids - копирование прав, атрибутов, жёстких ссылок, ACL и xattrs (нужно sudo и одинаковые UID/GID)
--compress --compress-choice=zstd --zl=3 - сжатие через zstd (поддерживается в rsync ≥ 3.2). Если rsync старой версии, используйте просто -z
/SOURCE/ - путь к исходным данным (например: /var/www/nextcloud/data/)
user@DEST:/TARGET/ - путь к целевой папке на сервере и данные пользователя (пароль будет запрошен при запуске).

Если много уже сжатого контента (видео, архивы), использование --compress может замедлить процесс. В таком случае, уберите этот параметр.

Рекомендации по запуску

Чтобы копирование пережило обрыв SSH-сессии, используйте tmux:
Установка:

apt install tmux -y

Запуск:

tmux

Внутри сессии выполните команду rsync, которая приведена выше. Если SSH-сессия оборвётся, переподключитесь и получите список сессий:

tmux ls

Подключение к нужной сессии выполняется следующей командой:

tmux attach -t 0 # где 0 — номер сессии

Maintenance-мод для Nginx

Зачем?

Краткий ответ - за тем, чтобы при "отвале" сервиса, клиенты видели не дефолтный HTTP/500 ERROR, а вашу разметку.

Как?

В конфигурационном файле сайта (допустим, /etc/nginx/sites-enabled/default), в блоке server указываем что-то типа такого:

server {  
    # Тут то, что было в блоке до ...   
  
    # ------------------------------------------------------  
    # Общий флаг (/var/www/html/maintenance.flag)  
    # ------------------------------------------------------  
    if (-f /var/www/html/maintenance.flag) {  
        return 503;  
    }  
    # ------------------------------------------------------  
    # Общий обработчик для сервиснах сообщений  
    # ------------------------------------------------------  
    error_page 500 502 503 504 =200 @maintenance;  
    error_page 408         =200 @maintenance;  
    # этот блок оставляем для физической подачи файла  
    location @maintenance {  
        root /var/www/html;  
        rewrite ^(.\*)$ /maintenance.html break;  
    }  
    # Тут то, что было в блоке после ...   
}

После чего применим:

nginx -t && nginx -s reload

Как это работает?

Как только ты создашь файл /var/www/html/maintenance.flag, Nginx увидит это и переведет кластер в состояние обслуживание MAINTENANCE). Вместо сайта на фронтэнд-часть будет отправлен файл /var/www/html/maintenance.html.

Аналогичная ситуация будет в случае получения ошибок с HTTP-кодами 500, 502, 503, 504 и 408  

Бонус: MAINTENANCE-мод только для конкретных IP-адресов


Если ты знаешь адреса хостов администраторов, и при этом не хочешь чтобы они видели сообщение об обслуживании (т.е. им отдавался сайт в том состоянии, в котором он на самом деле сейчас находится, в конфиге сайта используй такую конструкцию:

# ------------------------------------------------------
# Добавился новый блок для директив MAP и GEO, 
# т.к. они не могут располагаться в server
# ------------------------------------------------------
http {
    # Кто считается админом (НЕ видит maintenance)
    geo $is_admin {
        default 0;
        10.10.10.0/24  1;        # вся подсеть
        11.111.111.11  1;        # твой внешний IP
        127.0.0.1      1;        # локально
    }
    # Принимаем решение, показывать ли maintenance этой сессии
    map "$maintenance:$is_admin" $show_maintenance {
        default 0;    # по умолчанию – не показывать
        "1:0" 1;      # если техработы включены И клиент не админ → показываем
    }

}
# ------------------------------------------------------
# Основной блок сайта
# ------------------------------------------------------
server {
    # Тут то, что было в блоке до ... 
    # ------------------------------------------------------
    # Флаг техработ (оставляем как есть, но через переменную)
    set $maintenance 0;
    if (-f /var/www/html/maintenance.flag) { set $maintenance 1; }

    # Блокировка до прокси/роутинга (генерируем исключение, если инстанс в состоянии MAINTENCE)
    if ($show_maintenance) { return 503; }

    # Блок, который указывает на конкретный файл со страницей обслуживания
    error_page 503 =200 @maintenance;
    location @maintenance {
        root /var/www/html;
        rewrite ^(.*)$ /maintenance.html break;
    }
    # ------------------------------------------------------
    # Тут то, что было в блоке после ... 
}

И аналогично предыдущему примеру, после этого выполни:

nginx -t && nginx -s reload

Как проверить что все работает?

Чтобы перевести хост в состояние "на тех. обслуживании", выполни команду:

touch /var/www/html/maintenance.flag

Чтобы снять его с него и прекратить показывать страницу формата "у нас ТО":

rm /var/www/html/maintenance.flag

При этом, никакие службы перезапускать не нужно.


Дата обновления мануала: 23.08.25

Быстрая настройка времени на Linux-хосте

Реально быстро

Чтобы настроить время на хосте, выполни:

timedatectl set-timezone Europe/Moscow

Чтобы получить список зон: 

timedatectl list-timezones

 

Установка WayDroid

Какую проблему будем решать?

В какой-то момент мне понадобилось заиметь Android-хост в своей инфраструктуре. Да не простой, а с GooglePlay и, по возможности, не заставляющий ЦПУ биться в конвульсиях при запуске первого инстанса.

Основная загвоздка тут в том, что решение должно так или иначе вписываться в мои реалии - наличие хоста Proxmox и нескольких (десятков) ВМ. Вариант с LXC я отмел сразу, т.к. мне нужен был ванильный Android, а не его сборка. К тому же, я бы не хотел на одной ноде держать и LXC, и ВМ.   

И я нашел решение этой проблемы - WayDroid.

image.png

Как мы ее будем решать?

В первую очередь, определимся с вводными. Я буду:

  1. Создавать ВМ на Ubuntu Server 24.04 (это важно, т.к. на момент написания статьи это самый стабильный дистрибутивы под такие цели);
  2. Устанавливать и настраивать систему как в GUI, так и в CLI (я почему отдельно что и где нужно сделать).

Процесс установки

Создание ВМ

В PVE:

  1. Загружаем и импортируем образ Ubuntu Server 22.04 в PVE (прямая ссылка на ISO-образ Ubuntu)
  2. Создаем ВМ с произвольным названием указываем наш ISO как загрузочный (установочный) образ
  3. Перед ее запуском, сверяемся с параметрами:
    1. Ресурсы (рекомендовано под 1 ВМ, можно и больше):
      1. ЦПУ, ядер: 4 (важно: тип ЦПУ - "host")
      2. ОЗУ, ГБ: 4
      3. СХД, ГБ: 75 (в моем случае, SSD)
    2. Machine: q35
    3. Display: SPICE
  4. (ВАЖНО!) Устанавливаем сетевой адаптер - для установки нам понадобится интернет 

Получается примерно так:

image.png

Дальше, устанавливаем ОС. Из важных моментов:

  1. Ubuntu наверняка вам предложит обновить установочный скрипт на более новую версию. В моменте, лучше согласиться с обновлением. Если вы не согласились - ничего страшного, на работу системы это не влияет; 
  2. На моменте разметки, выделите LVM больше места (чтобы убрать свободное место, в установщике оно помечено как "free space");
  3. Мы установили Display типа SPICE (qxl). Это сыграет в плане производительности. Но с нюансом... 

Важно понимать, что привычная Console в ВЕБ (сверху справа) через него не откроется - в моем случае, PVE 9 версии по этой кнопке начинает скачивать обрывки сертификата (и это не зависит от браузера). Не пугайтесь, установку вы можете продолжить из одноименной вкладки слева:

image.png

В крайнем случае, вы можете временно изменить значение параметра на default.

TIP: Переходите к следующему после того, как вы установите систему

Установка WayDroid

Установка GUI

Важно понимать, что WayDroid работает исключительно в GUI. При том, только на Wayland.

Поэтому первым шагом его необходимо установить. Делается это 3 командами:

# Опционально: установка времени (в примере - Москва)
timedatectl set-timezone Europe/Moscow
# Синхронизация с репозиториями 
apt update
# Установка последних обновлений пакетов на ОС (может занять какое-то время)
apt upgrade -y
# Установка доп. ПО - оно будет полезно дальше
apt install -y curl wget nano
# Установка GUI на Wayland (может занять какое-то большое кол-во времени)
apt install -y ubuntu-desktop-minimal

После установки, нам нужно сделать еще 1 вещь: заставить ОС грузиться не в терминальную, а в графическую оболочку. Делается это двумя командами, включая перезагрузку.

sudo systemctl set-default graphical.target
reboot

Ждем, пока ВМ перезагрузится. Затем - первый вход.

                                                                                                       Важно!                                                                                                     

  1. Входим от обычного пользователя (не root);
  2. При входе, после выбора пользователя, нажимаем на "шестеренку" (⚙️) и выбираем Ubuntu (без каких-либо приписок).  

После первого входа в ОС, в терминале проверяем, что мы используем верный GUI-менеджер:

echo $XDG_SESSION_TYPE
# Ожидаем вывод:
# wayland

Если вывод совпадает (wayland) - переходим к дальнейшей установке. В противном случае - гуглим

Установка эмулятора WayDroid

Здесь все просто. Все делается в 3 команды:

sudo apt install -y curl
curl https://repo.waydro.id | sudo bash
sudo apt install -y waydroid

После этого, у вас должна появиться команда "waydroid" в CLI.

Запуск первого приложения

Скачивание образов

Скачиваем образы Android:

# Без сервисов GooglePlay
sudo waydroid init
# С сервисами GooglePlay (не самый стабильный, но возможный вариант)
sudo waydroid init -s GAPPS

В случае, если вы получаете таймаут:

image.png

Вы можете попробовать временно отключить IPv6. Часто, это решает проблему:

sudo sysctl -w net.ipv6.conf.all.disable_ipv6=1
sudo sysctl -w net.ipv6.conf.default.disable_ipv6=1
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1

После чего повторите команды выше.

Запуск приложения

                          Если здесь будут использованы команды из CLI - они выполняются в GUI-сессии, а не через SSH!                         

Теперь, мы можем попробовать запустить свой первый инстанс. В CLI кастуем в разных терминалах:

waydroid session start
waydroid show-full-ui

И ждем, пока Waydroid запустит сессию. Поздравляю! Вам удалось. Выглядеть оно будет примерно вот так:

Главный экран

image.png

Браузер

image.png

Сайд-бар (доступен по клику сверху)

image.png

Настройки

image.png

Галерея

image.png

RDP на Linux (GNOME Remote Desktop)

Этот метод подойдет для GUI Gnome с менеджером Wayland: встает "болт-он", работает быстрее и стабильнее аналогов.
Однако, с другими GUI и менеджерами сессий могут возникнуть проблемы - там лучше не экспериментировать с GRDP, а рассмотреть аналоги

Что нужно знать перед установкой

Перед тем как идти по этому мануалу, вам нужно знать МИНУСЫ этого подхода:

  1. Подключения будет работать только до того момента, пока работает пользовательская сессия. Если пользователь уйдет в Lock-screen, удаленный доступ пропадет
  2. MSTSC.exe (удаленный рабочий стол Windows) не сможет работать с этим решением в связи с отсутствием у последнего механизма NLA (Network Layer Authentification). MObaXTerm - тоже не сможет подключиться, как использует MSTSC.exe под капотом 
  3. GNOME Remote Desktop НЕ является полноценным сервером RDP, и уж тем более не дружит с Windows-way клиентами. С этим решением в целом работает очень мало какое ПО под Windows, но в списке таких звездочек - Remmina и FreeRDP   

Из плюсов - скорость и стабильность работы (по слухам, я так и не удосужился проверять). 

Установка

Шаг 1. Установка  GRDP

apt update
apt install -y gnome-remote-desktop

Все дальнейшие шаги будут выполняться от пользователя user1 (не от root!) и в GUI (не в терминале!)

Шаг 2. Включаем RDP

gsettings set org.gnome.desktop.remote-desktop.rdp enable true

Шаг 3. Создание учетных данных для пользователя

Для создания пользователя, выполняем команду от имени того, кто будет заходить на сервер (не root!). Например, для user1:

su - user1
# Чтобы пароль не светился и не попадал в history:
read -rp "RDP user: " RDP_USER
read -srp "RDP pass: " RDP_PASS; echo
grdctl rdp set-credentials "$RDP_USER" "$RDP_PASS"
unset RDP_USER RDP_PASS

Шаг 4. Разрешаем подключение

grdctl rdp enable
# Убираем view-only-mod (блокировка ввода через RDP)
grdctl rdp disable-view-only
# Автозагрузка
systemctl --user enable --now gnome-remote-desktop

Шаг 5. Заранее открываем порт 3389 (пример с UFW)

Если порт 3389 будет закрыть, подключения не произойдет. Чтобы открыть его через UFW, выполняем: 

sudo ufw allow 3389/tcp
sudo ufw reload
# Посмотреть список правил можно так:
sudo ufw status

Шаг 6. Скорее всего, в логах gnome-remote-desktop при первом запуске вы увидите что-то такое:

● gnome-remote-desktop.service - GNOME Remote Desktop
     Loaded: loaded (/usr/lib/systemd/user/gnome-remote-desktop.service; enabled; vendor preset: enabled)
     Active: active (running) since Wed 2026-01-14 20:30:15 MSK; 3min 8s ago
   Main PID: 31760 (gnome-remote-de)
      Tasks: 4 (limit: 9379)
     Memory: 15.4M
        CPU: 319ms
     CGroup: /user.slice/user-1000.slice/user@1000.service/app.slice/gnome-remote-desktop.service
             └─31760 /usr/libexec/gnome-remote-desktop-daemon

янв 14 20:30:15 home-srv-android systemd[1683]: Starting GNOME Remote Desktop...
янв 14 20:30:15 home-srv-android systemd[1683]: Started GNOME Remote Desktop.
янв 14 20:30:15 home-srv-android gnome-remote-de[31760]: RDP TLS certificate and key not configured properly
янв 14 20:32:50 home-srv-android systemd[1683]: Started GNOME Remote Desktop.
янв 14 20:33:23 home-srv-android systemd[1683]: Started GNOME Remote Desktop.

И порт 3389 слушаться не будет, сколько бы вы не перезагружали службу.

Проблема тут вот в этой ошибке:

RDP TLS certificate and key not configured properly

И тут есть 2 варианта решения проблемы: использовать самоподписанный или нормальный TLS-сертификат.

Решение через self-signed TLS

1. Генерируем самоподписанный сертификат в ~/.local/share/grd:

mkdir -p ~/.local/share/grd
openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -nodes \
  -keyout ~/.local/share/grd/rdp.key \
  -out ~/.local/share/grd/rdp.crt \
  -subj "/CN=home-srv-android"
chmod 600 ~/.local/share/grd/rdp.key

Важно:
1. Ключ должен быть без пароля (unencrypted PEM);
2. Файлы должны быть читаемы пользователем, который будет подключаться (иначе демон их не прочтет).

Если у вас есть свой TLS-сертификат, просто подложите его в директорию:
1. Сертификат: ~/.local/share/grd/rdp.crt
2. Приватный ключ: ~/.local/share/grd/rdp.key
Или измените команду ниже

2. Привязываем его к GRDP:

grdctl rdp set-tls-cert ~/.local/share/grd/rdp.crt
grdctl rdp set-tls-key  ~/.local/share/grd/rdp.key
grdctl rdp enable

3. Перезапускаем сервис и проверяем, что он запущен

# Перезапуск делается от имени ЭТОГО ПОЛЬЗОВАТЕЛЯ, А НЕ ROOT!
systemctl --user restart gnome-remote-desktop
# Проверяем статус
grdctl status
# Проверяем, что порт слушается
sudo ss -ltnp | grep -E ':3389'

ВАЖНОЕ ПРАВИЛО!
ВСЕ команды типа grdctl и systemctl --user выполняются ТОЛЬКО от того пользователя, кому они предназначены (без sudo). В нашем случае, это user1

Как настроить Outlook 2019 для использования моего почтового сервера

Процесс добавления УЗ

В Outlook при добавлении учётки:

  1. Введите email
  2. Выберите "Дополнительные параметры"
  3. Далее, “Настроить вручную”. Там выберите IMAP

Дальше нужно настроить параметры почты:

Входящая (IMAP)

  1. Сервер: mail.elijahkamsky.ru
  2. Порт: 993
  3. Шифрование: SSL/TLS
  4. Метод входа: Обычный пароль
  5. Имя пользователя: полный email (*****@elijahkamsky.ru)
  6. Исходящая (SMTP)

Сервер: mail.elijahkamsky.ru

  1. Порт: 587
  2. Шифрование: STARTTLS
  3. Требуется проверка подлинности: Да
  4. Имя пользователя/пароль: те же

В дополнительных настройках Outlook проверьте, чтобы SPA (Secure Password Authentication) было выключено. Outlook иногда пытается его включить.

Внешний вид почтовика

image.png

Пример сообщения на внешние электронные адреса

image.png

Настройка скрипта автозапуска через SystemD-юнит (серверный подход)

Предисловие

Какие существуют подходы к автозагрузке в Linux?

Есть 3 самых популярных подхода решения задачи "я хочу, чтобы скрипт запускался после перезагрузки сервера":

  1. Через файл rc.local - устаревший подход, да и правка системных конфигов, даже таких, всегда чревата;
  2. @reboot в cron - это явный костыль, хотя многие со мной не согласятся;
  3. Скрипты в духе "sleep 60 && ..." - это зло. Они не дают контроля и работают буквально "на шару". Слишком много ответственности для простого bash-скрипта.

ИМХО автора
Для серверов единственно правильный путь решения этой проблемы сейчас - systemd unit

Почему это лучше, чем альтернативные подходы?

Подход автора лучше, потому что он:

  1. Дает значительно больше контроля над сервисом: его остановка, внеплановый запуск или сбор информации о его работе становятся значительно проще для администратора - для этого будут использоваться стандартные утилиты, к которым все привыкли (systemctl и journalctl);  
  2. Разделяет ответственность: логика скрипта находится отдельно от логики, которая ответственна за его запуск;
  3. Позволяет менять логику скрипта отдельно, не меняя любой внешней логики (атомарность в подходе).   

Пример настройки скрипта для перезапуска сервисов

Пишем сервисный файл (SystemD-юнит)

Я редактирую файл юнита. Допустим, он будет называться post-reboot-tasks, тогда:

nano /etc/systemd/system/post-reboot-tasks.service

 В самом юните я делаю следующее:

[Unit]
Description=Post reboot tasks
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=/opt/scripts/post-reboot.sh
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

Почему это работает?

Тут у читателя может возникнуть вопрос: А с чего ты взял, что оно сработает только при перезагрузке? Где ты здесь указываешь, что он будет выполнен именно и только после ребута системы? 

Отвечаю

В SystemD это делается не отдельной строкой в духе "AfterReboot=true", а самим фактом, что юнит включён в target, который достигается при загрузке. В этом примере вот эта строчка и говорит, "запускать при старте системы":

WantedBy=multi-user.target

Когда вы делаете:

systemctl enable post-reboot-tasks

SystemD создаёт symlink юнита в:

/etc/systemd/system/multi-user.target.wants/post-reboot-tasks.service

А multi-user.target - это стандартная "нормальная загрузка сервера без GUI" (условно, runlevel=3). Каждый раз, когда система доходит до этого Target'а  (то есть после ребута/бут-цикла), SystemD подхватит юнит и запустит его.

Здесь "после ребута" = "после каждой загрузки", т.е.  юнит будет выполняться каждый boot.

Подготовка скрипта

Здесь все просто. Я:

  1. Создаю директорию, которую ранее указывал в сервисном файле;
  2. Создаю и заполняю скрипт внутри нее (см. пример ниже);
  3. Выдаю необходимые права для запуска скрипта;
  4. Применяю изменения для SystemD, чтобы юнит появился в списке и попал в пул активных.

image.png

Команда, которой я это сделал:

mkdir -p /opt/scripts/ && \
nano /opt/scripts/post-reboot.sh && \
chmod +x /opt/scripts/post-reboot.sh && \
systemctl daemon-reexec && systemctl daemon-reload && \
systemctl enable --now post-reboot-tasks

Теперь, когда создан скрипт и юнит-файл попал в реестр SystemD, он будет запускаться при каждой перезагрузке

Пример наполнения скрипта

Перейдем к примерам скриптов. В моем случае, я бы хотел автоматический перезапуска ряда сервисов после того, как система загрузилась и получила IP-адрес. Именно это я и делаю здесь:

image.png

В случае, если вы захотите изменить его логику, вы можете просто отредактировать скрипт. Перезапускать юнит или указывать SystemD о его изменении не нужно.

                                    Важное уточнение, которое я вынесу отдельно                                   

Скрипт будет запускаться при загрузке системы только в том случае, если его статус - enabled. В противном случае скрипт не попадет в автозагрузку. Проверить это можно командой:

systemctl status post-reboot-tasks

image.png

Генерация нескольких сотен тестовых файлов в цикле (Linux)

Генерация

for i in {1..501}; do echo $(date +%T_%s) > "${i}.txt" ; echo "$i"; done
# Sample content:
# 12:22:07_1769505327
for i in {1..501}; do echo "$(date +%T_%s && openssl rand -base64 24)" > "${i}.txt" ; echo -e "Generated \e[1;34m${i}.txt\e[0m"; done
# Sample content:
# 12:22:07_1769505727
# vCN9MgYoOe1PLQiitXEGhSWqaCcFwqba
for i in {1..501}; do fname="$(openssl rand -hex 32)"; echo -e "$(date +%T_%s)\n$fname" > "${fname}${i}.txt" ; echo -e "Generated \e[1;34m${fname}${i}.txt\e[0m"; done
# Sample content:
# 12:28:20_1769506100
# 48a2a31880bb565ca40baa3147a35c70786dfcca689de6f453690dd8e9f24d30

image.png

Создание архива

Создание архива одной командой (все txt-файлы в этой директории):

zip -r archive_$(date +%F).zip ./*.txt 

image.png

Бонус: сильно ускоренная версия скрипта

#!/bin/bash
# Генерация 10k файлов
count=10000
for ((i=1;i<=count;i++)); do
  fname="$(openssl rand -hex 32)"
  printf -v now '%(%T_%s)T' -1           # builtin, без fork()
  printf '%s\n%s\n' "$now" "$fname" "$RANDOM" > "${fname}${i}.txt"
  # логирует реже, иначе терминал станет бутылочным горлышком
  (( i % 500 == 0 )) && echo "Generated $i files..."
done
echo -e "\e[1;34mDONE!\e[0m"

Но здесь есть нюанс: оно работает быстро только на SSD, т.к. каждый файл нужно записать на диск. И тут упор будет уже в скорость чтения/записи. И разница критичная. Вот пример для SSD:

image.png

На HDD этот процесс будет выполняться более чем в 100 раз дольше.

Бонус 2: подсчет кол-ва файлов в директории

Чтобы посчитать кол-во файлов в директории, можно использовать:

ls -1 | wc -l

Но с этой командой есть нюанс: она считает вообще все записи, включая директории. Чтобы посчитать только файлы с расширением .txt можно использовать GREP:

ls -1 | grep '.txt' | wc -l

Разница видна здесь:

image.png

RU locale setup on Linux (debian-based only)

sudo localectl set-locale LANG=ru_RU.UTF-8 && export LANG=ru_RU.UTF-8 LC_ALL=ru_RU.UTF-8

 

Скрипт для генерации произвольной структуры файлов и папок

Скрипт генерации

Новая версия

#!/usr/bin/env bash
set -euo pipefail

# === Настройки (можно переопределять через ENV) ===
ROOT_DIR="${ROOT_DIR:-./upload_test_tree}"  # куда генерить
TOTAL_ITEMS="${TOTAL_ITEMS:-40000}"         # всего объектов (файлы+папки)
FILES_COUNT="${FILES_COUNT:-10000}"         # сколько файлов
MAX_DEPTH="${MAX_DEPTH:-8}"                 # максимальная глубина (root=0)
PRINT_EVERY="${PRINT_EVERY:-1000}"          # прогресс раз в N созданий

# === Валидация ===
if (( FILES_COUNT > TOTAL_ITEMS )); then
  echo "FILES_COUNT ($FILES_COUNT) не может быть больше TOTAL_ITEMS ($TOTAL_ITEMS)" >&2
  exit 1
fi

DIRS_COUNT=$((TOTAL_ITEMS - FILES_COUNT))   # директорий (включая root? нет)
# Мы отдельно создаём root, поэтому целевая сумма директорий = DIRS_COUNT + 1

rm -rf -- "$ROOT_DIR"
mkdir -p -- "$ROOT_DIR"

# Списки директорий и их глубин
dirs=("$ROOT_DIR")
depths=(0)

created_dirs=1      # root уже создан
created_files=0
created_total=0     # будем считать созданные "объекты" сверх root

mkname() {
  local prefix="$1"
  # Быстро и "достаточно уникально" без urandom/openssl:
  # счётчик + BASHPID + RANDOM
  echo "${prefix}_${BASHPID}_${created_total}_${RANDOM}${RANDOM}"
}

pick_parent_for_file() {
  echo $(( RANDOM % ${#dirs[@]} ))
}

pick_parent_for_dir() {
  local tries=8 idx
  while (( tries-- > 0 )); do
    idx=$(( RANDOM % ${#dirs[@]} ))
    if (( depths[idx] < MAX_DEPTH )); then
      echo "$idx"
      return
    fi
  done
  for idx in "${!dirs[@]}"; do
    if (( depths[idx] < MAX_DEPTH )); then
      echo "$idx"
      return
    fi
  done
  # если всё на MAX_DEPTH — просто вернём любой
  echo $(( RANDOM % ${#dirs[@]} ))
}

echo "ROOT_DIR=$ROOT_DIR"
echo "TOTAL_ITEMS=$TOTAL_ITEMS (dirs=$DIRS_COUNT, files=$FILES_COUNT), MAX_DEPTH=$MAX_DEPTH"
echo

target_dirs=$((DIRS_COUNT + 1))   # + root
target_files=$FILES_COUNT
target_total=$TOTAL_ITEMS

while (( (created_dirs + created_files) < target_total )); do
  remaining_dirs=$(( target_dirs - created_dirs ))
  remaining_files=$(( target_files - created_files ))

  make_dir=0
  if (( remaining_dirs > 0 && remaining_files > 0 )); then
    roll=$(( RANDOM % (remaining_dirs + remaining_files) ))
    if (( roll < remaining_dirs )); then make_dir=1; fi
  elif (( remaining_dirs > 0 )); then
    make_dir=1
  else
    make_dir=0
  fi

  if (( make_dir == 1 )); then
    pidx="$(pick_parent_for_dir)"
    parent="${dirs[pidx]}"
    dname="$(mkname d)"
    newdir="$parent/$dname"
    mkdir -p -- "$newdir"
    dirs+=("$newdir")
    depths+=($(( depths[pidx] + 1 )))
    ((++created_dirs))
  else
    pidx="$(pick_parent_for_file)"
    parent="${dirs[pidx]}"
    fname="$(mkname f)"
    : > "$parent/$fname.bin"
    ((++created_files))
  fi

  ((++created_total))

  if (( created_total % PRINT_EVERY == 0 )); then
    echo "Created: total=$((created_dirs + created_files)) dirs=$created_dirs files=$created_files (dir_pool=${#dirs[@]})"
  fi
done

echo
echo "DONE."
echo "Path: $ROOT_DIR"
echo "Dirs created (incl root): $created_dirs"
echo "Files created: $created_files"
echo "Total created: $((created_dirs + created_files))"

Пример запуска скрипта

image.png

Пример генерируемой структуры

image.png

                                     Старая версия                                

#!/usr/bin/env bash
set -euo pipefail

# === Настройки (можно переопределять через ENV) ===
ROOT_DIR="${ROOT_DIR:-./upload_test_tree}"  # куда генерить
TOTAL_ITEMS="${TOTAL_ITEMS:-40000}"         # всего объектов (файлы+папки)
FILES_COUNT="${FILES_COUNT:-10000}"         # сколько файлов
MAX_DEPTH="${MAX_DEPTH:-8}"                 # максимальная глубина (root=0)
PRINT_EVERY="${PRINT_EVERY:-1000}"          # прогресс раз в N созданий

# === Валидация ===
if (( FILES_COUNT > TOTAL_ITEMS )); then
  echo "FILES_COUNT ($FILES_COUNT) не может быть больше TOTAL_ITEMS ($TOTAL_ITEMS)" >&2
  exit 1
fi

DIRS_COUNT=$((TOTAL_ITEMS - FILES_COUNT))   # директорий (включая root? нет)
# Мы отдельно создаём root, поэтому целевая сумма директорий = DIRS_COUNT + 1

rm -rf -- "$ROOT_DIR"
mkdir -p -- "$ROOT_DIR"

# Списки директорий и их глубин
dirs=("$ROOT_DIR")
depths=(0)

created_dirs=1      # root уже создан
created_files=0
created_total=0     # будем считать созданные "объекты" сверх root

mkname() {
  local prefix="$1"
  # Быстро и "достаточно уникально" без urandom/openssl:
  # счётчик + BASHPID + RANDOM
  echo "${prefix}_${BASHPID}_${created_total}_${RANDOM}${RANDOM}"
}

pick_parent_for_file() {
  echo $(( RANDOM % ${#dirs[@]} ))
}

pick_parent_for_dir() {
  local tries=8 idx
  while (( tries-- > 0 )); do
    idx=$(( RANDOM % ${#dirs[@]} ))
    if (( depths[idx] < MAX_DEPTH )); then
      echo "$idx"
      return
    fi
  done
  for idx in "${!dirs[@]}"; do
    if (( depths[idx] < MAX_DEPTH )); then
      echo "$idx"
      return
    fi
  done
  # если всё на MAX_DEPTH — просто вернём любой
  echo $(( RANDOM % ${#dirs[@]} ))
}

echo "ROOT_DIR=$ROOT_DIR"
echo "TOTAL_ITEMS=$TOTAL_ITEMS (dirs=$DIRS_COUNT, files=$FILES_COUNT), MAX_DEPTH=$MAX_DEPTH"
echo

target_dirs=$((DIRS_COUNT + 1))   # + root
target_files=$FILES_COUNT
target_total=$TOTAL_ITEMS

while (( (created_dirs + created_files) < target_total )); do
  remaining_dirs=$(( target_dirs - created_dirs ))
  remaining_files=$(( target_files - created_files ))

  make_dir=0
  if (( remaining_dirs > 0 && remaining_files > 0 )); then
    roll=$(( RANDOM % (remaining_dirs + remaining_files) ))
    if (( roll < remaining_dirs )); then make_dir=1; fi
  elif (( remaining_dirs > 0 )); then
    make_dir=1
  else
    make_dir=0
  fi

  if (( make_dir == 1 )); then
    pidx="$(pick_parent_for_dir)"
    parent="${dirs[pidx]}"
    dname="$(mkname d)"
    newdir="$parent/$dname"
    mkdir -p -- "$newdir"
    dirs+=("$newdir")
    depths+=($(( depths[pidx] + 1 )))
    ((++created_dirs))
  else
    pidx="$(pick_parent_for_file)"
    parent="${dirs[pidx]}"
    fname="$(mkname f)"
    : > "$parent/$fname.bin"
    ((++created_files))
  fi

  ((++created_total))

  if (( created_total % PRINT_EVERY == 0 )); then
    echo "Created: total=$((created_dirs + created_files)) dirs=$created_dirs files=$created_files (dir_pool=${#dirs[@]})"
  fi
done

echo
echo "DONE."
echo "Path: $ROOT_DIR"
echo "Dirs created (incl root): $created_dirs"
echo "Files created: $created_files"
echo "Total created: $((created_dirs + created_files))"

Пример использования скрипта 

Настройки генерации:

ROOT_DIR="${ROOT_DIR:-./upload_test_tree}"  # куда генерить
TOTAL_ITEMS="${TOTAL_ITEMS:-40000}"         # всего объектов (файлы+папки)
FILES_COUNT="${FILES_COUNT:-10000}"         # сколько файлов
MAX_DEPTH="${MAX_DEPTH:-8}"                 # максимальная глубина (root=0)
PRINT_EVERY="${PRINT_EVERY:-40000}"         # прогресс раз в N созданий

Пример использования вывода с такими настройками:

image.png

Быстрая активация Windows и MS Office

Для быстрой активации Windows и MS Office:

  1. Откройте PowerShell от имени администратора;
  2. Выполните команду:
irm https://get.activated.win | iex

Важно: для этого типа активации нужно стабильное интернет-подключение.

Выпуск и обновление сертификата Let's Encrypt (WildCard) через DNS

Что я хочу сделать? 

У меня есть доступ к записям на публичном DNS-сервере, который содержит мой домен. Я хочу перевыпустить TLS-сертификат через CertBot 

Как мне это сделать?

На примере домена elijahkamsky.ru:

certbot certonly \
  --manual \
  --preferred-challenges dns \
  -d elijahkamsky.ru \
  -d *.elijahkamsky.ru
_acme-challenge.elijahkamsky.ru

Например:

_acme-challenge.elijahkamsky.ru TXT qwe123asdasd

После добавления записи — сертификат выпустится автоматически.

Где лежит сертификат?

После выпуска сертификат будет в директории:

/etc/letsencrypt/live/elijahkamsky.ru/

Там:

cert.pem
chain.pem
fullchain.pem
privkey.pem

Обычно веб-сервера используют только:

fullchain.pem
privkey.pem

                                            Обратите внимание!                                             

Сертификат будет выпущен только после того, как основная часть публичных DNS-серверов обновит вашу запись.

Полное обновление DNS-записей может занимать вплоть до 48 часов.

Проверить это можно тут: https://dnschecker.org/

Или тут: https://toolbox.googleapps.com/apps/dig/#TXT

Тип записи: TXT

Пример проверки:

image.png

Активация Windows и MS Office

"Мне прям по-быстрому для Windows 10 PRO"

В Poweshell от имени Администратора:

slmgr /ipk W269N-WFGWX-YVC9B-4J6C9-T83GX
slmgr /skms kms8.msguides.com
slmgr /ato
slmgr /xpr

Я хочу почитать

Чтобы активировать Windows 10 существует 2 метода:

  1. Использовать скрипт автоматической активации

В Powershell от имени администратора вставляем:

irm https://get.activated.win | iex

Дальше скрипт все сделает за нас. Ориентируйтесь на меню скрипта и встроенные подсказки для работы с ним

  1. Активация через службу KMS (вручную)
slmgr /skms kms.lotro.cc
slmgr /ipk "SERIAL NUMBER HERE"

Замените SERIAL NUMBER HERE в команде на 1 из доступных ключей в зависимости от редакции Windows:

# kms.lotro.cc
Home/Core                            TX9XD-98N7V-6WMQ6-BX7FG-H8Q99        
Home/Core (Country Specific)         PVMJN-6DFY6-9CCP6-7BKTT-D3WVR  
Home/Core (Single Language)          7HNRX-D7KGG-3K4RQ-4WPJ4-YTDFH  
Home/Core N                          3KHY7-WNT83-DGQKR-F7HPR-844BM 

Professional                         W269N-WFGWX-YVC9B-4J6C9-T83GX 
Professional N                       MH37W-N47XK-V7XM9-C7227-GCQG9 
Enterprise                           NPPR9-FWDCX-D2C8J-H872K-2YT43 
Enterprise N                         DPH2V-TTNVB-4X9Q3-TJR4H-KHJW4 

Education                            NW6C2-QMPVW-D7KKK-3GKT6-VCFB2 
Education N                          2WH4N-8QGBV-H22JP-CT43Q-MDWWJ 

Enterprise 2015 LTSB                 WNMTR-4C88C-JK8YV-HQ7T2-76DF9
Enterprise 2015 LTSB N               2F77B-TNFGY-69QQF-B8YKP-D69TJ 
Enterprise 2016 LTSB                 DCPHK-NFMTC-H88MJ-PFHPY-QJ4BJ  
Enterprise 2016 LTSB N               QFFDN-GRT3P-VKWWX-X7T3R-8B639

# kms8.msguides.com
Home: TX9XD-98N7V-6WMQ6-BX7FG-H8Q99
Home N: 3KHY7-WNT83-DGQKR-F7HPR-844BM
Home Single Language: 7HNRX-D7KGG-3K4RQ-4WPJ4-YTDFH
Home Country Specific: PVMJN-6DFY6-9CCP6-7BKTT-D3WVR

Professional: W269N-WFGWX-YVC9B-4J6C9-T83GX
Professional N: MH37W-N47XK-V7XM9-C7227-GCQG9

Education: NW6C2-QMPVW-D7KKK-3GKT6-VCFB2
Education N: 2WH4N-8QGBV-H22JP-CT43Q-MDWWJ

Enterprise: NPPR9-FWDCX-D2C8J-H872K-2YT43
Enterprise N: DPH2V-TTNVB-4X9Q3-TJR4H-KHJW4

Например, slmgr /ipk W269N-WFGWX-YVC9B-4J6C9-T83GX

slmgr /ato

Если сервер ответил положительно и ваша ревизия Windows совпадает с той, к которой вы указали ключ, то вы увидите окно с подтверждением активации Windows:

Ссылка на репозиторий

Настройка мультиязычности и поддержки кириллицы в Linux

Этот мануал описывает, как корректно настроить отображение русских символов (UTF-8) в Linux-системе: консоль, SSH, приложения, systemd, Docker и Ansible.


🔍 Как понять, есть ли проблема

Типичные симптомы:

Проверка текущего состояния:

locale

❌ Плохо:

LANG=C
LC_ALL=

✅ Хорошо:

LANG=ru_RU.UTF-8
LC_ALL=

📦 Установка необходимых локалей

Debian / Ubuntu

apt update
apt install -y locales

RHEL / Rocky / Alma / CentOS Stream

dnf install -y glibc-langpack-ru

Arch Linux

pacman -S glibc

🧩 Генерация локали ru_RU.UTF-8

Debian / Ubuntu

nano /etc/locale.gen

Раскомментировать:

ru_RU.UTF-8 UTF-8

Сгенерировать:

locale-gen

🌐 Установка локали по умолчанию

localectl set-locale LANG=ru_RU.UTF-8

🖥 Принудительная установка (cron, systemd, Ansible)

/etc/environment

LANG=ru_RU.UTF-8
LC_ALL=ru_RU.UTF-8

🔐 SSH

В /etc/ssh/sshd_config:

AcceptEnv LANG LC_*
systemctl restart sshd

🧰 systemd

В unit-файле:

[Service]
Environment="LANG=ru_RU.UTF-8"
Environment="LC_ALL=ru_RU.UTF-8"
systemctl daemon-reexec

🐳 Docker

ENV LANG=ru_RU.UTF-8
ENV LC_ALL=ru_RU.UTF-8

🛠 Ansible

ansible_env:
  LANG: ru_RU.UTF-8
  LC_ALL: ru_RU.UTF-8

🧪 Проверка

echo "Привет, мир"
touch тест.txt
ls

✅ Итог

Если локаль сгенерирована и LANG=ru_RU.UTF-8 задан глобально — кириллица будет работать везде.