# Общие фишки

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

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

- **Установить пакет <span style="color: rgb(45, 194, 107);">apache2-utils</span>**

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

```bash
apt-get install apache2-utils -y
```

- **Создать файл с пользователями и паролями**

```bash
# С указанием имени пользователя:
htpasswd -c /etc/nginx/.htpasswd username
```

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

```bash
sudo chmod 640 /etc/nginx/.htpasswd
sudo chown root:www-data /etc/nginx/.htpasswd
```

- **Настроить Nginx**

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

**Обратите внимание:** аутентификация работает <span style="text-decoration: underline;">только на конкретный location</span> (и все, что за ним), а не на весь возможный пул

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

```bash
nginx -t && nginx -s reload
```

При переходе на эндпоинт <span style="color: rgb(45, 194, 107);">/protected</span> (и все, что находится за ним), вам будет предложено ввести логин и пароль, который вы сгенерировали ранее

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

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

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

<span style="color: rgb(255, 255, 255); background-color: rgb(186, 55, 42);">**<span style="color: rgb(255, 255, 255); background-color: rgb(186, 55, 42);"><span style="color: rgb(255, 255, 255); background-color: rgb(186, 55, 42);"><span style="color: rgb(255, 255, 255); background-color: rgb(186, 55, 42);"> </span><span style="color: rgb(255, 255, 255); background-color: rgb(186, 55, 42);"> </span></span><span style="color: rgb(255, 255, 255); background-color: rgb(186, 55, 42);"><span style="color: rgb(255, 255, 255); background-color: rgb(186, 55, 42);"> </span><span style="color: rgb(255, 255, 255); background-color: rgb(186, 55, 42);"> </span></span><span style="color: rgb(255, 255, 255); background-color: rgb(186, 55, 42);"><span style="color: rgb(255, 255, 255); background-color: rgb(186, 55, 42);"> </span><span style="color: rgb(255, 255, 255); background-color: rgb(186, 55, 42);"> </span></span><span style="color: rgb(255, 255, 255); background-color: rgb(186, 55, 42);"><span style="color: rgb(255, 255, 255); background-color: rgb(186, 55, 42);"> </span><span style="color: rgb(255, 255, 255); background-color: rgb(186, 55, 42);"> </span></span><span style="color: rgb(255, 255, 255); background-color: rgb(186, 55, 42);"><span style="color: rgb(255, 255, 255); background-color: rgb(186, 55, 42);"> </span><span style="color: rgb(255, 255, 255); background-color: rgb(186, 55, 42);"> </span></span></span>ВАЖНО!<span style="color: rgb(255, 255, 255); background-color: rgb(186, 55, 42);"><span style="color: rgb(255, 255, 255); background-color: rgb(186, 55, 42);"> </span><span style="color: rgb(255, 255, 255); background-color: rgb(186, 55, 42);"> </span></span><span style="color: rgb(255, 255, 255); background-color: rgb(186, 55, 42);"><span style="color: rgb(255, 255, 255); background-color: rgb(186, 55, 42);"> </span><span style="color: rgb(255, 255, 255); background-color: rgb(186, 55, 42);"> </span></span><span style="color: rgb(255, 255, 255); background-color: rgb(186, 55, 42);"><span style="color: rgb(255, 255, 255); background-color: rgb(186, 55, 42);"> </span><span style="color: rgb(255, 255, 255); background-color: rgb(186, 55, 42);"> </span></span><span style="color: rgb(255, 255, 255); background-color: rgb(186, 55, 42);"><span style="color: rgb(255, 255, 255); background-color: rgb(186, 55, 42);"> </span><span style="color: rgb(255, 255, 255); background-color: rgb(186, 55, 42);"> </span></span><span style="color: rgb(255, 255, 255); background-color: rgb(186, 55, 42);"><span style="color: rgb(255, 255, 255); background-color: rgb(186, 55, 42);"> </span><span style="color: rgb(255, 255, 255); background-color: rgb(186, 55, 42);"> </span></span>** </span>

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

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

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

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

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

Далее необходимо:  
1\. Ввести путь, куда будут сохранены ключи – по-умолчанию это <span style="color: rgb(45, 194, 107);">**~/.ssh/id\_rsa**</span>   
2\. Ввести парольную фразу для расшифровки приватного ключа. Если оставить поле пустым, при его использовании пароль не будет запрашиваться  
Так же важно добавить публичный ключ в один из конфигурационных файлов – <span style="color: rgb(45, 194, 107);">**authorized\_keys**</span>:

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

**Пояснение**  
SSH-ключ будет в файле <span style="color: rgb(45, 194, 107);">**id\_rsa**</span>, в файле <span style="color: rgb(45, 194, 107);">**id\_rsa.pub**</span> будет связанным с ним публичный ключ. Файл <span style="color: rgb(45, 194, 107);">**authorized\_keys**</span> будет хранить в себе список из отпечатков (публичный ключей) для каждого приватного ключа, доступного для этого пользователя.

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

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

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

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

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

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

```bash
AllowUsers root
```

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

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

```bash
systemctl restart ssh
```

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

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

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

##### **Примечание**

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

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

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

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

<span style="color: rgb(255, 255, 255); background-color: rgb(186, 55, 42);"> **ВАЖНО!** </span>  
Эта команда удалит все содержимое с расширением <span style="color: rgb(45, 194, 107);">**\*.conf**</span> из директории <span style="color: rgb(45, 194, 107);">**/etc/ssh/sshd\_config.d**</span> !!!

# Разрешение нескольких 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. Скрипт универсальный и может использоваться для внесения изменений в файл <span style="color: rgb(22, 145, 121);">**termsrv.dll**</span> на всех версиях Windows 10 и Windows 11.

```powershell
# 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:

```powershell
Set-ExecutionPolicy Bypass -Scope Process -Force
```

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

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

Скрипт нужно запускать после установки обновлений Windows, чтобы немедленно внести изменения в файл <span style="color: rgb(22, 145, 121);">**termsrv.dll**</span> (защищает вас от ручной правки файла после каждой установки обновлений). Скрипт подходит для всех версий Windows 10 старше 1809 и для Windows 11.

Преимущество этого способа включения нескольких RDP сессий в Windows в том, что он работает путем замены файла части файла оригинальной системной библиотеки <span style="color: rgb(22, 145, 121);">**termsrv.dll**</span>. Как следствие, на этот подход не реагируют антивирусы.

  
<span style="background-color: rgb(186, 55, 42); color: rgb(255, 255, 255);"> **ВНИМАНИЕ!!! У ЭТОГО ПОДХОДА ЕСТЬ ОСОБЕННОСТЬ!** </span>  
При каждом обновлении Windows 10 (или при обновлении версии файла <span style="color: rgb(22, 145, 121);">**termsrv.dll**</span> в рамках других обновлений) придется снова запускать скрипт, т.к. оригинальная библиотека вернется в свое прежнее состояние (до патча)!

#### Ссылки

- Оригинальная статья: [https://web-profi.by/neskolko-odnovremennyx-rdp-sessij-polzovatelej-vwindows-10/](https://web-profi.by/neskolko-odnovremennyx-rdp-sessij-polzovatelej-vwindows-10/)
- Репозиторий проекта: [https://github.com/winadm/posh/blob/master/Desktop/RDP\_patch.ps1](https://github.com/winadm/posh/blob/master/Desktop/RDP_patch.ps1)

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

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

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

```bash
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/
```

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

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

Если много уже сжатого контента (видео, архивы), использование <span style="color: rgb(45, 194, 107);">**--compress**</span> может замедлить процесс. В таком случае, уберите этот параметр.

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


Чтобы копирование пережило обрыв SSH-сессии, используйте <span style="color: rgb(45, 194, 107);">**tmux**</span>:  
Установка:

```bash
apt install tmux -y
```

Запуск:

```bash
tmux
```

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

```bash
tmux ls
```

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

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

# Maintenance-мод для Nginx

### Зачем?

Краткий ответ - за тем, чтобы при "отвале" сервиса, клиенты видели не дефолтный <span style="color: rgb(45, 194, 107);">**HTTP/500 ERROR**</span>, а вашу разметку.

### Как?

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

```ini
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;  
    }  
    # Тут то, что было в блоке после ...   
}
```

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

```bash
nginx -t && nginx -s reload
```

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

Как только ты создашь файл <span style="color: rgb(45, 194, 107);">**/var/www/html/maintenance.flag**</span>, Nginx увидит это и переведет кластер в состояние обслуживание MAINTENANCE). Вместо сайта на фронтэнд-часть будет отправлен файл <span style="color: rgb(45, 194, 107);">**/var/www/html/maintenance.html**</span>.

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

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

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

```ini
# ------------------------------------------------------
# Добавился новый блок для директив 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;
    }
    # ------------------------------------------------------
    # Тут то, что было в блоке после ... 
}
```

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

```bash
nginx -t && nginx -s reload
```

---

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

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

```bash
touch /var/www/html/maintenance.flag
```

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

```bash
rm /var/www/html/maintenance.flag
```

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

---

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

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

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

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

```bash
timedatectl set-timezone Europe/Moscow
```

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

```bash
timedatectl list-timezones
```

# Установка WayDroid

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

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

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

И я нашел решение этой проблемы - [WayDroid](https://waydro.id/).

[![image.png](https://info.elijahkamsky.ru/uploads/images/gallery/2026-01/scaled-1680-/image.png)](https://info.elijahkamsky.ru/uploads/images/gallery/2026-01/image.png)

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

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

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

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

## Создание ВМ

В PVE:

1. Загружаем и импортируем образ Ubuntu Server 22.04 в PVE ([прямая ссылка на ISO-образ Ubuntu](https://releases.ubuntu.com/releases/22.04/ubuntu-22.04.5-live-server-amd64.iso))
2. Создаем ВМ с произвольным названием указываем наш ISO как загрузочный (установочный) образ
3. Перед ее запуском, сверяемся с параметрами: 
    1. Ресурсы (рекомендовано под 1 ВМ, можно и больше): 
        1. ЦПУ, ядер: <span style="color: rgb(45, 194, 107);"><span style="color: #2dc26b;">4 </span><span style="color: rgb(224, 62, 45);">(<span style="text-decoration: underline;">**важно**</span>: тип ЦПУ - "**<span style="color: rgb(45, 194, 107);">host</span>**"</span><span style="color: rgb(224, 62, 45);">)</span></span>
        2. ОЗУ, ГБ: <span style="color: rgb(45, 194, 107);">4</span>
        3. СХД, ГБ: <span style="color: rgb(45, 194, 107);">75 </span>(в моем случае, SSD)
    2. Machine: <span style="color: rgb(45, 194, 107);">q35</span>
    3. Display: <span style="color: rgb(45, 194, 107);">SPICE</span>
4. **(ВАЖНО!)** Устанавливаем сетевой адаптер - для установки нам понадобится интернет

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

[![image.png](https://info.elijahkamsky.ru/uploads/images/gallery/2026-01/scaled-1680-/AWPimage.png)](https://info.elijahkamsky.ru/uploads/images/gallery/2026-01/AWPimage.png)

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

1. Ubuntu наверняка вам предложит обновить установочный скрипт на более новую версию. В моменте, **лучше согласиться с обновлением**. Если вы не согласились - ничего страшного, на работу системы это не влияет;
2. На моменте разметки, выделите LVM больше места (чтобы убрать свободное место, в установщике оно помечено как "<span style="color: rgb(53, 152, 219);">free space</span>");
3. Мы установили Display типа <span style="color: rgb(45, 194, 107);">SPICE (qxl)</span>. Это сыграет в плане производительности. Но с нюансом...

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

[![image.png](https://info.elijahkamsky.ru/uploads/images/gallery/2026-01/scaled-1680-/oJFimage.png)](https://info.elijahkamsky.ru/uploads/images/gallery/2026-01/oJFimage.png)

В крайнем случае, вы можете временно изменить значение параметра на <span style="color: rgb(45, 194, 107);">default</span>.

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

## Установка WayDroid

#### Установка GUI

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

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

```bash
# Опционально: установка времени (в примере - Москва)
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 вещь: заставить ОС грузиться не в терминальную, а в **графическую оболочку**. Делается это двумя командами, включая перезагрузку.

```bash
sudo systemctl set-default graphical.target
reboot
```

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

<span style="background-color: rgb(186, 55, 42); color: rgb(236, 240, 241);"> **Важно!** </span>

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

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

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

Если вывод совпадает (<span style="color: rgb(45, 194, 107);">wayland</span>) - переходим к дальнейшей установке. **В противном случае - гуглим**.

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

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

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

После этого, у вас должна появиться команда "<span style="color: rgb(45, 194, 107);">waydroid</span>" в CLI.

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

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

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

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

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

[![image.png](https://info.elijahkamsky.ru/uploads/images/gallery/2026-01/scaled-1680-/DBIimage.png)](https://info.elijahkamsky.ru/uploads/images/gallery/2026-01/DBIimage.png)

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

```bash
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
```

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

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

<span style="background-color: rgb(186, 55, 42); color: rgb(255, 255, 255);"><span style="background-color: rgb(186, 55, 42); color: rgb(255, 255, 255);"><span style="background-color: rgb(186, 55, 42); color: rgb(255, 255, 255);"><span style="background-color: rgb(186, 55, 42); color: rgb(255, 255, 255);"> </span></span> </span><span style="background-color: rgb(186, 55, 42); color: rgb(255, 255, 255);"> </span>Если здесь будут использованы команды из CLI - **они выполняются в GUI-сессии**, а не через SSH! <span style="background-color: rgb(186, 55, 42); color: rgb(255, 255, 255);"> </span></span>

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

```bash
waydroid session start
waydroid show-full-ui
```

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

<p class="callout info">Главный экран</p>

[![image.png](https://info.elijahkamsky.ru/uploads/images/gallery/2026-01/scaled-1680-/6nUimage.png)](https://info.elijahkamsky.ru/uploads/images/gallery/2026-01/6nUimage.png)

<p class="callout info">Браузер</p>

[![image.png](https://info.elijahkamsky.ru/uploads/images/gallery/2026-01/scaled-1680-/z1Wimage.png)](https://info.elijahkamsky.ru/uploads/images/gallery/2026-01/z1Wimage.png)

<p class="callout info">Сайд-бар (доступен по клику сверху)</p>

[![image.png](https://info.elijahkamsky.ru/uploads/images/gallery/2026-01/scaled-1680-/Im3image.png)](https://info.elijahkamsky.ru/uploads/images/gallery/2026-01/Im3image.png)

<p class="callout info">Настройки</p>

[![image.png](https://info.elijahkamsky.ru/uploads/images/gallery/2026-01/scaled-1680-/ilRimage.png)](https://info.elijahkamsky.ru/uploads/images/gallery/2026-01/ilRimage.png)

<p class="callout info">Галерея</p>

[![image.png](https://info.elijahkamsky.ru/uploads/images/gallery/2026-01/scaled-1680-/9bEimage.png)](https://info.elijahkamsky.ru/uploads/images/gallery/2026-01/9bEimage.png)

# RDP на Linux (GNOME Remote Desktop)

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

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

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

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**

```bash
apt update
apt install -y gnome-remote-desktop
```

<p class="callout danger">Все дальнейшие шаги будут выполняться от пользователя **user1** (<span style="text-decoration: underline;">не от root!</span>) и **в GUI** (<span style="text-decoration: underline;">не в терминале!</span>)  
</p>

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

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

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

Для создания пользователя, выполняем команду от имени **того, кто будет заходить на сервер** (<span style="text-decoration: underline;">не root!</span>). Например, для <span style="color: rgb(45, 194, 107);">user1</span>:

```bash
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. Разрешаем подключение**

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

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

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

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

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

```yaml
● 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 слушаться не будет, сколько бы вы не перезагружали службу.

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

```python
RDP TLS certificate and key not configured properly
```

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

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

1\. Генерируем самоподписанный сертификат в <span style="color: rgb(45, 194, 107);">~/.local/share/grd</span>:

```bash
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
```

<p class="callout warning">Важно:  
1. Ключ должен быть без пароля (unencrypted PEM);  
2. Файлы должны быть **читаемы пользователем, который будет подключаться** (иначе демон их не прочтет).</p>

<p class="callout info"><span style="color: rgb(255, 255, 255);">Если у вас есть **свой** TLS-сертификат, просто подложите его в директорию:</span>  
<span style="color: rgb(255, 255, 255);">1. **Сертификат:**</span> <span style="color: rgb(45, 194, 107);">~/.local/share/grd/rdp.crt</span>  
<span style="color: rgb(255, 255, 255);">2. **Приватный ключ:**</span> <span style="color: rgb(45, 194, 107);">~/.local/share/grd/rdp.key  
<span style="color: rgb(255, 255, 255);">Или измените команду ниже</span>  
</span></p>

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

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

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

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

<p class="callout warning">**ВАЖНОЕ ПРАВИЛО!**  
ВСЕ команды типа <span style="color: rgb(255, 255, 255);">grdctl</span> и <span style="color: rgb(255, 255, 255);">systemctl --user</span> выполняются **<span style="text-decoration: underline;">ТОЛЬКО</span>** от того пользователя, кому они предназначены (<span style="text-decoration: underline;">без sudo</span>). В нашем случае, это <span style="color: rgb(255, 255, 255);">user1</span></p>

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

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

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

1. Введите email
2. Выберите "Дополнительные параметры"
3. Далее, “Настроить вручную”. Там выберите <span style="color: rgb(45, 194, 107);">IMAP</span>

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

**Входящая (IMAP)**

1. Сервер: <span style="color: rgb(45, 194, 107);">mail.elijahkamsky.ru</span>
2. Порт: 993
3. Шифрование: SSL/TLS
4. Метод входа: Обычный пароль
5. Имя пользователя: полный email (<span style="color: rgb(45, 194, 107);">\*\*\*\*\*@elijahkamsky.ru</span>)
6. Исходящая (SMTP)

Сервер: <span style="color: rgb(53, 152, 219);">**mail.elijahkamsky.ru**</span>

1. Порт: 587
2. Шифрование: STARTTLS
3. Требуется проверка подлинности: Да
4. Имя пользователя/пароль: <span style="text-decoration: underline;">те же</span>

<p class="callout warning">В дополнительных настройках Outlook проверьте, чтобы SPA (Secure Password Authentication) было **выключено**. Outlook иногда пытается его включить.</p>

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

[![image.png](https://info.elijahkamsky.ru/uploads/images/gallery/2026-01/scaled-1680-/ky1image.png)](https://info.elijahkamsky.ru/uploads/images/gallery/2026-01/ky1image.png)

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

[![image.png](https://info.elijahkamsky.ru/uploads/images/gallery/2026-01/scaled-1680-/TXfimage.png)](https://info.elijahkamsky.ru/uploads/images/gallery/2026-01/TXfimage.png)

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

# Предисловие

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

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

1. Через файл <span style="color: rgb(45, 194, 107);">rc.local</span> - устаревший подход, да и правка системных конфигов, даже таких, всегда чревата;
2. <span style="color: rgb(45, 194, 107);">@reboot в cron</span> - это явный костыль, хотя многие со мной не согласятся;
3. Скрипты в духе "<span style="color: rgb(45, 194, 107);">sleep 60 &amp;&amp; ...</span>" - это зло. Они не дают контроля и работают буквально "на шару". Слишком много ответственности для простого bash-скрипта.

<p class="callout info">**ИМХО автора**  
Для серверов единственно правильный путь решения этой проблемы сейчас - <span style="color: rgb(45, 194, 107);">**systemd unit**</span>  
</p>

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

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

1. Дает значительно больше контроля над сервисом: его остановка, внеплановый запуск или сбор информации о его работе становятся значительно проще для администратора - для этого будут использоваться стандартные утилиты, к которым все привыкли (<span style="color: rgb(45, 194, 107);">systemctl</span> и <span style="color: rgb(45, 194, 107);">journalctl</span>);
2. Разделяет ответственность: логика скрипта находится отдельно от логики, которая ответственна за его запуск;
3. Позволяет менять логику скрипта отдельно, не меняя любой внешней логики (атомарность в подходе).

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

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

Я редактирую файл юнита. Допустим, он будет называться <span style="color: rgb(45, 194, 107);">post-reboot-tasks</span>, тогда:

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

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

```ini
[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 это делается не отдельной строкой в духе "<span style="color: rgb(45, 194, 107);">AfterReboot=true</span>", а самим фактом, что юнит включён в target, который достигается при загрузке. В этом примере вот эта строчка и говорит, "запускать при старте системы":

```ini
WantedBy=multi-user.target
```

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

```bash
systemctl enable post-reboot-tasks
```

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

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

А <span style="color: rgb(45, 194, 107);">multi-user.target</span> - это стандартная "нормальная загрузка **сервера без GUI**" (условно, <span style="color: rgb(45, 194, 107);">runlevel=3</span>). Каждый раз, когда система доходит до этого Target'а (то есть после ребута/бут-цикла), SystemD подхватит юнит и запустит его.

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

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

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

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

[![image.png](https://info.elijahkamsky.ru/uploads/images/gallery/2026-01/scaled-1680-/AZDimage.png)](https://info.elijahkamsky.ru/uploads/images/gallery/2026-01/AZDimage.png)

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

```bash
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
```

<p class="callout success">Теперь, когда создан скрипт и юнит-файл попал в реестр SystemD, он будет запускаться при каждой перезагрузке</p>

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

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

[![image.png](https://info.elijahkamsky.ru/uploads/images/gallery/2026-01/scaled-1680-/aX6image.png)](https://info.elijahkamsky.ru/uploads/images/gallery/2026-01/aX6image.png)

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

##### <span style="color: rgb(255, 255, 255); background-color: rgb(224, 62, 45);"> **Важное уточнение, которое я вынесу отдельно**<span style="color: rgb(255, 255, 255); background-color: rgb(224, 62, 45);"> </span><span style="color: rgb(255, 255, 255); background-color: rgb(224, 62, 45);"> </span><span style="color: rgb(255, 255, 255); background-color: rgb(224, 62, 45);"> </span><span style="color: rgb(255, 255, 255); background-color: rgb(224, 62, 45);"> </span><span style="color: rgb(255, 255, 255); background-color: rgb(224, 62, 45);"> </span></span>

Скрипт будет запускаться при загрузке системы только в том случае, если его статус - <span style="color: rgb(45, 194, 107);">enabled</span>. **В противном случае скрипт не попадет в автозагрузку**. Проверить это можно командой:

```bash
systemctl status post-reboot-tasks
```

[![image.png](https://info.elijahkamsky.ru/uploads/images/gallery/2026-01/scaled-1680-/7p5image.png)](https://info.elijahkamsky.ru/uploads/images/gallery/2026-01/7p5image.png)

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

### Генерация

- С небольшой датой (501 файл):

```bash
for i in {1..501}; do echo $(date +%T_%s) > "${i}.txt" ; echo "$i"; done
# Sample content:
# 12:22:07_1769505327
```

- С произвольной неповторяющейся датой (501 файл):

```bash
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
```

- С уникальными именами и данными (501 файл):

```bash
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](https://info.elijahkamsky.ru/uploads/images/gallery/2026-01/scaled-1680-/mo9image.png)](https://info.elijahkamsky.ru/uploads/images/gallery/2026-01/mo9image.png)

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

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

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

[![image.png](https://info.elijahkamsky.ru/uploads/images/gallery/2026-01/scaled-1680-/7K6image.png)](https://info.elijahkamsky.ru/uploads/images/gallery/2026-01/7K6image.png)

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

```bash
#!/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](https://info.elijahkamsky.ru/uploads/images/gallery/2026-01/scaled-1680-/TW3image.png)](https://info.elijahkamsky.ru/uploads/images/gallery/2026-01/TW3image.png)

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

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

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

```bash
ls -1 | wc -l
```

Но с этой командой есть нюанс: она считает вообще все записи, <span style="text-decoration: underline;">включая директории</span>. Чтобы посчитать только файлы с расширением <span style="color: rgb(45, 194, 107);">**.txt**</span> можно использовать **GREP**:

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

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

[![image.png](https://info.elijahkamsky.ru/uploads/images/gallery/2026-01/scaled-1680-/wMMimage.png)](https://info.elijahkamsky.ru/uploads/images/gallery/2026-01/wMMimage.png)

# RU locale setup on Linux (debian-based only)

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

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

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

### Новая версия

```bash
#!/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](https://info.elijahkamsky.ru/uploads/images/gallery/2026-01/scaled-1680-/qllimage.png)](https://info.elijahkamsky.ru/uploads/images/gallery/2026-01/qllimage.png)

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

[![image.png](https://info.elijahkamsky.ru/uploads/images/gallery/2026-01/scaled-1680-/bDLimage.png)](https://info.elijahkamsky.ru/uploads/images/gallery/2026-01/bDLimage.png)

### <span style="color: rgb(255, 255, 255); background-color: rgb(186, 55, 42);"> </span><span style="color: rgb(255, 255, 255); background-color: rgb(186, 55, 42);">Старая версия </span>

```bash
#!/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))"
```

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

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

```bash
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](https://info.elijahkamsky.ru/uploads/images/gallery/2026-01/scaled-1680-/AE9image.png)](https://info.elijahkamsky.ru/uploads/images/gallery/2026-01/AE9image.png)

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

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

1. Откройте PowerShell <span style="text-decoration: underline;"><span style="color: rgb(230, 126, 35); text-decoration: underline;">от имени администратора</span></span>;
2. Выполните команду:

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

- В открывшемся окне выберите какой продукт вы ходите активировать: Windows или MS Office.
- Выберете тип активации продукта. **TIP:** наиболее стабильный тип активации - через KMS.
- Следуйте инструкциям далее. Не прерывайте скрипт - дайте ему закончить свою работу. Среднее время активации не превышает 1 минуты

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

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

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

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

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

На примере домена <span style="color: rgb(45, 194, 107);">elijahkamsky.ru</span>:

- В CLI на любом хосте, где установлен [<span style="color: rgb(45, 194, 107);">CertBot</span>](https://github.com/certbot/certbot):

```bash
certbot certonly \
  --manual \
  --preferred-challenges dns \
  -d elijahkamsky.ru \
  -d *.elijahkamsky.ru
```

- Certbot скажет создать TXT-запись:

```bash
_acme-challenge.elijahkamsky.ru
```

Например:

```bash
_acme-challenge.elijahkamsky.ru TXT qwe123asdasd
```

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

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

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

```bash
/etc/letsencrypt/live/elijahkamsky.ru/
```

Там:

```bash
cert.pem
chain.pem
fullchain.pem
privkey.pem
```

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

```bash
fullchain.pem
privkey.pem
```

#### <span style="color: rgb(255, 255, 255); background-color: rgb(224, 62, 45);"><span style="color: rgb(255, 255, 255); background-color: rgb(224, 62, 45);"> </span><span style="color: rgb(255, 255, 255); background-color: rgb(224, 62, 45);"> </span> </span><span style="color: rgb(255, 255, 255); background-color: rgb(224, 62, 45);">**Обратите внимание!**<span style="color: rgb(255, 255, 255); background-color: rgb(224, 62, 45);"> </span><span style="color: rgb(255, 255, 255); background-color: rgb(224, 62, 45);"> </span> </span>

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

<span style="text-decoration: underline; color: rgb(224, 62, 45);">Полное обновление DNS-записей может занимать вплоть до 48 часов</span>.

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

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

*Тип записи: **TXT***

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

[![image.png](https://info.elijahkamsky.ru/uploads/images/gallery/2026-03/scaled-1680-/RC0image.png)](https://info.elijahkamsky.ru/uploads/images/gallery/2026-03/RC0image.png)

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

### "Мне прям по-быстрому для Windows 10 PRO"

В Poweshell от имени Администратора:

```bash
slmgr /ipk W269N-WFGWX-YVC9B-4J6C9-T83GX
slmgr /skms kms8.msguides.com
slmgr /ato
slmgr /xpr
```

### Я хочу почитать

Чтобы активировать Windows 10 существует 2 метода:

1. Использовать скрипт автоматической активации

В Powershell от имени администратора вставляем: 

```bash
irm https://get.activated.win | iex
```

Дальше скрипт все сделает за нас. Ориентируйтесь на меню скрипта и встроенные подсказки для работы с ним

2. Активация через службу KMS (вручную)

- Открываем Powershell от имени администратора
- Устанавливаем сервер для верификации ключа KMS:

```bash
slmgr /skms kms.lotro.cc
```

- Устанавливаем ключ продукта:

```bash
slmgr /ipk "SERIAL NUMBER HERE"
```

Замените `SERIAL NUMBER HERE` в команде на 1 из доступных ключей в зависимости от редакции Windows:

```bash
# 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`

- Принудительно проверяем установленный ключ на KMS-сервере

```bash
slmgr /ato
```

Если сервер ответил положительно и ваша ревизия Windows совпадает с той, к которой вы указали ключ, то вы увидите окно с подтверждением активации Windows:

[![](https://info.elijahkamsky.ru/uploads/images/gallery/2026-05/scaled-1680-/image-1779652142019.png)](https://info.elijahkamsky.ru/uploads/images/gallery/2026-05/image-1779652142019.png)
---

**[Ссылка на репозиторий](https://github.com/sury4karmakar/Windows10-ActivationKey/blob/main/README.md)**

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

Этот мануал описывает, как корректно настроить **отображение русских символов (UTF-8)** в Linux-системе: консоль, SSH, приложения, systemd, Docker и Ansible.

---

### 🔍 Как понять, есть ли проблема

Типичные симптомы:
- вместо кириллицы — `????` или кракозябры
- `locale` выводит `C` или `POSIX`
- команды с русскими именами файлов работают некорректно
- в логах systemd или приложений битая кодировка

Проверка текущего состояния:

```bash
locale
```

❌ Плохо:
```
LANG=C
LC_ALL=
```

✅ Хорошо:
```
LANG=ru_RU.UTF-8
LC_ALL=
```

---

### 📦 Установка необходимых локалей

#### Debian / Ubuntu

```bash
apt update
apt install -y locales
```

#### RHEL / Rocky / Alma / CentOS Stream

```bash
dnf install -y glibc-langpack-ru
```

#### Arch Linux

```bash
pacman -S glibc
```

---

### 🧩 Генерация локали ru_RU.UTF-8

#### Debian / Ubuntu

```bash
nano /etc/locale.gen
```
Раскомментировать:
```
ru_RU.UTF-8 UTF-8
```

Сгенерировать:
```bash
locale-gen
```

---

### 🌐 Установка локали по умолчанию

```bash
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_*
```

```bash
systemctl restart sshd
```

---

### 🧰 systemd

В unit-файле:
```ini
[Service]
Environment="LANG=ru_RU.UTF-8"
Environment="LC_ALL=ru_RU.UTF-8"
```

```bash
systemctl daemon-reexec
```

---

### 🐳 Docker

```dockerfile
ENV LANG=ru_RU.UTF-8
ENV LC_ALL=ru_RU.UTF-8
```

---

### 🛠 Ansible

```yaml
ansible_env:
  LANG: ru_RU.UTF-8
  LC_ALL: ru_RU.UTF-8
```

---

### 🧪 Проверка

```bash
echo "Привет, мир"
touch тест.txt
ls
```

---

## ✅ Итог

Если локаль сгенерирована и `LANG=ru_RU.UTF-8` задан глобально — кириллица будет работать везде.