Integrate Server Monitor installer and real E2E tests #17

Open
ochenstarik-ui wants to merge 23 commits from codex/monitor-installer-e2e into main
25 changed files with 2790 additions and 350 deletions
Showing only changes of commit 4016d0e7e7 - Show all commits

45
.github/workflows/bash-syntax.yml vendored Normal file
View file

@ -0,0 +1,45 @@
name: Bash syntax
on:
push:
branches: ['**']
pull_request:
permissions:
contents: read
jobs:
syntax:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Check every installer
run: |
set -e
while IFS= read -r -d '' script; do
echo "Checking ${script}"
bash -n "${script}"
done < <(find . -type f -name '*.sh' -print0)
- name: Test step 1 selections
run: bash tests/test-step1-selection.sh
- name: Test step 2 IPv4 firewall mode
run: bash tests/test-step2-ipv4-firewall.sh
- name: Test master language selection
run: bash tests/test-master-language.sh
- name: Test README languages and monitoring links
run: bash tests/test-readme-languages.sh
- name: Test step 3 SSH configuration parsing
run: bash tests/test-step3-sshd-parsing.sh
- name: Test step 5 repeated-run actions
run: bash tests/test-step5-actions.sh
- name: Test step 8 AI agent catalog
run: bash tests/test-step8-agent-catalog.sh

431
README.md
View file

@ -1,377 +1,202 @@
# Быстрая настройка Ubuntu Server
# Lightweight Ubuntu Server Setup
Набор независимых Bash-скриптов для первоначальной настройки Ubuntu Server. Можно запустить только нужные модули или выполнить их по порядку для полной настройки.
**English** | [Русский](docs/readme/README.ru.md) | [Español](docs/readme/README.es.md) | [Deutsch](docs/readme/README.de.md) | [Français](docs/readme/README.fr.md) | [Português](docs/readme/README.pt.md) | [中文](docs/readme/README.zh-CN.md) | [日本語](docs/readme/README.ja.md) | [العربية](docs/readme/README.ar.md) | [हिन्दी](docs/readme/README.hi.md)
## Что входит в проект
A modular set of Bash installers for the initial setup, security hardening, administration, VPN, web panel, WARP, and backups on Ubuntu Server. Run the unified wizard for a guided installation or execute only the modules you need.
| Файл | Назначение | Можно запускать отдельно |
| --- | --- | --- |
| `ochenstarik-server-1.sh` | Предлагает выбрать часовой пояс, создаёт swap-файл 2 ГБ и задаёт `vm.swappiness=20` | Да |
| `ochenstarik-server-2.sh` | Предлагает выбрать будущий SSH-порт и режим IPv4/IPv6, обновляет систему, ставит пакеты и настраивает UFW | Да |
| `ochenstarik-server-user-3.sh` | Создаёт администратора, переносит SSH на порт из этапа 2, проверяет права и включает fail2ban | После установки необходимых пакетов; проще всего сначала запустить скрипт 2 |
| `ochenstarik-server-tg-4.sh` | Отправляет уведомления в Telegram при успешном входе по SSH | После установки OpenSSH и `curl`; обычно после скрипта 2 или 3 |
| `ochenstarik-server-vpn-5.sh` | Устанавливает Xray и направляет системный трафик через VLESS + REALITY | Да; зависимости устанавливаются автоматически |
| `ochenstarik-server-panel-warp-6.sh` | Устанавливает 3x-ui и Cloudflare WARP, настраивает и проверяет их порты | Ubuntu 22.04/24.04/26.04 или Debian 12/13 |
| `ochenstarik-server-backup-7.sh` | Создаёт неизменяемый первичный снимок и настраивает выбранные расписания резервного копирования | Да; зависимости устанавливаются автоматически |
| `ochenstarik-server-monitor-manager.sh` | Устанавливает главный WireGuard Hub или подключаемый узел Server Monitor Manager | Да; Hub нужен белый IP, вторичному узлу — только исходящий доступ |
> **Manage multiple servers:** use the companion [Server Monitor Manager](https://github.com/ochenstarik-ui/server-monitor-manager) project to add servers, collect reports, and manage several hosts from one application. It is currently in early development and should not yet be treated as production-ready.
Файлы `*.env.example` являются только примерами. Не записывайте настоящие пароли, токены и приватные SSH-ключи в Git.
## Quick installation
## Требования
- Ubuntu Server или совместимая Debian-система с `systemd`;
- доступ `root` или пользователь с `sudo`;
- подключение к интернету;
- рекомендуется сохранить текущую SSH-сессию открытой до проверки нового входа.
## Получение проекта
Рекомендуемый способ — загрузка архива через `codeload.github.com`. Он подходит в том числе для серверов, на которых соединение с основным доменом `github.com` завершается ошибкой `SSL connection timeout`:
Copy the complete block into the server terminal. It downloads the repository archive through `codeload.github.com`, stops on errors, and starts the unified installer:
```bash
set -e
cd "$HOME"
sudo apt-get update
sudo apt-get install -y curl ca-certificates tar
archive="$(mktemp)"
trap 'rm -f "$archive"' EXIT
curl -4 -fL \
--retry 5 \
--retry-delay 10 \
--connect-timeout 30 \
https://codeload.github.com/ochenstarik-ui/lightweight-server/tar.gz/refs/heads/main \
-o lightweight-server.tar.gz
mkdir lightweight-server
tar -xzf lightweight-server.tar.gz -C lightweight-server --strip-components=1
rm lightweight-server.tar.gz
-o "$archive"
install -d -m 700 lightweight-server
tar -xzf "$archive" -C lightweight-server --strip-components=1
rm -f "$archive"
trap - EXIT
cd lightweight-server
chmod 700 ./*.sh
sudo ./ochenstarik-server-install.sh
```
Если `github.com` доступен с сервера, проект также можно получить через Git:
The first wizard prompt selects the dialog language. English is the default; Russian, Spanish, German, French, Portuguese, Simplified Chinese, Japanese, Arabic, and Hindi are also available. The wizard then presents steps 18 in order. Each step can be installed, skipped, or used to exit the wizard.
Keep the current SSH session open while changing the SSH port. Test the new login in a second terminal before disconnecting.
## Short wizard-only installation
If `raw.githubusercontent.com` is reachable from the server:
```bash
set -e
cd "$HOME"
sudo apt-get update
sudo apt-get install -y git ca-certificates
git -c http.version=HTTP/1.1 clone --depth 1 https://github.com/ochenstarik-ui/lightweight-server.git
cd lightweight-server
chmod 700 ./*.sh
sudo apt-get install -y curl ca-certificates
curl -4 -fLO \
--retry 5 \
--retry-delay 10 \
--connect-timeout 30 \
https://raw.githubusercontent.com/ochenstarik-ui/lightweight-server/main/ochenstarik-server-install.sh
chmod 700 ochenstarik-server-install.sh
sudo ./ochenstarik-server-install.sh
```
Команда `set -e` останавливает последовательность при ошибке загрузки, поэтому команды `cd` и `chmod` не будут выполняться для отсутствующей папки.
The wizard downloads any missing module from the `main` branch and validates it with `bash -n` before execution.
Перед запуском любого выбранного файла можно проверить его синтаксис:
## Modules
| Script | Purpose | Standalone use |
| --- | --- | --- |
| `ochenstarik-server-install.sh` | Unified wizard with language selection and sequential module installation | Recommended |
| `ochenstarik-server-1.sh` | Timezone, terminal locale, program groups, and a 2 GiB swap file | Yes |
| `ochenstarik-server-2.sh` | Base packages, SSH port, IPv4/IPv6 mode, UFW, and port management | Yes |
| `ochenstarik-server-user-3.sh` | Administrative user, SSH key permissions, SSH migration, sudo, and fail2ban | Run after step 2 |
| `ochenstarik-server-tg-4.sh` | Telegram notifications for successful SSH logins | Optional |
| `ochenstarik-server-vpn-5.sh` | System VPN through Xray; repeated runs can connect, disconnect, reconfigure, or show status | Optional |
| `ochenstarik-server-panel-warp-6.sh` | 3x-ui panel and local Cloudflare WARP proxy | Optional |
| `ochenstarik-server-backup-7.sh` | Protected initial snapshot and selected daily, weekly, or monthly schedules | Optional |
| `ochenstarik-server-ai-agents-8.sh` | Installs selected AI agents: Hermes, OpenClaw, OpenHands, OpenCode, Aider, AutoGPT, or Pi Coding Agent | Optional |
| `ochenstarik-server-monitor-manager.sh` | Installs the Server Monitor Manager SSH endpoint, public WireGuard Hub, or outbound-only Node | Optional; Hub needs a public UDP endpoint |
| `ochenstarik-server-uninstall.sh` | Removes project-managed settings so installation can start again | Use carefully |
Every module can be downloaded and run independently:
```bash
bash -n ./ИМЯ-СКРИПТА.sh
curl -fLO https://raw.githubusercontent.com/ochenstarik-ui/lightweight-server/main/SCRIPT_NAME.sh
chmod 700 SCRIPT_NAME.sh
bash -n SCRIPT_NAME.sh
sudo ./SCRIPT_NAME.sh
```
## Server Monitor Manager: своя сеть без Tailscale
Replace `SCRIPT_NAME.sh` with the required filename from the table.
Файл `ochenstarik-server-monitor-manager.sh` устанавливает лёгкую сеть WireGuard со звездной топологией. Главный сервер с белым IP становится Hub, а домашний и другие серверы подключаются к нему исходящими соединениями. Белый IP на вторичных узлах не нужен.
## Server Monitor Manager Hub and Nodes
На главном сервере откройте входящий UDP-порт WireGuard (по умолчанию `51820`) в панели хостинга и запустите:
The Hub needs a public IPv4 address or domain and an open UDP port (default `51820`). Secondary Nodes establish outbound WireGuard connections and do not need a public IP.
```bash
curl -fLO https://raw.githubusercontent.com/ochenstarik-ui/lightweight-server/agent/server-monitor-installer/ochenstarik-server-monitor-manager.sh
curl -fLO https://raw.githubusercontent.com/ochenstarik-ui/lightweight-server/main/ochenstarik-server-monitor-manager.sh
chmod 700 ochenstarik-server-monitor-manager.sh
bash -n ochenstarik-server-monitor-manager.sh
sudo ./ochenstarik-server-monitor-manager.sh hub
```
После установки создайте отдельный секретный код конфигурации для каждого узла:
Create a separate configuration code for each Node on the Hub, then run the same installer with `node` on the matching server:
```bash
sudo ochenstarik-smm node-code ai-agent
sudo ochenstarik-smm node-code home
sudo ochenstarik-smm node-code server2
```
На соответствующем вторичном сервере скачайте тот же файл и выполните:
```bash
sudo ./ochenstarik-server-monitor-manager.sh node
```
Установщик попросит публичный SSH-ключ приложения и код, созданный для этого узла. Код содержит приватный ключ WireGuard узла: передавайте его только в защищённый терминал, используйте для одного сервера и создайте новый узел, если код стал известен посторонним.
Связи направленные и управляются на Hub. Например, разрешить AI-агенту доступ к домашнему серверу и ко второму серверу, а затем отключить только второй маршрут:
Links are directional. The reverse direction requires its own rule:
```bash
sudo ochenstarik-smm link-connect ai-agent home
sudo ochenstarik-smm link-connect ai-agent server2
sudo ochenstarik-smm link-disconnect ai-agent server2
```
Проверка состояния:
```bash
sudo ochenstarik-smm link-disconnect ai-agent home
sudo ochenstarik-smm nodes
sudo ochenstarik-smm links
sudo ochenstarik-smm status
```
Hub маршрутизирует только явно разрешённые пары `источник → цель`; обратное направление включается отдельно. Вторичные серверы не соединяются друг с другом напрямую и не требуют Tailscale или другого внешнего сервиса.
The current configuration code contains the Node private WireGuard key and is intended only for early testing. Do not store it in Git or messages. Local Node key generation and expiring one-time enrollment are tracked before production use.
## 1. Часовой пояс и настройка swap
## Installation flow
Файл: `ochenstarik-server-1.sh`.
1. Select the wizard dialog language.
2. Configure the timezone, terminal locale, recommended tools, and swap.
3. Select IPv4, IPv6, or dual-stack operation; install base services and select the future SSH port.
4. Create or update the administrative user, install the public SSH key, repair ownership and permissions, and move SSH to the port selected in step 2.
5. Optionally configure Telegram SSH-login notifications.
6. Optionally configure the system VPN and subscription port.
7. Optionally deploy 3x-ui and Cloudflare WARP with selected panel and subscription ports.
8. Optionally create the permanent initial backup and configure rotating schedules.
9. Optionally install one or more AI agents for the administrative user.
Сначала показывает нумерованное меню популярных часовых поясов, включая `Asia/Novosibirsk`, `Europe/Moscow` и `UTC`. Нужную зону достаточно выбрать по номеру. Для остальных зон предусмотрен полный список с последовательным выбором региона и часового пояса — вводить имя вручную не требуется. Затем скрипт создаёт `/swapfile` размером 2 ГБ, добавляет его в `/etc/fstab` и устанавливает `vm.swappiness=20`. Повторный запуск предусмотрен.
```bash
curl -fLO https://raw.githubusercontent.com/ochenstarik-ui/lightweight-server/main/ochenstarik-server-1.sh
chmod 700 ochenstarik-server-1.sh
bash -n ochenstarik-server-1.sh
sudo ./ochenstarik-server-1.sh
```
Проверка:
```bash
swapon --show
sysctl vm.swappiness
```
## 2. Базовые пакеты, SSH-порт и брандмауэр
Файл: `ochenstarik-server-2.sh`.
При каждом запуске скрипт предлагает три действия: полную установку или обновление, изменение режима IP без переустановки пакетов либо добавление открытых портов. При полной установке он запрашивает SSH-порт для этапа 3, обновляет систему и устанавливает OpenSSH, UFW, fail2ban, инструменты обработки документов, изображений, аудио и видео.
Для входящих подключений можно выбрать:
- только IPv4 — IPv6 отключается в UFW и системно через `sysctl`; этот режим рекомендуется, если 3x-ui используется только по IPv4;
- только IPv6 — управляемые скриптом порты открываются лишь для IPv6;
- IPv4 + IPv6 — порты открываются для обоих семейств.
Выбор сохраняется в `/etc/ochenstarik-server/ip-family.conf`, а список управляемых портов — в `/etc/ochenstarik-server/ufw-managed-ports.conf`. При переходе на режим с одним семейством старые правила этих портов пересоздаются. Скрипт не разрешит отключить семейство адресов, через которое работает текущая SSH-сессия. Для режима IPv6 также проверяются глобальный IPv6-адрес и маршрут по умолчанию.
В режиме «только IPv6» системный IPv4 остаётся доступен для исходящих соединений и работы менеджера пакетов, но входящие управляемые порты по IPv4 закрываются UFW. Полное переносимое отключение IPv4 в Linux без привязки к конкретной сетевой конфигурации не выполняется.
Открываются TCP-порты:
- `22` — временный SSH-порт;
- выбранный SSH-порт — будущий порт SSH;
- `80`, `443` — HTTP и HTTPS;
- `2096`, `40000`, `63636` — порты приложений.
```bash
curl -fLO https://raw.githubusercontent.com/ochenstarik-ui/lightweight-server/main/ochenstarik-server-2.sh
chmod 700 ochenstarik-server-2.sh
bash -n ochenstarik-server-2.sh
sudo ./ochenstarik-server-2.sh
```
Проверка:
```bash
sudo ufw status verbose
cat /etc/ochenstarik-server/ip-family.conf
cat /proc/sys/net/ipv6/conf/all/disable_ipv6
timedatectl
```
## 3. Администратор и защита SSH
Файл: `ochenstarik-server-user-3.sh`.
При каждом запуске скрипт предлагает два режима:
1. новая установка или повторная настройка администратора и SSH;
2. только добавление открытых портов в UFW без изменения пользователя и SSH.
В режиме новой установки скрипт интерактивно запрашивает имя администратора, SSH-ключ и пароль. SSH-порт автоматически читается из защищённого файла, созданного этапом 2. Если этап 2 не выполнялся, скрипт предложит выбрать порт вручную. Затем он:
- создаёт или обновляет пользователя;
- добавляет пользователя в группу `sudo`;
- назначает пользователю его домашнюю папку и всё содержимое `.ssh`;
- устанавливает права `700` для `.ssh` и `600` для `authorized_keys` и остальных файлов ключей;
- запрещает вход `root` по SSH;
- переносит SSH на выбранный порт и добавляет для него правило UFW;
- настраивает fail2ban;
- по выбору управляет правилами UFW.
Правила UFW добавляются с учётом режима IPv4/IPv6, сохранённого скриптом 2. Новые порты также записываются в общий список управляемых правил, поэтому последующее переключение IP-режима пересоздаст их корректно.
Для отдельного запуска сначала установите зависимости:
```bash
sudo apt-get update
sudo apt-get install -y sudo ufw fail2ban openssl openssh-server
curl -fLO https://raw.githubusercontent.com/ochenstarik-ui/lightweight-server/main/ochenstarik-server-user-3.sh
chmod 700 ochenstarik-server-user-3.sh
bash -n ochenstarik-server-user-3.sh
sudo ./ochenstarik-server-user-3.sh
```
Не закрывая текущую сессию, откройте второй терминал и проверьте вход:
```bash
ssh -p ВЫБРАННЫЙ_ПОРТ ИМЯ_ПОЛЬЗОВАТЕЛЯ@IP_СЕРВЕРА
```
Только после успешной проверки можно удалить временное правило порта 22:
```bash
sudo ufw delete allow 22/tcp
```
Чтобы позднее открыть дополнительные порты, повторно запустите скрипт, выберите пункт `2` и введите правила через пробел или запятую:
```text
80 443/tcp 53/udp 40000
```
Для записей без протокола используется TCP. Допустимы только порты от `1` до `65535`. Если UFW неактивен, правила будут сохранены, но скрипт не включит брандмауэр автоматически во избежание потери SSH-доступа.
## 4. Telegram-уведомления о входах по SSH
Файл: `ochenstarik-server-tg-4.sh`.
Создайте бота через `@BotFather`, отправьте ему сообщение и узнайте ID чата. Скрипт запросит токен и ID интерактивно, отправит тестовое сообщение и подключит уведомления через PAM.
Для отдельного запуска:
```bash
sudo apt-get update
sudo apt-get install -y curl openssh-server logrotate
curl -fLO https://raw.githubusercontent.com/ochenstarik-ui/lightweight-server/main/ochenstarik-server-tg-4.sh
chmod 700 ochenstarik-server-tg-4.sh
bash -n ochenstarik-server-tg-4.sh
sudo ./ochenstarik-server-tg-4.sh
```
Журнал уведомлений:
```bash
sudo tail -f /var/log/ochenstarik-ssh-login-telegram.log
```
Токен сохраняется на сервере в `/etc/ochenstarik-server/telegram.conf` с ограниченными правами. Не добавляйте этот файл в репозиторий.
## 5. Системный VPN через Xray
Файл: `ochenstarik-server-vpn-5.sh`.
Скрипт устанавливает Xray и необходимые пакеты, принимает прямую ссылку `vless://` либо HTTPS-ссылку подписки 3x-ui и настраивает маршрутизацию через `nftables`. Если подписка содержит VLESS-ссылки на нескольких портах, скрипт выводит доступные порты и просит выбрать нужный. Поддерживается VLESS с транспортом TCP/RAW и REALITY.
```bash
curl -fLO https://raw.githubusercontent.com/ochenstarik-ui/lightweight-server/main/ochenstarik-server-vpn-5.sh
chmod 700 ochenstarik-server-vpn-5.sh
bash -n ochenstarik-server-vpn-5.sh
sudo ./ochenstarik-server-vpn-5.sh
```
Во время запуска ссылка вводится скрыто. Скрипт сравнивает внешний IP до и после настройки и использует автоматический откат при ошибке. SSH и ответы сервисов на портах `443` и `63636` остаются на прямом маршруте.
Проверка маршрутизации:
```bash
sudo /usr/local/sbin/ochenstarik-xray-routing status
curl -4 https://api.ipify.org
```
Отключение системного VPN:
## VPN connection control
After the first successful setup, running `ochenstarik-server-vpn-5.sh` again opens an action menu: connect using saved settings, disconnect while keeping the configuration, replace the subscription, or show status. The same actions are available directly:
```bash
sudo ./ochenstarik-server-vpn-5.sh --enable
sudo ./ochenstarik-server-vpn-5.sh --disable
sudo ./ochenstarik-server-vpn-5.sh --reconfigure
sudo ./ochenstarik-server-vpn-5.sh --status
```
## 6. Панель 3x-ui и локальный proxy Cloudflare WARP
Disconnecting removes the system routing rules but keeps Xray and its configuration so the VPN can be reconnected without entering the subscription again.
Файл: `ochenstarik-server-panel-warp-6.sh`.
## Optional AI agents
Скрипт предлагает выбрать три разных порта:
Step 8 offers a multiple selection of seven popular agents: Hermes Agent, OpenClaw, OpenHands, OpenCode, Aider, AutoGPT, and Pi Coding Agent. Installers are downloaded only from each project's official HTTPS endpoint, checked with `bash -n`, and launched as the selected regular user rather than directly as `root`.
- порт веб-панели 3x-ui, по умолчанию `2053`;
- порт подписок 3x-ui, по умолчанию `2096`;
- локальный SOCKS5/HTTP proxy-порт WARP, по умолчанию `40000`.
AutoGPT is a larger Docker-based platform. Its official installer can request `sudo`, install Docker-related dependencies, and start local services. Pi is a lightweight terminal coding agent; after installation, start it with `pi` and use `/login` to connect a supported provider.
3x-ui устанавливается официальным интерактивным установщиком. После установки выбранные порты записываются в стандартную SQLite-базу с предварительной резервной копией. Скрипт включает сервис подписок, запускает `x-ui` и проверяет оба TCP-слушателя.
The script does not request or store model-provider API keys. After installation, log in as the selected user and run the displayed onboarding command. AI agents can execute commands, access files, install plugins, and use network services; review their permissions and sandbox settings before connecting production data or secrets.
Cloudflare WARP устанавливается из официального APT-репозитория, переводится в Local proxy mode через MASQUE и проверяется запросом к Cloudflare: ответ должен содержать `warp=on`.
## Firewall and SSH notes
- Step 2 supports IPv4-only, IPv6-only, and dual-stack UFW rules. IPv4-only mode blocks public inbound IPv6 through UFW without disabling the kernel IPv6 stack.
- A repeated step 2 run can change the IP-family mode or add ports without reinstalling everything.
- Step 3 reads the SSH port chosen in step 2 and creates an administrator with sudo access.
- The home directory, `.ssh`, and `authorized_keys` ownership and permissions are checked on every run.
- Do not remove temporary port 22 access until a new SSH session succeeds on the selected port.
## Backups
Step 7 can create:
- one initial snapshot that is never rotated automatically;
- daily backups retained for 7 days;
- weekly backups retained for 4 weeks;
- monthly backups retained for 12 months.
Schedules are selected independently, so any combination can be enabled. Keep an additional copy on another server or storage device; a local backup does not protect against loss of the entire host.
## Multi-server monitoring and management
[Server Monitor Manager](https://github.com/ochenstarik-ui/server-monitor-manager) is the companion project for centralized server operations. Its planned architecture includes:
- a Flutter client for Android and iOS;
- an ASP.NET Core API with accounts, TOTP two-factor authentication, and SignalR;
- an outbound Linux agent for metrics and managed operations;
- support for adding and monitoring multiple servers from one application.
The manager is under active early development. Review its security and development documentation before connecting real production servers.
## Requirements
- Ubuntu Server with systemd and APT;
- root access through `sudo`;
- an active SSH session;
- outbound HTTPS access to GitHub or GitHub codeload;
- a valid public SSH key for the administrator setup.
## Update
For a Git clone:
```bash
curl -fLO https://raw.githubusercontent.com/ochenstarik-ui/lightweight-server/main/ochenstarik-server-panel-warp-6.sh
chmod 700 ochenstarik-server-panel-warp-6.sh
bash -n ochenstarik-server-panel-warp-6.sh
sudo ./ochenstarik-server-panel-warp-6.sh
cd ~/lightweight-server
git pull
sudo ./ochenstarik-server-install.sh
```
В UFW открываются и проверяются `80/tcp`, `443/tcp`, выбранный порт панели и выбранный порт подписок. WARP слушает только локально на `127.0.0.1:40000` либо другом выбранном порту. Этот порт намеренно не публикуется через UFW, поскольку локальный proxy не требует аутентификации.
Публичные порты 3x-ui открываются только для семейств адресов, выбранных на этапе 2. В режиме `ipv4` скрипт не создаёт правил IPv6.
Поддерживаются официально доступные пакеты WARP для Ubuntu `jammy`, `noble`, `resolute` и Debian `bookworm`, `trixie` на `amd64` и `arm64`.
Официальные источники: [установка 3x-ui](https://github.com/MHSanaei/3x-ui/wiki/Installation), [пакеты Cloudflare WARP](https://pkg.cloudflareclient.com/) и [режимы WARP](https://developers.cloudflare.com/warp-client/warp-modes/).
## 7. Резервные копии сервера
Файл: `ochenstarik-server-backup-7.sh`.
Скрипт создаёт полный сжатый снимок корневой файловой системы с сохранением владельцев, ACL и расширенных атрибутов. Виртуальные файловые системы, временные каталоги, swap, подключённые диски и сам каталог бэкапов исключаются. Первичный снимок создаётся сразу, повторно не перезаписывается и не участвует в ротации. Если файловая система поддерживает атрибут `immutable`, первичный архив и его контрольная сумма дополнительно защищаются от случайного удаления.
При установке можно выбрать несколько расписаний одним вводом, например `1 3`:
- ежедневное — хранится 7 последних архивов;
- еженедельное — хранится 4 последних архива;
- ежемесячное — хранится 12 последних архивов;
- только первичный снимок без автоматического расписания.
## Reset project-managed configuration
```bash
curl -fLO https://raw.githubusercontent.com/ochenstarik-ui/lightweight-server/main/ochenstarik-server-backup-7.sh
chmod 700 ochenstarik-server-backup-7.sh
bash -n ochenstarik-server-backup-7.sh
sudo ./ochenstarik-server-backup-7.sh
cd ~/lightweight-server
sudo ./ochenstarik-server-uninstall.sh
```
По умолчанию архивы сохраняются в `/var/backups/ochenstarik-server`, но во время установки можно указать абсолютный путь на другом диске. Незавершённые архивы имеют суффикс `.partial` и не попадают в ротацию. Для каждого готового архива создаётся файл `.sha256`.
Проверка расписаний и ручной запуск:
```bash
systemctl list-timers "ochenstarik-backup-*"
sudo /usr/local/sbin/ochenstarik-server-backup daily
sudo journalctl -u ochenstarik-backup@daily.service
```
Проверка целостности первичного снимка:
```bash
cd /var/backups/ochenstarik-server/initial
sha256sum -c ./*.sha256
```
Восстанавливайте архив только из rescue-системы или в отдельный пустой каталог, предварительно проверив контрольную сумму. Пример распаковки в подготовленный каталог `/restore`:
```bash
mkdir -p /restore
zstd -dc /ПУТЬ/К/АРХИВУ.tar.zst | tar --acls --xattrs --numeric-owner -xpf - -C /restore
```
Это локальная резервная копия, а не защита от отказа диска или потери сервера. Регулярно переносите готовые архивы и `.sha256` на отдельный сервер или накопитель. Снимок создаётся с работающей системы; для активно изменяющихся баз данных дополнительно используйте штатный экспорт или согласованный снимок самой базы.
## Полная установка
Если нужны все модули, запускайте их по очереди:
```bash
sudo ./ochenstarik-server-1.sh
sudo ./ochenstarik-server-2.sh
sudo ./ochenstarik-server-user-3.sh
sudo ./ochenstarik-server-tg-4.sh
sudo ./ochenstarik-server-vpn-5.sh
sudo ./ochenstarik-server-panel-warp-6.sh
sudo ./ochenstarik-server-backup-7.sh
```
VPN, 3x-ui, WARP, Telegram-уведомления и автоматические расписания резервного копирования являются необязательными этапами.
## Безопасность
- Всегда проверяйте скачанные скрипты перед запуском от `root`.
- Не публикуйте токены Telegram, пароли, приватные SSH-ключи и ссылки VLESS.
- Не закрывайте действующую SSH-сессию во время переноса SSH на новый порт.
- Перед настройкой на рабочем сервере сделайте резервную копию важных данных.
Read every confirmation carefully. The reset script preserves critical Ubuntu packages and avoids removing access until explicitly confirmed.

33
docs/readme/README.ar.md Normal file
View file

@ -0,0 +1,33 @@
# الإعداد السريع لخادم Ubuntu
[English](../../README.md) | [Русский](README.ru.md) | [Español](README.es.md) | [Deutsch](README.de.md) | [Français](README.fr.md) | [Português](README.pt.md) | [中文](README.zh-CN.md) | [日本語](README.ja.md) | **العربية** | [हिन्दी](README.hi.md)
مجموعة نصوص Bash مستقلة لإعداد Ubuntu Server وتأمينه وإدارة SSH وVPN و3x-ui وWARP والنسخ الاحتياطية.
> لمراقبة عدة خوادم وإدارتها من تطبيق واحد، استخدم [Server Monitor Manager](https://github.com/ochenstarik-ui/server-monitor-manager). المشروع ما زال في مرحلة تطوير مبكرة.
## التثبيت السريع
```bash
sudo apt-get update
sudo apt-get install -y curl ca-certificates
curl -4 -fLO --retry 5 --retry-delay 10 --connect-timeout 30 \
https://raw.githubusercontent.com/ochenstarik-ui/lightweight-server/main/ochenstarik-server-install.sh
chmod 700 ochenstarik-server-install.sh
sudo ./ochenstarik-server-install.sh
```
السؤال الأول يحدد لغة المثبت، واللغة الافتراضية هي English. بعد ذلك يمكن تشغيل كل مرحلة من المراحل الثماني أو تخطيها أو إنهاء المثبت.
## المراحل
1. المنطقة الزمنية ولغة الطرفية والبرامج وملف swap.
2. الحزم الأساسية ومنفذ SSH وIPv4/IPv6 وUFW.
3. المستخدم الإداري ومفتاح SSH والصلاحيات وsudo وfail2ban.
4. إشعارات Telegram عند تسجيل الدخول عبر SSH.
5. VPN للنظام باستخدام Xray مع إمكانية الاتصال أو الفصل عند إعادة التشغيل.
6. لوحة 3x-ui ووكيل Cloudflare WARP المحلي.
7. النسخة الأولية والنسخ اليومية أو الأسبوعية أو الشهرية.
8. وكلاء ذكاء اصطناعي اختياريون: Hermes أو OpenClaw أو OpenHands أو OpenCode أو Aider أو AutoGPT أو Pi Coding Agent.
لا تغلق جلسة SSH الحالية أثناء تغيير المنفذ. اختبر الاتصال الجديد أولاً في طرفية أخرى. راجع [الوثائق الإنجليزية الرئيسية](../../README.md) لمزيد من التفاصيل.

33
docs/readme/README.de.md Normal file
View file

@ -0,0 +1,33 @@
# Schnelle Einrichtung von Ubuntu Server
[English](../../README.md) | [Русский](README.ru.md) | [Español](README.es.md) | **Deutsch** | [Français](README.fr.md) | [Português](README.pt.md) | [中文](README.zh-CN.md) | [日本語](README.ja.md) | [العربية](README.ar.md) | [हिन्दी](README.hi.md)
Modulare Bash-Skripte für Ersteinrichtung, Absicherung, SSH, VPN, 3x-ui, WARP und Sicherungen auf Ubuntu Server.
> Zur Überwachung und gemeinsamen Verwaltung mehrerer Server verwenden Sie [Server Monitor Manager](https://github.com/ochenstarik-ui/server-monitor-manager). Das Projekt befindet sich noch in einer frühen Entwicklungsphase.
## Schnellinstallation
```bash
sudo apt-get update
sudo apt-get install -y curl ca-certificates
curl -4 -fLO --retry 5 --retry-delay 10 --connect-timeout 30 \
https://raw.githubusercontent.com/ochenstarik-ui/lightweight-server/main/ochenstarik-server-install.sh
chmod 700 ochenstarik-server-install.sh
sudo ./ochenstarik-server-install.sh
```
Die erste Frage wählt die Sprache des Installers; English ist voreingestellt. Danach kann jeder der acht Schritte ausgeführt, übersprungen oder zum Beenden verwendet werden.
## Schritte
1. Zeitzone, Terminalsprache, empfohlene Programme und Swap.
2. Basispakete, SSH-Port, IPv4/IPv6 und UFW.
3. Administrator, SSH-Schlüssel, Rechte, sudo und fail2ban.
4. Telegram-Benachrichtigungen für SSH-Anmeldungen.
5. System-VPN über Xray mit Verbinden und Trennen bei späteren Ausführungen.
6. 3x-ui und lokaler Cloudflare-WARP-Proxy.
7. Erstsicherung sowie tägliche, wöchentliche oder monatliche Sicherungen.
8. Optionale KI-Agenten: Hermes, OpenClaw, OpenHands, OpenCode, Aider, AutoGPT oder Pi Coding Agent.
Schließen Sie die aktuelle SSH-Sitzung während der Portänderung nicht. Testen Sie zuerst die neue Anmeldung in einem zweiten Terminal. Weitere Hinweise enthält die [englische Hauptdokumentation](../../README.md).

33
docs/readme/README.es.md Normal file
View file

@ -0,0 +1,33 @@
# Configuración rápida de Ubuntu Server
[English](../../README.md) | [Русский](README.ru.md) | **Español** | [Deutsch](README.de.md) | [Français](README.fr.md) | [Português](README.pt.md) | [中文](README.zh-CN.md) | [日本語](README.ja.md) | [العربية](README.ar.md) | [हिन्दी](README.hi.md)
Conjunto modular de scripts Bash para la configuración inicial, seguridad, SSH, VPN, 3x-ui, WARP y copias de seguridad de Ubuntu Server.
> Para supervisar y administrar varios servidores desde una aplicación, consulte [Server Monitor Manager](https://github.com/ochenstarik-ui/server-monitor-manager). El proyecto se encuentra en una fase temprana de desarrollo.
## Instalación rápida
```bash
sudo apt-get update
sudo apt-get install -y curl ca-certificates
curl -4 -fLO --retry 5 --retry-delay 10 --connect-timeout 30 \
https://raw.githubusercontent.com/ochenstarik-ui/lightweight-server/main/ochenstarik-server-install.sh
chmod 700 ochenstarik-server-install.sh
sudo ./ochenstarik-server-install.sh
```
La primera pregunta selecciona el idioma del instalador; English es el valor predeterminado. Después puede instalar, omitir o detener cada una de las ocho etapas.
## Etapas
1. Zona horaria, idioma del terminal, programas recomendados y swap.
2. Paquetes básicos, puerto SSH, IPv4/IPv6 y UFW.
3. Usuario administrador, clave SSH, permisos, sudo y fail2ban.
4. Notificaciones de Telegram para accesos SSH.
5. VPN del sistema mediante Xray, con conexión y desconexión en ejecuciones posteriores.
6. Panel 3x-ui y proxy local Cloudflare WARP.
7. Instantánea inicial y copias diarias, semanales o mensuales.
8. Agentes de IA opcionales: Hermes, OpenClaw, OpenHands, OpenCode, Aider, AutoGPT o Pi Coding Agent.
No cierre la sesión SSH actual durante el cambio de puerto. Pruebe primero el nuevo acceso en otro terminal. Consulte la [documentación principal en inglés](../../README.md) para la instalación por archivo, actualización y restablecimiento.

33
docs/readme/README.fr.md Normal file
View file

@ -0,0 +1,33 @@
# Configuration rapide dUbuntu Server
[English](../../README.md) | [Русский](README.ru.md) | [Español](README.es.md) | [Deutsch](README.de.md) | **Français** | [Português](README.pt.md) | [中文](README.zh-CN.md) | [日本語](README.ja.md) | [العربية](README.ar.md) | [हिन्दी](README.hi.md)
Scripts Bash modulaires pour la configuration initiale, la sécurisation, SSH, le VPN, 3x-ui, WARP et les sauvegardes dUbuntu Server.
> Pour surveiller et administrer plusieurs serveurs depuis une seule application, consultez [Server Monitor Manager](https://github.com/ochenstarik-ui/server-monitor-manager). Ce projet est encore au début de son développement.
## Installation rapide
```bash
sudo apt-get update
sudo apt-get install -y curl ca-certificates
curl -4 -fLO --retry 5 --retry-delay 10 --connect-timeout 30 \
https://raw.githubusercontent.com/ochenstarik-ui/lightweight-server/main/ochenstarik-server-install.sh
chmod 700 ochenstarik-server-install.sh
sudo ./ochenstarik-server-install.sh
```
La première question sélectionne la langue de linstallateur ; English est la valeur par défaut. Chacune des huit étapes peut ensuite être installée, ignorée ou utilisée pour quitter.
## Étapes
1. Fuseau horaire, langue du terminal, programmes recommandés et swap.
2. Paquets de base, port SSH, IPv4/IPv6 et UFW.
3. Administrateur, clé SSH, permissions, sudo et fail2ban.
4. Notifications Telegram des connexions SSH.
5. VPN système via Xray, avec connexion et déconnexion lors des exécutions suivantes.
6. Panneau 3x-ui et proxy local Cloudflare WARP.
7. Instantané initial et sauvegardes quotidiennes, hebdomadaires ou mensuelles.
8. Agents IA facultatifs : Hermes, OpenClaw, OpenHands, OpenCode, Aider, AutoGPT ou Pi Coding Agent.
Ne fermez pas la session SSH actuelle pendant le changement de port. Testez dabord la nouvelle connexion dans un autre terminal. Consultez la [documentation principale en anglais](../../README.md) pour plus de détails.

33
docs/readme/README.hi.md Normal file
View file

@ -0,0 +1,33 @@
# Ubuntu Server का त्वरित सेटअप
[English](../../README.md) | [Русский](README.ru.md) | [Español](README.es.md) | [Deutsch](README.de.md) | [Français](README.fr.md) | [Português](README.pt.md) | [中文](README.zh-CN.md) | [日本語](README.ja.md) | [العربية](README.ar.md) | **हिन्दी**
Ubuntu Server की प्रारंभिक स्थापना, सुरक्षा, SSH, VPN, 3x-ui, WARP और बैकअप के लिए मॉड्यूलर Bash स्क्रिप्ट।
> एक ऐप से कई सर्वरों की निगरानी और संयुक्त प्रबंधन के लिए [Server Monitor Manager](https://github.com/ochenstarik-ui/server-monitor-manager) का उपयोग करें। यह परियोजना अभी शुरुआती विकास चरण में है।
## त्वरित स्थापना
```bash
sudo apt-get update
sudo apt-get install -y curl ca-certificates
curl -4 -fLO --retry 5 --retry-delay 10 --connect-timeout 30 \
https://raw.githubusercontent.com/ochenstarik-ui/lightweight-server/main/ochenstarik-server-install.sh
chmod 700 ochenstarik-server-install.sh
sudo ./ochenstarik-server-install.sh
```
पहला प्रश्न इंस्टॉलर की भाषा चुनता है; डिफ़ॉल्ट English है। इसके बाद आठों चरणों को चलाया, छोड़ा या इंस्टॉलर बंद किया जा सकता है।
## चरण
1. समय क्षेत्र, टर्मिनल भाषा, अनुशंसित प्रोग्राम और swap।
2. मूल पैकेज, SSH पोर्ट, IPv4/IPv6 और UFW।
3. प्रशासक, SSH कुंजी, अनुमतियाँ, sudo और fail2ban।
4. SSH लॉगिन के लिए Telegram सूचनाएँ।
5. Xray के माध्यम से सिस्टम VPN, दोबारा चलाने पर कनेक्ट या डिस्कनेक्ट करने की सुविधा।
6. 3x-ui पैनल और स्थानीय Cloudflare WARP प्रॉक्सी।
7. प्रारंभिक स्नैपशॉट और दैनिक, साप्ताहिक या मासिक बैकअप।
8. वैकल्पिक AI एजेंट: Hermes, OpenClaw, OpenHands, OpenCode, Aider, AutoGPT या Pi Coding Agent।
SSH पोर्ट बदलते समय मौजूदा सत्र बंद न करें। पहले दूसरे टर्मिनल में नया लॉगिन जाँचें। अधिक जानकारी के लिए [मुख्य अंग्रेज़ी दस्तावेज़](../../README.md) देखें।

33
docs/readme/README.ja.md Normal file
View file

@ -0,0 +1,33 @@
# Ubuntu Server クイックセットアップ
[English](../../README.md) | [Русский](README.ru.md) | [Español](README.es.md) | [Deutsch](README.de.md) | [Français](README.fr.md) | [Português](README.pt.md) | [中文](README.zh-CN.md) | **日本語** | [العربية](README.ar.md) | [हिन्दी](README.hi.md)
Ubuntu Server の初期設定、セキュリティ、SSH、VPN、3x-ui、WARP、バックアップを行うモジュール式 Bash スクリプトです。
> 複数のサーバーを一つのアプリで監視・管理するには、[Server Monitor Manager](https://github.com/ochenstarik-ui/server-monitor-manager) を使用してください。このプロジェクトは現在、開発の初期段階です。
## クイックインストール
```bash
sudo apt-get update
sudo apt-get install -y curl ca-certificates
curl -4 -fLO --retry 5 --retry-delay 10 --connect-timeout 30 \
https://raw.githubusercontent.com/ochenstarik-ui/lightweight-server/main/ochenstarik-server-install.sh
chmod 700 ochenstarik-server-install.sh
sudo ./ochenstarik-server-install.sh
```
最初の質問でインストーラーの言語を選択します。既定値は English です。その後、8 つの各ステップを実行、スキップ、または終了できます。
## ステップ
1. タイムゾーン、端末言語、推奨プログラム、スワップ。
2. 基本パッケージ、SSH ポート、IPv4/IPv6、UFW。
3. 管理者、SSH 鍵、アクセス権、sudo、fail2ban。
4. SSH ログインの Telegram 通知。
5. Xray によるシステム VPN。再実行時に接続または切断できます。
6. 3x-ui パネルとローカル Cloudflare WARP プロキシ。
7. 初回スナップショットと日次、週次、月次バックアップ。
8. オプションの AI エージェントHermes、OpenClaw、OpenHands、OpenCode、Aider、AutoGPT、Pi Coding Agent。
SSH ポートの変更中は現在のセッションを閉じないでください。別のターミナルで新しい接続を先に確認してください。詳細は[英語のメイン文書](../../README.md)を参照してください。

33
docs/readme/README.pt.md Normal file
View file

@ -0,0 +1,33 @@
# Configuração rápida do Ubuntu Server
[English](../../README.md) | [Русский](README.ru.md) | [Español](README.es.md) | [Deutsch](README.de.md) | [Français](README.fr.md) | **Português** | [中文](README.zh-CN.md) | [日本語](README.ja.md) | [العربية](README.ar.md) | [हिन्दी](README.hi.md)
Scripts Bash modulares para configuração inicial, segurança, SSH, VPN, 3x-ui, WARP e backups no Ubuntu Server.
> Para monitorar e administrar vários servidores em um único aplicativo, use o [Server Monitor Manager](https://github.com/ochenstarik-ui/server-monitor-manager). O projeto ainda está em uma fase inicial de desenvolvimento.
## Instalação rápida
```bash
sudo apt-get update
sudo apt-get install -y curl ca-certificates
curl -4 -fLO --retry 5 --retry-delay 10 --connect-timeout 30 \
https://raw.githubusercontent.com/ochenstarik-ui/lightweight-server/main/ochenstarik-server-install.sh
chmod 700 ochenstarik-server-install.sh
sudo ./ochenstarik-server-install.sh
```
A primeira pergunta seleciona o idioma do instalador; English é o padrão. Depois, cada uma das oito etapas pode ser instalada, ignorada ou usada para sair.
## Etapas
1. Fuso horário, idioma do terminal, programas recomendados e swap.
2. Pacotes básicos, porta SSH, IPv4/IPv6 e UFW.
3. Administrador, chave SSH, permissões, sudo e fail2ban.
4. Notificações Telegram para logins SSH.
5. VPN do sistema por Xray, com conexão e desconexão nas execuções seguintes.
6. Painel 3x-ui e proxy local Cloudflare WARP.
7. Snapshot inicial e backups diários, semanais ou mensais.
8. Agentes de IA opcionais: Hermes, OpenClaw, OpenHands, OpenCode, Aider, AutoGPT ou Pi Coding Agent.
Não feche a sessão SSH atual durante a alteração da porta. Teste primeiro o novo login em outro terminal. Consulte a [documentação principal em inglês](../../README.md) para obter detalhes adicionais.

484
docs/readme/README.ru.md Normal file
View file

@ -0,0 +1,484 @@
# Быстрая настройка Ubuntu Server
[English](../../README.md) | **Русский** | [Español](README.es.md) | [Deutsch](README.de.md) | [Français](README.fr.md) | [Português](README.pt.md) | [中文](README.zh-CN.md) | [日本語](README.ja.md) | [العربية](README.ar.md) | [हिन्दी](README.hi.md)
> Для мониторинга и объединённого управления несколькими серверами используйте дополнительный проект [Server Monitor Manager](https://github.com/ochenstarik-ui/server-monitor-manager). Проект находится на ранней стадии разработки.
Набор независимых Bash-скриптов для первоначальной настройки Ubuntu Server. Можно запустить только нужные модули или выполнить их по порядку для полной настройки.
## Быстрая установка
Скопируйте весь блок в терминал сервера. Он загружает архив через `codeload.github.com`, проверяет ошибки на каждом шаге и запускает единый пошаговый мастер:
```bash
set -e
cd "$HOME"
sudo apt-get update
sudo apt-get install -y curl ca-certificates tar
archive="$(mktemp)"
trap 'rm -f "$archive"' EXIT
curl -4 -fL \
--retry 5 \
--retry-delay 10 \
--connect-timeout 30 \
https://codeload.github.com/ochenstarik-ui/lightweight-server/tar.gz/refs/heads/main \
-o "$archive"
install -d -m 700 lightweight-server
tar -xzf "$archive" -C lightweight-server --strip-components=1
rm -f "$archive"
trap - EXIT
cd lightweight-server
chmod 700 ./*.sh
sudo ./ochenstarik-server-install.sh
```
Первым запросом мастер предлагает выбрать язык диалога и только затем показывает этапы 18 по порядку. На каждом этапе можно выбрать установку, пропуск или завершение. В первом этапе пустой ввод устанавливает все рекомендуемые наборы программ. Во время изменения SSH-порта не закрывайте текущую сессию — сначала проверьте новый вход во втором терминале.
Если нужен только мастер без скачивания полного архива:
```bash
sudo apt-get update
sudo apt-get install -y curl ca-certificates
curl -4 -fLO \
--retry 5 \
--retry-delay 10 \
--connect-timeout 30 \
https://raw.githubusercontent.com/ochenstarik-ui/lightweight-server/main/ochenstarik-server-install.sh
chmod 700 ochenstarik-server-install.sh
sudo ./ochenstarik-server-install.sh
```
При запуске одного мастера недостающие этапы будут загружены автоматически из основной ветки и проверены командой `bash -n`.
## Что входит в проект
| Файл | Назначение | Можно запускать отдельно |
| --- | --- | --- |
| `ochenstarik-server-install.sh` | Сначала выбирает язык диалога, затем последовательно предлагает установить, пропустить или завершить каждый этап | Да; рекомендуемый способ полной настройки |
| `ochenstarik-server-1.sh` | Предлагает выбрать часовой пояс, язык терминала и наборы программ, создаёт swap-файл 2 ГБ | Да |
| `ochenstarik-server-2.sh` | Предлагает будущий SSH-порт и IPv4/IPv6, обновляет систему, ставит обязательные пакеты и настраивает UFW | Да |
| `ochenstarik-server-user-3.sh` | Создаёт администратора, переносит SSH на порт из этапа 2, проверяет права и включает fail2ban | После установки необходимых пакетов; проще всего сначала запустить скрипт 2 |
| `ochenstarik-server-tg-4.sh` | Отправляет уведомления в Telegram при успешном входе по SSH | После установки OpenSSH и `curl`; обычно после скрипта 2 или 3 |
| `ochenstarik-server-vpn-5.sh` | Устанавливает Xray и направляет системный трафик через VLESS + REALITY | Да; зависимости устанавливаются автоматически |
| `ochenstarik-server-panel-warp-6.sh` | Устанавливает 3x-ui и Cloudflare WARP, настраивает и проверяет их порты | Ubuntu 22.04/24.04/26.04 или Debian 12/13 |
| `ochenstarik-server-backup-7.sh` | Создаёт неизменяемый первичный снимок и настраивает выбранные расписания резервного копирования | Да; зависимости устанавливаются автоматически |
| `ochenstarik-server-ai-agents-8.sh` | Устанавливает выбранных AI-агентов: Hermes, OpenClaw, OpenHands, OpenCode, Aider, AutoGPT или Pi Coding Agent | Да; API-ключи настраиваются отдельно |
| `ochenstarik-server-monitor-manager.sh` | Устанавливает SSH endpoint, главный WireGuard Hub или подключаемый Node для Server Monitor Manager | Да; Hub требуется публичный UDP-адрес |
| `ochenstarik-server-uninstall.sh` | Удаляет настройки, службы и данные проекта для повторной установки с начала | Да; запускать только из сохранённой SSH-сессии |
Файлы `*.env.example` являются только примерами. Не записывайте настоящие пароли, токены и приватные SSH-ключи в Git.
## Server Monitor Manager: Hub и вторичные серверы
Главному Hub нужен белый IPv4 или домен и открытый UDP-порт (по умолчанию `51820`). Вторичные Node подключаются исходящим WireGuard-соединением и не требуют белого IP.
```bash
curl -fLO https://raw.githubusercontent.com/ochenstarik-ui/lightweight-server/main/ochenstarik-server-monitor-manager.sh
chmod 700 ochenstarik-server-monitor-manager.sh
bash -n ochenstarik-server-monitor-manager.sh
sudo ./ochenstarik-server-monitor-manager.sh hub
```
На Hub создайте отдельный код для каждого Node, после чего на соответствующем сервере запустите тот же файл в режиме `node`:
```bash
sudo ochenstarik-smm node-code ai-agent
sudo ochenstarik-smm node-code home
sudo ./ochenstarik-server-monitor-manager.sh node
```
Links являются направленными:
```bash
sudo ochenstarik-smm link-connect ai-agent home
sudo ochenstarik-smm link-disconnect ai-agent home
sudo ochenstarik-smm nodes
sudo ochenstarik-smm links
```
Текущий код конфигурации содержит приватный WireGuard-ключ Node и предназначен только для раннего тестирования. Не сохраняйте его в Git или сообщениях. Локальная генерация ключа и одноразовая регистрация с ограниченным сроком будут добавлены до production-использования.
## Требования
- Ubuntu Server или совместимая Debian-система с `systemd`;
- доступ `root` или пользователь с `sudo`;
- подключение к интернету;
- рекомендуется сохранить текущую SSH-сессию открытой до проверки нового входа.
## Получение проекта
Рекомендуемый способ — загрузка архива через `codeload.github.com`. Он подходит в том числе для серверов, на которых соединение с основным доменом `github.com` завершается ошибкой `SSL connection timeout`:
```bash
set -e
cd "$HOME"
sudo apt-get update
sudo apt-get install -y curl ca-certificates tar
curl -4 -fL \
--retry 5 \
--retry-delay 10 \
--connect-timeout 30 \
https://codeload.github.com/ochenstarik-ui/lightweight-server/tar.gz/refs/heads/main \
-o lightweight-server.tar.gz
mkdir lightweight-server
tar -xzf lightweight-server.tar.gz -C lightweight-server --strip-components=1
rm lightweight-server.tar.gz
cd lightweight-server
chmod 700 ./*.sh
```
Если `github.com` доступен с сервера, проект также можно получить через Git:
```bash
set -e
cd "$HOME"
sudo apt-get update
sudo apt-get install -y git ca-certificates
git -c http.version=HTTP/1.1 clone --depth 1 https://github.com/ochenstarik-ui/lightweight-server.git
cd lightweight-server
chmod 700 ./*.sh
```
Команда `set -e` останавливает последовательность при ошибке загрузки, поэтому команды `cd` и `chmod` не будут выполняться для отсутствующей папки.
Перед запуском любого выбранного файла можно проверить его синтаксис:
```bash
bash -n ./ИМЯ-СКРИПТА.sh
```
## 1. Часовой пояс, язык, программы и swap
Файл: `ochenstarik-server-1.sh`.
При запуске единого мастера самый первый запрос, ещё до выбора этапа 1, задаёт язык диалога: English, Русский, Español, Deutsch, Français, Português, 中文, 日本語, العربية или हिन्दी. Начальное меню выводится на английском, а пустой ввод оставляет English. Выбор передаётся первому этапу автоматически и не меняет системную локаль сервера.
Первый этап начинается с нумерованного меню популярных часовых поясов, включая `Asia/Novosibirsk`, `Europe/Moscow` и `UTC`. Нужную зону достаточно выбрать по номеру. Для остальных зон предусмотрен полный список с последовательным выбором региона и часового пояса — вводить имя вручную не требуется.
После выбора времени скрипт показывает меню языков терминала: оставить текущий, русский, английский, немецкий, французский, испанский, итальянский, португальский, польский, украинский, турецкий, китайский, японский, корейский либо выбрать другую локаль из полного системного списка. Выбранный язык применяется к новым терминальным сессиям. Исходный `/etc/default/locale` сохраняется для последующего восстановления.
Затем можно отметить несколько наборов программ номерами через пробел, например `1 2 4 9`. Пустой ввод устанавливает все наборы, а `0` пропускает дополнительные программы:
1. Терминал и повседневная работа — Git, wget/curl, jq/yq, ripgrep, fd, fzf, lnav, редакторы, MC, tmux/screen, htop/btop, ncdu, rsync и strace.
2. Диагностика сети — DNS, ping, MTR, traceroute, tcpdump, nmap, netcat, HTTPie, whois, ethtool и socat.
3. Мониторинг системы и дисков — iotop, sysstat, SMART, lm-sensors, atop, nethogs, vnStat и psmisc.
4. Безопасность и обслуживание — auditd, AppArmor tools, Lynis, needrestart, unattended-upgrades, debsums, ACL и extended attributes.
5. Разработка и автоматизация — компилятор, ShellCheck, Python pip/venv и pipx.
6. Архивы и данные — 7-Zip, ZIP/RAR, SQLite, csvkit, клиенты PostgreSQL и Redis.
7. Документы, PDF и OCR — Poppler, qpdf, Ghostscript, OCRmyPDF/Tesseract, LibreOffice, Pandoc, antiword и catdoc.
8. Изображения, аудио и видео — ImageMagick, ExifTool, WebP, FFmpeg и MediaInfo.
9. Резервное копирование — restic, BorgBackup и rclone.
Скрипт проверяет доступность каждого пакета в подключённых репозиториях. Недоступные для конкретной версии или набора репозиториев пакеты пропускаются с предупреждением. Список программ, которых не было до запуска, сохраняется в `/etc/ochenstarik-server/step1-installed-packages.list` для безопасной очистки.
Затем скрипт создаёт `/swapfile` размером 2 ГБ, добавляет его в `/etc/fstab` и устанавливает `vm.swappiness=20`. Повторный запуск предусмотрен.
```bash
curl -fLO https://raw.githubusercontent.com/ochenstarik-ui/lightweight-server/main/ochenstarik-server-1.sh
chmod 700 ochenstarik-server-1.sh
bash -n ochenstarik-server-1.sh
sudo ./ochenstarik-server-1.sh
```
Проверка:
```bash
swapon --show
sysctl vm.swappiness
```
## 2. Базовые пакеты, SSH-порт и брандмауэр
Файл: `ochenstarik-server-2.sh`.
При каждом запуске скрипт предлагает три действия: полную установку или обновление, изменение режима IP без переустановки пакетов либо добавление открытых портов. При полной установке он запрашивает SSH-порт для этапа 3, обновляет систему и устанавливает только обязательные компоненты: OpenSSH, UFW, fail2ban, `sudo`, TLS-сертификаты и сетевые инструменты. Дополнительные программы выбираются на этапе 1.
Для входящих подключений можно выбрать:
- только IPv4 — публичные входящие IPv6-порты закрываются через UFW, но IPv6-стек ядра не отключается; этот режим рекомендуется, если 3x-ui используется только по IPv4;
- только IPv6 — управляемые скриптом порты открываются лишь для IPv6;
- IPv4 + IPv6 — порты открываются для обоих семейств.
Выбор сохраняется в `/etc/ochenstarik-server/ip-family.conf`, а список управляемых портов — в `/etc/ochenstarik-server/ufw-managed-ports.conf`. При переходе на режим с одним семейством старые правила этих портов пересоздаются. Скрипт не разрешит отключить семейство адресов, через которое работает текущая SSH-сессия. Для режима IPv4 UFW остаётся с поддержкой IPv6, чтобы политика `deny incoming` продолжала закрывать публичный IPv6. Для режима IPv6 также проверяются глобальный IPv6-адрес и маршрут по умолчанию.
В режиме «только IPv6» системный IPv4 остаётся доступен для исходящих соединений и работы менеджера пакетов, но входящие управляемые порты по IPv4 закрываются UFW. Полное переносимое отключение IPv4 в Linux без привязки к конкретной сетевой конфигурации не выполняется.
Открываются TCP-порты:
- `22` — временный SSH-порт;
- выбранный SSH-порт — будущий порт SSH;
- `80`, `443` — HTTP и HTTPS;
- `2096`, `40000`, `63636` — порты приложений.
```bash
curl -fLO https://raw.githubusercontent.com/ochenstarik-ui/lightweight-server/main/ochenstarik-server-2.sh
chmod 700 ochenstarik-server-2.sh
bash -n ochenstarik-server-2.sh
sudo ./ochenstarik-server-2.sh
```
Проверка:
```bash
sudo ufw status verbose
cat /etc/ochenstarik-server/ip-family.conf
cat /proc/sys/net/ipv6/conf/all/disable_ipv6
timedatectl
```
## 3. Администратор и защита SSH
Файл: `ochenstarik-server-user-3.sh`.
При каждом запуске скрипт предлагает два режима:
1. новая установка или повторная настройка администратора и SSH;
2. только добавление открытых портов в UFW без изменения пользователя и SSH.
В режиме новой установки скрипт интерактивно запрашивает имя администратора, SSH-ключ и пароль. SSH-порт автоматически читается из защищённого файла, созданного этапом 2. Если этап 2 не выполнялся, скрипт предложит выбрать порт вручную. Затем он:
- создаёт или обновляет пользователя;
- добавляет пользователя в группу `sudo`;
- назначает пользователю его домашнюю папку и всё содержимое `.ssh`;
- устанавливает права `700` для `.ssh` и `600` для `authorized_keys` и остальных файлов ключей;
- запрещает вход `root` по SSH;
- переносит SSH на выбранный порт и добавляет для него правило UFW;
- настраивает fail2ban;
- по выбору управляет правилами UFW.
Правила UFW добавляются с учётом режима IPv4/IPv6, сохранённого скриптом 2. Новые порты также записываются в общий список управляемых правил, поэтому последующее переключение IP-режима пересоздаст их корректно.
Для отдельного запуска сначала установите зависимости:
```bash
sudo apt-get update
sudo apt-get install -y sudo ufw fail2ban openssl openssh-server
curl -fLO https://raw.githubusercontent.com/ochenstarik-ui/lightweight-server/main/ochenstarik-server-user-3.sh
chmod 700 ochenstarik-server-user-3.sh
bash -n ochenstarik-server-user-3.sh
sudo ./ochenstarik-server-user-3.sh
```
Не закрывая текущую сессию, откройте второй терминал и проверьте вход:
```bash
ssh -p ВЫБРАННЫЙ_ПОРТ ИМЯ_ПОЛЬЗОВАТЕЛЯ@IP_СЕРВЕРА
```
Только после успешной проверки можно удалить временное правило порта 22:
```bash
sudo ufw delete allow 22/tcp
```
Чтобы позднее открыть дополнительные порты, повторно запустите скрипт, выберите пункт `2` и введите правила через пробел или запятую:
```text
80 443/tcp 53/udp 40000
```
Для записей без протокола используется TCP. Допустимы только порты от `1` до `65535`. Если UFW неактивен, правила будут сохранены, но скрипт не включит брандмауэр автоматически во избежание потери SSH-доступа.
## 4. Telegram-уведомления о входах по SSH
Файл: `ochenstarik-server-tg-4.sh`.
Создайте бота через `@BotFather`, отправьте ему сообщение и узнайте ID чата. Скрипт запросит токен и ID интерактивно, отправит тестовое сообщение и подключит уведомления через PAM.
Для отдельного запуска:
```bash
sudo apt-get update
sudo apt-get install -y curl openssh-server logrotate
curl -fLO https://raw.githubusercontent.com/ochenstarik-ui/lightweight-server/main/ochenstarik-server-tg-4.sh
chmod 700 ochenstarik-server-tg-4.sh
bash -n ochenstarik-server-tg-4.sh
sudo ./ochenstarik-server-tg-4.sh
```
Журнал уведомлений:
```bash
sudo tail -f /var/log/ochenstarik-ssh-login-telegram.log
```
Токен сохраняется на сервере в `/etc/ochenstarik-server/telegram.conf` с ограниченными правами. Не добавляйте этот файл в репозиторий.
## 5. Системный VPN через Xray
Файл: `ochenstarik-server-vpn-5.sh`.
Скрипт устанавливает Xray и необходимые пакеты, принимает прямую ссылку `vless://` либо HTTPS-ссылку подписки 3x-ui и настраивает маршрутизацию через `nftables`. Если подписка содержит VLESS-ссылки на нескольких портах, скрипт выводит доступные порты и просит выбрать нужный. Поддерживается VLESS с транспортом TCP/RAW и REALITY.
При повторном запуске появляется меню: подключить VPN с сохранёнными настройками, отключить его без удаления конфигурации, заменить подписку либо показать состояние.
```bash
curl -fLO https://raw.githubusercontent.com/ochenstarik-ui/lightweight-server/main/ochenstarik-server-vpn-5.sh
chmod 700 ochenstarik-server-vpn-5.sh
bash -n ochenstarik-server-vpn-5.sh
sudo ./ochenstarik-server-vpn-5.sh
```
Во время запуска ссылка вводится скрыто. Скрипт сравнивает внешний IP до и после настройки и использует автоматический откат при ошибке. SSH и ответы сервисов на портах `443` и `63636` остаются на прямом маршруте.
Проверка маршрутизации:
```bash
sudo /usr/local/sbin/ochenstarik-xray-routing status
curl -4 https://api.ipify.org
```
Прямые команды управления:
```bash
sudo ./ochenstarik-server-vpn-5.sh --enable
sudo ./ochenstarik-server-vpn-5.sh --disable
sudo ./ochenstarik-server-vpn-5.sh --reconfigure
sudo ./ochenstarik-server-vpn-5.sh --status
```
Отключение удаляет правила системной маршрутизации, но сохраняет Xray и настройки для быстрого повторного подключения.
## 6. Панель 3x-ui и локальный proxy Cloudflare WARP
Файл: `ochenstarik-server-panel-warp-6.sh`.
Скрипт предлагает выбрать три разных порта:
- порт веб-панели 3x-ui, по умолчанию `2053`;
- порт подписок 3x-ui, по умолчанию `2096`;
- локальный SOCKS5/HTTP proxy-порт WARP, по умолчанию `40000`.
3x-ui устанавливается официальным интерактивным установщиком. После установки выбранные порты записываются в стандартную SQLite-базу с предварительной резервной копией. Скрипт включает сервис подписок, запускает `x-ui` и проверяет оба TCP-слушателя.
Cloudflare WARP устанавливается из официального APT-репозитория, переводится в Local proxy mode через MASQUE и проверяется запросом к Cloudflare: ответ должен содержать `warp=on`.
```bash
curl -fLO https://raw.githubusercontent.com/ochenstarik-ui/lightweight-server/main/ochenstarik-server-panel-warp-6.sh
chmod 700 ochenstarik-server-panel-warp-6.sh
bash -n ochenstarik-server-panel-warp-6.sh
sudo ./ochenstarik-server-panel-warp-6.sh
```
В UFW открываются и проверяются `80/tcp`, `443/tcp`, выбранный порт панели и выбранный порт подписок. WARP слушает только локально на `127.0.0.1:40000` либо другом выбранном порту. Этот порт намеренно не публикуется через UFW, поскольку локальный proxy не требует аутентификации.
Публичные порты 3x-ui открываются только для семейств адресов, выбранных на этапе 2. В режиме `ipv4` скрипт не создаёт правил IPv6.
Поддерживаются официально доступные пакеты WARP для Ubuntu `jammy`, `noble`, `resolute` и Debian `bookworm`, `trixie` на `amd64` и `arm64`.
Официальные источники: [установка 3x-ui](https://github.com/MHSanaei/3x-ui/wiki/Installation), [пакеты Cloudflare WARP](https://pkg.cloudflareclient.com/) и [режимы WARP](https://developers.cloudflare.com/warp-client/warp-modes/).
## 7. Резервные копии сервера
Файл: `ochenstarik-server-backup-7.sh`.
Скрипт создаёт полный сжатый снимок корневой файловой системы с сохранением владельцев, ACL и расширенных атрибутов. Виртуальные файловые системы, временные каталоги, swap, подключённые диски и сам каталог бэкапов исключаются. Первичный снимок создаётся сразу, повторно не перезаписывается и не участвует в ротации. Если файловая система поддерживает атрибут `immutable`, первичный архив и его контрольная сумма дополнительно защищаются от случайного удаления.
При установке можно выбрать несколько расписаний одним вводом, например `1 3`:
- ежедневное — хранится 7 последних архивов;
- еженедельное — хранится 4 последних архива;
- ежемесячное — хранится 12 последних архивов;
- только первичный снимок без автоматического расписания.
```bash
curl -fLO https://raw.githubusercontent.com/ochenstarik-ui/lightweight-server/main/ochenstarik-server-backup-7.sh
chmod 700 ochenstarik-server-backup-7.sh
bash -n ochenstarik-server-backup-7.sh
sudo ./ochenstarik-server-backup-7.sh
```
По умолчанию архивы сохраняются в `/var/backups/ochenstarik-server`, но во время установки можно указать абсолютный путь на другом диске. Незавершённые архивы имеют суффикс `.partial` и не попадают в ротацию. Для каждого готового архива создаётся файл `.sha256`.
Проверка расписаний и ручной запуск:
```bash
systemctl list-timers "ochenstarik-backup-*"
sudo /usr/local/sbin/ochenstarik-server-backup daily
sudo journalctl -u ochenstarik-backup@daily.service
```
Проверка целостности первичного снимка:
```bash
cd /var/backups/ochenstarik-server/initial
sha256sum -c ./*.sha256
```
Восстанавливайте архив только из rescue-системы или в отдельный пустой каталог, предварительно проверив контрольную сумму. Пример распаковки в подготовленный каталог `/restore`:
```bash
mkdir -p /restore
zstd -dc /ПУТЬ/К/АРХИВУ.tar.zst | tar --acls --xattrs --numeric-owner -xpf - -C /restore
```
Это локальная резервная копия, а не защита от отказа диска или потери сервера. Регулярно переносите готовые архивы и `.sha256` на отдельный сервер или накопитель. Снимок создаётся с работающей системы; для активно изменяющихся баз данных дополнительно используйте штатный экспорт или согласованный снимок самой базы.
## Полная установка
Рекомендуемый способ — единый мастер. Он показывает этапы по порядку и перед каждым предлагает установить его, пропустить либо завершить работу. Если остальные файлы находятся рядом, мастер использует их. Если какой-либо этап отсутствует, мастер безопасно загрузит его из основной ветки, проверит `bash -n` и только потом запустит.
```bash
curl -fLO https://raw.githubusercontent.com/ochenstarik-ui/lightweight-server/main/ochenstarik-server-install.sh
chmod 700 ochenstarik-server-install.sh
bash -n ochenstarik-server-install.sh
sudo ./ochenstarik-server-install.sh
```
После ошибки этап можно повторить, пропустить или завершить весь мастер. Повторный запуск мастера разрешён: каждый модуль поддерживает повторную настройку.
Отдельные скрипты сохранены. При необходимости их по-прежнему можно запускать вручную по очереди:
```bash
sudo ./ochenstarik-server-1.sh
sudo ./ochenstarik-server-2.sh
sudo ./ochenstarik-server-user-3.sh
sudo ./ochenstarik-server-tg-4.sh
sudo ./ochenstarik-server-vpn-5.sh
sudo ./ochenstarik-server-panel-warp-6.sh
sudo ./ochenstarik-server-backup-7.sh
sudo ./ochenstarik-server-ai-agents-8.sh
```
## 8. AI-агенты на выбор
Файл: `ochenstarik-server-ai-agents-8.sh`.
Скрипт предлагает множественный выбор из Hermes Agent, OpenClaw, OpenHands, OpenCode, Aider, AutoGPT и Pi Coding Agent. Установщики запускаются от выбранного обычного пользователя, а не напрямую от `root`. Каждый официальный установщик сначала загружается по HTTPS во временный файл и проходит проверку `bash -n`.
AutoGPT — более тяжёлая Docker-платформа: её официальный установщик может запросить `sudo`, установить зависимости Docker и запустить локальные службы. Pi — лёгкий терминальный агент; после установки его можно запустить командой `pi`, а затем выполнить `/login` для подключения провайдера.
API-ключи скрипт не запрашивает и не сохраняет. После установки нужно войти под выбранным пользователем и выполнить показанную команду первоначальной настройки. AI-агенты могут запускать команды, изменять файлы, использовать сеть и устанавливать плагины, поэтому перед подключением рабочих данных необходимо проверить разрешения, песочницу и доступ к секретам.
```bash
curl -fLO https://raw.githubusercontent.com/ochenstarik-ui/lightweight-server/main/ochenstarik-server-ai-agents-8.sh
chmod 700 ochenstarik-server-ai-agents-8.sh
bash -n ochenstarik-server-ai-agents-8.sh
sudo ./ochenstarik-server-ai-agents-8.sh
```
VPN, 3x-ui, WARP, Telegram-уведомления, автоматические расписания резервного копирования и AI-агенты являются необязательными этапами.
## Удаление и повторная установка с начала
Файл: `ochenstarik-server-uninstall.sh`.
Скрипт удаляет управляемые правила UFW, настройки IPv4/IPv6, SSH drop-in, fail2ban-конфигурацию, Telegram-hook, Xray, 3x-ui, WARP, расписания резервного копирования, swap и служебные файлы проекта. Перед изменением SSH открывается порт `22/tcp`. Правило порта, через который работает текущая SSH-сессия, сохраняется, чтобы не потерять доступ.
Удаление администратора и самих архивов резервных копий требует отдельных подтверждений. Обязательные системные пакеты второго этапа и обновления Ubuntu сохраняются, чтобы не потерять доступ к серверу. Языковые пакеты и дополнительные программы удаляются только если первый скрипт зафиксировал, что до его запуска их не было.
```bash
curl -fLO https://raw.githubusercontent.com/ochenstarik-ui/lightweight-server/main/ochenstarik-server-uninstall.sh
chmod 700 ochenstarik-server-uninstall.sh
bash -n ochenstarik-server-uninstall.sh
sudo ./ochenstarik-server-uninstall.sh
```
Не закрывайте текущую SSH-сессию. После очистки сначала проверьте отдельное подключение через порт 22, затем снова запускайте нужные установочные скрипты. Старое правило текущего SSH-порта можно удалить только после успешной проверки нового входа.
## Безопасность
- Всегда проверяйте скачанные скрипты перед запуском от `root`.
- Не публикуйте токены Telegram, пароли, приватные SSH-ключи и ссылки VLESS.
- Не закрывайте действующую SSH-сессию во время переноса SSH на новый порт.
- Перед настройкой на рабочем сервере сделайте резервную копию важных данных.

View file

@ -0,0 +1,33 @@
# Ubuntu Server 快速配置
[English](../../README.md) | [Русский](README.ru.md) | [Español](README.es.md) | [Deutsch](README.de.md) | [Français](README.fr.md) | [Português](README.pt.md) | **中文** | [日本語](README.ja.md) | [العربية](README.ar.md) | [हिन्दी](README.hi.md)
一组模块化 Bash 脚本,用于 Ubuntu Server 的初始配置、安全加固、SSH、VPN、3x-ui、WARP 和备份。
> 如需在一个应用中监控并统一管理多台服务器,请使用 [Server Monitor Manager](https://github.com/ochenstarik-ui/server-monitor-manager)。该项目目前仍处于早期开发阶段。
## 快速安装
```bash
sudo apt-get update
sudo apt-get install -y curl ca-certificates
curl -4 -fLO --retry 5 --retry-delay 10 --connect-timeout 30 \
https://raw.githubusercontent.com/ochenstarik-ui/lightweight-server/main/ochenstarik-server-install.sh
chmod 700 ochenstarik-server-install.sh
sudo ./ochenstarik-server-install.sh
```
安装程序的第一个问题用于选择界面语言,默认是 English。随后八个步骤都可以运行、跳过或用于退出安装程序。
## 安装步骤
1. 时区、终端语言、推荐程序和交换文件。
2. 基础软件包、SSH 端口、IPv4/IPv6 和 UFW。
3. 管理员、SSH 密钥、权限、sudo 和 fail2ban。
4. SSH 登录的 Telegram 通知。
5. 通过 Xray 配置系统 VPN重复运行时可连接或断开。
6. 3x-ui 面板和本地 Cloudflare WARP 代理。
7. 初始快照以及每日、每周或每月备份。
8. 可选 AI 代理Hermes、OpenClaw、OpenHands、OpenCode、Aider、AutoGPT 或 Pi Coding Agent。
更改 SSH 端口时不要关闭当前会话。请先在另一个终端中测试新连接。更多说明请参阅[英文主文档](../../README.md)。

View file

@ -5,28 +5,587 @@ readonly SWAPFILE="/swapfile"
readonly SWAPSIZE="2G"
readonly SWAPPINESS="20"
readonly SYSCTL_FILE="/etc/sysctl.d/60-hermes-swap.conf"
readonly CONFIG_DIR="/etc/ochenstarik-server"
readonly LOCALE_BACKUP="${CONFIG_DIR}/locale-before-selection.conf"
readonly LOCALE_ABSENT_MARKER="${CONFIG_DIR}/locale-before-selection.absent"
readonly LOCALE_PACKAGES="${CONFIG_DIR}/locale-installed-packages.list"
readonly LEGACY_LOCALE_BACKUP="${CONFIG_DIR}/locale-before-russian.conf"
readonly LEGACY_LOCALE_ABSENT_MARKER="${CONFIG_DIR}/locale-before-russian.absent"
readonly LEGACY_LOCALE_PACKAGES="${CONFIG_DIR}/russian-locale-installed-packages.list"
readonly PROGRAM_PACKAGES="${CONFIG_DIR}/step1-installed-packages.list"
declare -a PROGRAM_GROUP_TITLES=(
"Terminal and everyday tools"
"Network diagnostics"
"System and disk monitoring"
"Ubuntu security and maintenance"
"Development and automation"
"Archives, tables, and local data"
"Documents, PDF, and OCR"
"Images, audio, and video"
"Backup and synchronization"
)
declare -a PROGRAM_GROUP_DESCRIPTIONS=(
"git, wget, curl, jq/yq, ripgrep/fd/fzf, lnav, editors, mc, tmux, htop/btop, ncdu, rsync"
"DNS/ping/MTR/traceroute, tcpdump, nmap, netcat, HTTPie, whois, ethtool, socat"
"iotop, sysstat, SMART, sensors, atop, nethogs, vnStat, process tools"
"auditd, AppArmor tools, Lynis, needrestart, automatic updates, debsums, ACL/xattr"
"compiler, ShellCheck, Python pip/venv, pipx"
"7-Zip/ZIP/RAR, SQLite, csvkit, PostgreSQL and Redis clients"
"Poppler, qpdf, Ghostscript, OCRmyPDF/Tesseract, LibreOffice, Pandoc, antiword/catdoc"
"ImageMagick, ExifTool, WebP, FFmpeg, MediaInfo"
"restic, BorgBackup, rclone"
)
readonly -a PROGRAM_GROUP_PACKAGES=(
"git wget curl ca-certificates jq yq ripgrep fd-find fzf lnav tree less nano vim mc tmux screen htop btop ncdu lsof strace rsync"
"dnsutils iputils-ping mtr-tiny traceroute tcpdump nmap netcat-openbsd httpie whois ethtool socat"
"iotop sysstat smartmontools lm-sensors atop nethogs vnstat psmisc"
"auditd apparmor-utils lynis needrestart unattended-upgrades debsums acl attr"
"build-essential shellcheck python3-pip python3-venv pipx"
"p7zip-full unzip zip unrar sqlite3 csvkit postgresql-client redis-tools"
"poppler-utils qpdf ghostscript ocrmypdf tesseract-ocr tesseract-ocr-eng tesseract-ocr-rus libreoffice pandoc antiword catdoc"
"imagemagick libimage-exiftool-perl webp ffmpeg mediainfo"
"restic borgbackup rclone"
)
APT_UPDATED=no
UI_LANG="${OCHENSTARIK_UI_LANG:-en}"
case "$UI_LANG" in
en|ru|es|de|fr|pt|zh|ja|ar|hi) ;;
*) UI_LANG=en ;;
esac
declare -A UI_TEXT=()
load_ui_text() {
UI_TEXT=(
[input_interrupted]="Input was interrupted"
[invalid_number]="Enter a number from %s to %s"
[no_options]="No options are available for: %s"
[select_option]="Enter the option number: "
[select_region]="Select a region:"
[select_timezone]="Select a timezone in %s:"
[timezone_heading]="Select the server timezone (current: %s):"
[other_timezone]="Other timezone (select by region)"
[unknown_timezone]="Unknown timezone: %s"
[timezone_set]="Timezone set to %s"
[terminal_heading]="Select the language for new terminal sessions (current: %s):"
[terminal_keep]="Keep the current terminal language"
[terminal_other]="Other locale from the full list"
[terminal_prompt]="Language number [1 — keep current]: "
[terminal_unchanged]="Terminal language was not changed"
[terminal_full_list]="Select a locale from the full list:"
[locale_package_missing]="Language package is unavailable and will be skipped: %s"
[locale_selected]="Locale %s selected. It will apply at the next login"
[program_heading]="Select program groups to install:"
[program_none]="Do not install additional programs"
[program_example]="Enter numbers separated by spaces, for example: 1 2 4 9."
[program_prompt]="Selection [Enter — install all groups]: "
[program_skipped]="Installation of additional programs was skipped"
[program_bad_number]="Invalid group number: %s"
[program_out_of_range]="Group number is out of range: %s"
[program_selected]="Selected group: %s"
[package_missing]="Package is unavailable in the configured repositories and will be skipped: %s"
[no_packages]="None of the selected packages were found"
[programs_installed]="Selected programs have been installed"
[apt_update]="Updating the package list"
[root_required]="Run this script as root"
[required_missing]="Required command not found: %s"
[swap_configuring]="Configuring %s swap file"
[setup_complete]="Server memory setup is complete"
[next_script]="Now run ochenstarik-server-2.sh as root."
)
case "$UI_LANG" in
ru) UI_TEXT+=(
[input_interrupted]="Ввод прерван" [invalid_number]="Введите номер от %s до %s"
[select_option]="Введите номер варианта: " [select_region]="Выберите регион:"
[select_timezone]="Выберите часовой пояс в регионе %s:"
[timezone_heading]="Выберите часовой пояс сервера (сейчас: %s):"
[other_timezone]="Другой часовой пояс (выбор по региону)"
[unknown_timezone]="Неизвестный часовой пояс: %s" [timezone_set]="Установлен часовой пояс %s"
[terminal_heading]="Выберите язык новых терминальных сессий (сейчас: %s):"
[terminal_keep]="Не менять язык терминала" [terminal_other]="Другая локаль из полного списка"
[terminal_prompt]="Номер языка [1 — не менять]: " [terminal_unchanged]="Язык терминала не изменён"
[terminal_full_list]="Выберите локаль из полного списка:"
[locale_package_missing]="Языковой пакет недоступен и будет пропущен: %s"
[locale_selected]="Выбрана локаль %s. Она применится при следующем входе"
[program_heading]="Выберите наборы программ для установки:"
[program_none]="Не устанавливать дополнительные программы"
[program_example]="Введите номера через пробел, например: 1 2 4 9."
[program_prompt]="Выбор [Enter — установить все наборы]: "
[program_skipped]="Установка дополнительных программ пропущена"
[program_bad_number]="Некорректный номер набора: %s"
[program_out_of_range]="Номер набора вне диапазона: %s"
[program_selected]="Выбран набор: %s"
[package_missing]="Пакет отсутствует в подключённых репозиториях и будет пропущен: %s"
[no_packages]="Ни один выбранный пакет не найден"
[programs_installed]="Выбранные программы установлены" [apt_update]="Обновление списка пакетов"
[root_required]="Запустите этот скрипт от root"
[required_missing]="Не найдена обязательная команда: %s"
[swap_configuring]="Настройка файла подкачки размером %s"
[setup_complete]="Настройка памяти сервера завершена"
[next_script]="Теперь запустите ochenstarik-server-2.sh от root."
) ;;
es) UI_TEXT+=(
[input_interrupted]="Entrada interrumpida" [invalid_number]="Introduzca un número del %s al %s"
[select_option]="Introduzca el número de opción: " [select_region]="Seleccione una región:"
[select_timezone]="Seleccione una zona horaria en %s:"
[timezone_heading]="Seleccione la zona horaria del servidor (actual: %s):"
[other_timezone]="Otra zona horaria (seleccionar por región)" [timezone_set]="Zona horaria configurada: %s"
[terminal_heading]="Seleccione el idioma para nuevas sesiones de terminal (actual: %s):"
[terminal_keep]="Mantener el idioma actual" [terminal_other]="Otra configuración regional de la lista completa"
[terminal_prompt]="Número de idioma [1 — mantener]: " [terminal_unchanged]="El idioma del terminal no se cambió"
[terminal_full_list]="Seleccione una configuración regional de la lista completa:"
[program_heading]="Seleccione los grupos de programas que desea instalar:"
[program_none]="No instalar programas adicionales"
[program_example]="Introduzca números separados por espacios, por ejemplo: 1 2 4 9."
[program_prompt]="Selección [Enter — instalar todos]: " [program_skipped]="Se omitió la instalación de programas adicionales"
[program_bad_number]="Número de grupo no válido: %s" [program_out_of_range]="Número de grupo fuera de rango: %s"
[program_selected]="Grupo seleccionado: %s" [programs_installed]="Los programas seleccionados se instalaron"
[apt_update]="Actualizando la lista de paquetes" [root_required]="Ejecute este script como root"
[swap_configuring]="Configurando un archivo swap de %s" [setup_complete]="Configuración de memoria completada"
[next_script]="Ahora ejecute ochenstarik-server-2.sh como root."
) ;;
de) UI_TEXT+=(
[input_interrupted]="Eingabe wurde unterbrochen" [invalid_number]="Geben Sie eine Zahl von %s bis %s ein"
[select_option]="Optionsnummer eingeben: " [select_region]="Region auswählen:"
[select_timezone]="Zeitzone in %s auswählen:"
[timezone_heading]="Server-Zeitzone auswählen (aktuell: %s):"
[other_timezone]="Andere Zeitzone (nach Region auswählen)" [timezone_set]="Zeitzone auf %s gesetzt"
[terminal_heading]="Sprache für neue Terminalsitzungen auswählen (aktuell: %s):"
[terminal_keep]="Aktuelle Terminalsprache beibehalten" [terminal_other]="Andere Locale aus der vollständigen Liste"
[terminal_prompt]="Sprachnummer [1 — beibehalten]: " [terminal_unchanged]="Terminalsprache wurde nicht geändert"
[terminal_full_list]="Locale aus der vollständigen Liste auswählen:"
[program_heading]="Zu installierende Programmgruppen auswählen:"
[program_none]="Keine zusätzlichen Programme installieren"
[program_example]="Nummern durch Leerzeichen getrennt eingeben, zum Beispiel: 1 2 4 9."
[program_prompt]="Auswahl [Enter — alle installieren]: " [program_skipped]="Installation zusätzlicher Programme übersprungen"
[program_bad_number]="Ungültige Gruppennummer: %s" [program_out_of_range]="Gruppennummer außerhalb des Bereichs: %s"
[program_selected]="Ausgewählte Gruppe: %s" [programs_installed]="Ausgewählte Programme wurden installiert"
[apt_update]="Paketliste wird aktualisiert" [root_required]="Führen Sie dieses Skript als root aus"
[swap_configuring]="%s-Swap-Datei wird eingerichtet" [setup_complete]="Speichereinrichtung abgeschlossen"
[next_script]="Führen Sie jetzt ochenstarik-server-2.sh als root aus."
) ;;
fr) UI_TEXT+=(
[input_interrupted]="Saisie interrompue" [invalid_number]="Saisissez un nombre de %s à %s"
[select_option]="Saisissez le numéro de loption : " [select_region]="Sélectionnez une région :"
[select_timezone]="Sélectionnez un fuseau horaire dans %s :"
[timezone_heading]="Sélectionnez le fuseau horaire du serveur (actuel : %s) :"
[other_timezone]="Autre fuseau horaire (sélection par région)" [timezone_set]="Fuseau horaire défini sur %s"
[terminal_heading]="Sélectionnez la langue des nouvelles sessions (actuelle : %s) :"
[terminal_keep]="Conserver la langue actuelle" [terminal_other]="Autre locale de la liste complète"
[terminal_prompt]="Numéro de langue [1 — conserver] : " [terminal_unchanged]="La langue du terminal na pas été modifiée"
[terminal_full_list]="Sélectionnez une locale dans la liste complète :"
[program_heading]="Sélectionnez les groupes de programmes à installer :"
[program_none]="Ne pas installer de programmes supplémentaires"
[program_example]="Saisissez les numéros séparés par des espaces, par exemple : 1 2 4 9."
[program_prompt]="Sélection [Entrée — tout installer] : " [program_skipped]="Installation des programmes supplémentaires ignorée"
[program_bad_number]="Numéro de groupe incorrect : %s" [program_out_of_range]="Numéro de groupe hors plage : %s"
[program_selected]="Groupe sélectionné : %s" [programs_installed]="Les programmes sélectionnés ont été installés"
[apt_update]="Mise à jour de la liste des paquets" [root_required]="Exécutez ce script en tant que root"
[swap_configuring]="Configuration dun fichier swap de %s" [setup_complete]="Configuration de la mémoire terminée"
[next_script]="Exécutez maintenant ochenstarik-server-2.sh en tant que root."
) ;;
pt) UI_TEXT+=(
[input_interrupted]="Entrada interrompida" [invalid_number]="Digite um número de %s a %s"
[select_option]="Digite o número da opção: " [select_region]="Selecione uma região:"
[select_timezone]="Selecione um fuso horário em %s:"
[timezone_heading]="Selecione o fuso horário do servidor (atual: %s):"
[other_timezone]="Outro fuso horário (selecionar por região)" [timezone_set]="Fuso horário definido como %s"
[terminal_heading]="Selecione o idioma das novas sessões de terminal (atual: %s):"
[terminal_keep]="Manter o idioma atual" [terminal_other]="Outra localidade da lista completa"
[terminal_prompt]="Número do idioma [1 — manter]: " [terminal_unchanged]="O idioma do terminal não foi alterado"
[terminal_full_list]="Selecione uma localidade na lista completa:"
[program_heading]="Selecione os grupos de programas para instalar:"
[program_none]="Não instalar programas adicionais"
[program_example]="Digite números separados por espaços, por exemplo: 1 2 4 9."
[program_prompt]="Seleção [Enter — instalar todos]: " [program_skipped]="Instalação de programas adicionais ignorada"
[program_bad_number]="Número de grupo inválido: %s" [program_out_of_range]="Número de grupo fora do intervalo: %s"
[program_selected]="Grupo selecionado: %s" [programs_installed]="Os programas selecionados foram instalados"
[apt_update]="Atualizando a lista de pacotes" [root_required]="Execute este script como root"
[swap_configuring]="Configurando arquivo swap de %s" [setup_complete]="Configuração de memória concluída"
[next_script]="Agora execute ochenstarik-server-2.sh como root."
) ;;
zh) UI_TEXT+=(
[input_interrupted]="输入已中断" [invalid_number]="请输入 %s 到 %s 之间的数字"
[select_option]="请输入选项编号:" [select_region]="请选择地区:" [select_timezone]="请选择 %s 的时区:"
[timezone_heading]="请选择服务器时区(当前:%s" [other_timezone]="其他时区(按地区选择)" [timezone_set]="时区已设置为 %s"
[terminal_heading]="请选择新终端会话的语言(当前:%s" [terminal_keep]="保留当前终端语言"
[terminal_other]="从完整列表选择其他区域设置" [terminal_prompt]="语言编号 [1 — 保留当前设置]"
[terminal_unchanged]="终端语言未更改" [terminal_full_list]="请从完整列表选择区域设置:"
[program_heading]="请选择要安装的程序组:" [program_none]="不安装其他程序"
[program_example]="请输入用空格分隔的编号例如1 2 4 9。" [program_prompt]="选择 [Enter — 全部安装]"
[program_skipped]="已跳过其他程序的安装" [program_bad_number]="无效的程序组编号:%s"
[program_out_of_range]="程序组编号超出范围:%s" [program_selected]="已选择程序组:%s"
[programs_installed]="已安装所选程序" [apt_update]="正在更新软件包列表" [root_required]="请以 root 身份运行此脚本"
[swap_configuring]="正在配置 %s 交换文件" [setup_complete]="服务器内存设置完成"
[next_script]="现在请以 root 身份运行 ochenstarik-server-2.sh。"
) ;;
ja) UI_TEXT+=(
[input_interrupted]="入力が中断されました" [invalid_number]="%s から %s の番号を入力してください"
[select_option]="オプション番号を入力してください: " [select_region]="地域を選択してください:"
[select_timezone]="%s のタイムゾーンを選択してください:"
[timezone_heading]="サーバーのタイムゾーンを選択してください(現在: %s:"
[other_timezone]="その他のタイムゾーン(地域から選択)" [timezone_set]="タイムゾーンを %s に設定しました"
[terminal_heading]="新しいターミナルセッションの言語を選択してください(現在: %s:"
[terminal_keep]="現在の言語を維持" [terminal_other]="完全なリストから別のロケールを選択"
[terminal_prompt]="言語番号 [1 — 変更しない]: " [terminal_unchanged]="ターミナルの言語は変更されませんでした"
[terminal_full_list]="完全なリストからロケールを選択してください:"
[program_heading]="インストールするプログラムグループを選択してください:"
[program_none]="追加プログラムをインストールしない"
[program_example]="番号を空白で区切って入力してください。例: 1 2 4 9。"
[program_prompt]="選択 [Enter — すべてインストール]: " [program_skipped]="追加プログラムのインストールをスキップしました"
[program_bad_number]="無効なグループ番号: %s" [program_out_of_range]="グループ番号が範囲外です: %s"
[program_selected]="選択したグループ: %s" [programs_installed]="選択したプログラムをインストールしました"
[apt_update]="パッケージリストを更新しています" [root_required]="このスクリプトを root で実行してください"
[swap_configuring]="%s のスワップファイルを設定しています" [setup_complete]="メモリ設定が完了しました"
[next_script]="次に ochenstarik-server-2.sh を root で実行してください。"
) ;;
ar) UI_TEXT+=(
[input_interrupted]="تم إيقاف الإدخال" [invalid_number]="أدخل رقماً من %s إلى %s"
[select_option]="أدخل رقم الخيار: " [select_region]="اختر المنطقة:" [select_timezone]="اختر منطقة زمنية في %s:"
[timezone_heading]="اختر المنطقة الزمنية للخادم (الحالية: %s):" [other_timezone]="منطقة زمنية أخرى (حسب المنطقة)"
[timezone_set]="تم ضبط المنطقة الزمنية على %s" [terminal_heading]="اختر لغة جلسات الطرفية الجديدة (الحالية: %s):"
[terminal_keep]="الاحتفاظ باللغة الحالية" [terminal_other]="إعداد محلي آخر من القائمة الكاملة"
[terminal_prompt]="رقم اللغة [1 — الاحتفاظ]: " [terminal_unchanged]="لم تتغير لغة الطرفية"
[terminal_full_list]="اختر إعداداً محلياً من القائمة الكاملة:"
[program_heading]="اختر مجموعات البرامج المراد تثبيتها:" [program_none]="عدم تثبيت برامج إضافية"
[program_example]="أدخل الأرقام مفصولة بمسافات، مثال: 1 2 4 9." [program_prompt]="الاختيار [Enter — تثبيت الكل]: "
[program_skipped]="تم تخطي تثبيت البرامج الإضافية" [program_bad_number]="رقم مجموعة غير صالح: %s"
[program_out_of_range]="رقم المجموعة خارج النطاق: %s" [program_selected]="المجموعة المحددة: %s"
[programs_installed]="تم تثبيت البرامج المحددة" [apt_update]="جارٍ تحديث قائمة الحزم"
[root_required]="شغّل هذا البرنامج النصي بصلاحية root" [swap_configuring]="جارٍ إعداد ملف swap بحجم %s"
[setup_complete]="اكتمل إعداد ذاكرة الخادم" [next_script]="شغّل الآن ochenstarik-server-2.sh بصلاحية root."
) ;;
hi) UI_TEXT+=(
[input_interrupted]="इनपुट बाधित हुआ" [invalid_number]="%s से %s तक की संख्या दर्ज करें"
[select_option]="विकल्प संख्या दर्ज करें: " [select_region]="क्षेत्र चुनें:" [select_timezone]="%s में समय क्षेत्र चुनें:"
[timezone_heading]="सर्वर का समय क्षेत्र चुनें (वर्तमान: %s):" [other_timezone]="अन्य समय क्षेत्र (क्षेत्र के अनुसार)"
[timezone_set]="समय क्षेत्र %s पर सेट किया गया" [terminal_heading]="नई टर्मिनल सत्रों की भाषा चुनें (वर्तमान: %s):"
[terminal_keep]="वर्तमान भाषा बनाए रखें" [terminal_other]="पूरी सूची से अन्य लोकेल"
[terminal_prompt]="भाषा संख्या [1 — वर्तमान रखें]: " [terminal_unchanged]="टर्मिनल भाषा नहीं बदली गई"
[terminal_full_list]="पूरी सूची से लोकेल चुनें:"
[program_heading]="इंस्टॉल करने के लिए प्रोग्राम समूह चुनें:" [program_none]="अतिरिक्त प्रोग्राम इंस्टॉल न करें"
[program_example]="खाली स्थान से अलग संख्याएँ दर्ज करें, उदाहरण: 1 2 4 9।" [program_prompt]="चयन [Enter — सभी इंस्टॉल करें]: "
[program_skipped]="अतिरिक्त प्रोग्राम की स्थापना छोड़ दी गई" [program_bad_number]="अमान्य समूह संख्या: %s"
[program_out_of_range]="समूह संख्या सीमा से बाहर है: %s" [program_selected]="चयनित समूह: %s"
[programs_installed]="चयनित प्रोग्राम इंस्टॉल हो गए" [apt_update]="पैकेज सूची अपडेट की जा रही है"
[root_required]="इस स्क्रिप्ट को root के रूप में चलाएँ" [swap_configuring]="%s swap फ़ाइल कॉन्फ़िगर की जा रही है"
[setup_complete]="सर्वर मेमोरी सेटअप पूरा हुआ" [next_script]="अब ochenstarik-server-2.sh को root के रूप में चलाएँ।"
) ;;
esac
}
msg() {
local key="$1"
shift
printf "${UI_TEXT[$key]}" "$@"
}
localize_program_catalog() {
case "$UI_LANG" in
ru) PROGRAM_GROUP_TITLES=(
"Терминал и повседневная работа" "Диагностика сети" "Мониторинг системы и дисков"
"Безопасность и обслуживание Ubuntu" "Разработка и автоматизация"
"Архивы, таблицы и локальные данные" "Документы, PDF и OCR"
"Изображения, аудио и видео" "Резервное копирование и синхронизация"
) ;;
es) PROGRAM_GROUP_TITLES=(
"Terminal y herramientas cotidianas" "Diagnóstico de red" "Supervisión del sistema y discos"
"Seguridad y mantenimiento de Ubuntu" "Desarrollo y automatización"
"Archivos, tablas y datos locales" "Documentos, PDF y OCR"
"Imágenes, audio y vídeo" "Copias de seguridad y sincronización"
) ;;
de) PROGRAM_GROUP_TITLES=(
"Terminal und Alltagswerkzeuge" "Netzwerkdiagnose" "System- und Datenträgerüberwachung"
"Ubuntu-Sicherheit und Wartung" "Entwicklung und Automatisierung"
"Archive, Tabellen und lokale Daten" "Dokumente, PDF und OCR"
"Bilder, Audio und Video" "Sicherung und Synchronisierung"
) ;;
fr) PROGRAM_GROUP_TITLES=(
"Terminal et outils quotidiens" "Diagnostic réseau" "Surveillance du système et des disques"
"Sécurité et maintenance dUbuntu" "Développement et automatisation"
"Archives, tableaux et données locales" "Documents, PDF et OCR"
"Images, audio et vidéo" "Sauvegarde et synchronisation"
) ;;
pt) PROGRAM_GROUP_TITLES=(
"Terminal e ferramentas diárias" "Diagnóstico de rede" "Monitoramento do sistema e discos"
"Segurança e manutenção do Ubuntu" "Desenvolvimento e automação"
"Arquivos, tabelas e dados locais" "Documentos, PDF e OCR"
"Imagens, áudio e vídeo" "Backup e sincronização"
) ;;
zh) PROGRAM_GROUP_TITLES=(
"终端与日常工具" "网络诊断" "系统与磁盘监控" "Ubuntu 安全与维护" "开发与自动化"
"压缩包、表格与本地数据" "文档、PDF 与 OCR" "图像、音频与视频" "备份与同步"
) ;;
ja) PROGRAM_GROUP_TITLES=(
"ターミナルと日常ツール" "ネットワーク診断" "システムとディスクの監視"
"Ubuntu のセキュリティと保守" "開発と自動化" "アーカイブ、表、ローカルデータ"
"文書、PDF、OCR" "画像、音声、動画" "バックアップと同期"
) ;;
ar) PROGRAM_GROUP_TITLES=(
"الطرفية والأدوات اليومية" "تشخيص الشبكة" "مراقبة النظام والأقراص"
"أمان Ubuntu وصيانته" "التطوير والأتمتة" "الأرشيفات والجداول والبيانات المحلية"
"المستندات وPDF وOCR" "الصور والصوت والفيديو" "النسخ الاحتياطي والمزامنة"
) ;;
hi) PROGRAM_GROUP_TITLES=(
"टर्मिनल और दैनिक उपकरण" "नेटवर्क निदान" "सिस्टम और डिस्क निगरानी"
"Ubuntu सुरक्षा और रखरखाव" "विकास और स्वचालन" "अभिलेख, तालिकाएँ और स्थानीय डेटा"
"दस्तावेज़, PDF और OCR" "चित्र, ऑडियो और वीडियो" "बैकअप और सिंक्रोनाइज़ेशन"
) ;;
esac
}
# The unified installer passes the selected dialog language through the
# environment. A directly launched step uses English by default.
load_ui_text
localize_program_catalog
log() { printf '[+] %s\n' "$*"; }
warn() { printf '[!] %s\n' "$*" >&2; }
die() { printf '[x] %s\n' "$*" >&2; exit 1; }
package_is_installed() {
dpkg-query -W -f='${Status}' "$1" 2>/dev/null | grep -Fqx 'install ok installed'
}
ensure_package_tools() {
local command_name
for command_name in apt-get apt-cache dpkg-query; do
command -v "$command_name" >/dev/null 2>&1 \
|| die "$(msg required_missing "$command_name")"
done
}
ensure_apt_updated() {
[[ "$APT_UPDATED" == no ]] || return 0
ensure_package_tools
log "$(msg apt_update)"
apt-get update
APT_UPDATED=yes
}
record_new_packages() {
local destination="$1" package_name
shift
install -d -m 700 -o root -g root "$CONFIG_DIR"
[[ ! -L "$destination" ]] || die "Отказ от записи через ссылку: $destination"
touch "$destination"
for package_name in "$@"; do
grep -Fqx -- "$package_name" "$destination" \
|| printf '%s\n' "$package_name" >> "$destination"
done
chown root:root "$destination"
chmod 600 "$destination"
}
migrate_legacy_locale_state() {
local package_name
install -d -m 700 -o root -g root "$CONFIG_DIR"
[[ ! -L "$LOCALE_BACKUP" && ! -L "$LOCALE_ABSENT_MARKER" \
&& ! -L "$LOCALE_PACKAGES" ]] || die "Файл состояния локали не должен быть ссылкой"
if [[ -f "$LEGACY_LOCALE_BACKUP" && ! -L "$LEGACY_LOCALE_BACKUP" \
&& ! -e "$LOCALE_BACKUP" ]]; then
mv -- "$LEGACY_LOCALE_BACKUP" "$LOCALE_BACKUP"
fi
if [[ -f "$LEGACY_LOCALE_ABSENT_MARKER" \
&& ! -L "$LEGACY_LOCALE_ABSENT_MARKER" && ! -e "$LOCALE_ABSENT_MARKER" ]]; then
mv -- "$LEGACY_LOCALE_ABSENT_MARKER" "$LOCALE_ABSENT_MARKER"
fi
if [[ -f "$LEGACY_LOCALE_PACKAGES" && ! -L "$LEGACY_LOCALE_PACKAGES" ]]; then
touch "$LOCALE_PACKAGES"
while IFS= read -r package_name || [[ -n "$package_name" ]]; do
[[ -z "$package_name" ]] || grep -Fqx -- "$package_name" "$LOCALE_PACKAGES" \
|| printf '%s\n' "$package_name" >> "$LOCALE_PACKAGES"
done < "$LEGACY_LOCALE_PACKAGES"
rm -f -- "$LEGACY_LOCALE_PACKAGES"
fi
}
save_original_locale() {
migrate_legacy_locale_state
if [[ ! -e "$LOCALE_BACKUP" && ! -e "$LOCALE_ABSENT_MARKER" ]]; then
if [[ -f /etc/default/locale && ! -L /etc/default/locale ]]; then
cp -a -- /etc/default/locale "$LOCALE_BACKUP"
chmod 600 "$LOCALE_BACKUP"
else
: > "$LOCALE_ABSENT_MARKER"
chmod 600 "$LOCALE_ABSENT_MARKER"
fi
fi
}
choose_terminal_language() {
local answer selected_locale selected_language primary_language package_name index
local current_locale="${LANG:-не задана}"
local -a titles=(
"$(msg terminal_keep)"
"Русский — ru_RU.UTF-8"
"English (US) — en_US.UTF-8"
"Deutsch — de_DE.UTF-8"
"Français — fr_FR.UTF-8"
"Español — es_ES.UTF-8"
"Italiano — it_IT.UTF-8"
"Português (Brasil) — pt_BR.UTF-8"
"Polski — pl_PL.UTF-8"
"Українська — uk_UA.UTF-8"
"Türkçe — tr_TR.UTF-8"
"简体中文 — zh_CN.UTF-8"
"日本語 — ja_JP.UTF-8"
"한국어 — ko_KR.UTF-8"
"$(msg terminal_other)"
)
local -a locales=(
"" ru_RU.UTF-8 en_US.UTF-8 de_DE.UTF-8 fr_FR.UTF-8 es_ES.UTF-8
it_IT.UTF-8 pt_BR.UTF-8 pl_PL.UTF-8 uk_UA.UTF-8 tr_TR.UTF-8
zh_CN.UTF-8 ja_JP.UTF-8 ko_KR.UTF-8 ""
)
local -a language_values=(
"" ru_RU:ru en_US:en de_DE:de fr_FR:fr es_ES:es it_IT:it
pt_BR:pt pl_PL:pl uk_UA:uk tr_TR:tr zh_CN:zh ja_JP:ja ko_KR:ko ""
)
local -a language_packages=(
"" "language-pack-ru manpages-ru" language-pack-en language-pack-de
language-pack-fr language-pack-es language-pack-it language-pack-pt
language-pack-pl language-pack-uk language-pack-tr language-pack-zh-hans
language-pack-ja language-pack-ko ""
)
local -a supported_locales packages=(locales) available_packages=() \
newly_installed=() extra_packages=()
while :; do
printf '\n%s\n' "$(msg terminal_heading "$current_locale")"
for index in "${!titles[@]}"; do
printf ' %d) %s\n' "$((index + 1))" "${titles[index]}"
done
read -rp "$(msg terminal_prompt)" answer || die "$(msg input_interrupted)"
answer="${answer:-1}"
[[ "$answer" =~ ^[0-9]+$ ]] && ((answer >= 1 && answer <= ${#titles[@]})) && break
warn "$(msg invalid_number 1 "${#titles[@]}")"
done
((answer == 1)) && { log "$(msg terminal_unchanged)"; return 0; }
ensure_apt_updated
save_original_locale
if ((answer == ${#titles[@]})); then
package_is_installed locales || newly_installed+=(locales)
apt-get install -y locales
mapfile -t supported_locales < <(awk '{print $1}' /usr/share/i18n/SUPPORTED | sort -u)
choose_numbered_option "$(msg terminal_full_list)" selected_locale \
"${supported_locales[@]}"
primary_language="${selected_locale%%_*}"
selected_language="${selected_locale%%.*}:${primary_language}"
packages+=("language-pack-${primary_language}")
else
selected_locale="${locales[answer - 1]}"
selected_language="${language_values[answer - 1]}"
read -r -a extra_packages <<< "${language_packages[answer - 1]}"
packages+=("${extra_packages[@]}")
fi
for package_name in "${packages[@]}"; do
[[ -n "$package_name" ]] || continue
if apt-cache show "$package_name" >/dev/null 2>&1; then
available_packages+=("$package_name")
package_is_installed "$package_name" || newly_installed+=("$package_name")
else
warn "$(msg locale_package_missing "$package_name")"
fi
done
export DEBIAN_FRONTEND=noninteractive
((${#available_packages[@]} == 0)) || apt-get install -y "${available_packages[@]}"
locale-gen "$selected_locale"
update-locale LANG="$selected_locale" LANGUAGE="$selected_language" \
LC_MESSAGES="$selected_locale"
record_new_packages "$LOCALE_PACKAGES" "${newly_installed[@]}"
log "$(msg locale_selected "$selected_locale")"
}
choose_and_install_programs() {
local answer token group_index package_name
local -a selected_groups=() group_packages=() requested=() available=() newly_installed=()
local -A selected=() seen=()
printf '\n%s\n' "$(msg program_heading)"
for group_index in "${!PROGRAM_GROUP_TITLES[@]}"; do
printf ' %d) %s\n %s\n' "$((group_index + 1))" \
"${PROGRAM_GROUP_TITLES[group_index]}" "${PROGRAM_GROUP_DESCRIPTIONS[group_index]}"
done
printf ' 0) %s\n' "$(msg program_none)"
printf '\n%s\n' "$(msg program_example)"
read -rp "$(msg program_prompt)" answer || die "$(msg input_interrupted)"
answer="${answer//,/ }"
if [[ -z "${answer//[[:space:]]/}" ]]; then
for group_index in "${!PROGRAM_GROUP_TITLES[@]}"; do
selected_groups+=("$group_index")
done
elif [[ "$answer" =~ ^[[:space:]]*0[[:space:]]*$ ]]; then
log "$(msg program_skipped)"
return 0
else
for token in $answer; do
[[ "$token" =~ ^[0-9]+$ ]] \
|| die "$(msg program_bad_number "$token")"
((token >= 1 && token <= ${#PROGRAM_GROUP_TITLES[@]})) \
|| die "$(msg program_out_of_range "$token")"
group_index="$((token - 1))"
[[ -n "${selected[$group_index]:-}" ]] || selected_groups+=("$group_index")
selected[$group_index]=yes
done
fi
ensure_apt_updated
for group_index in "${selected_groups[@]}"; do
log "$(msg program_selected "${PROGRAM_GROUP_TITLES[group_index]}")"
read -r -a group_packages <<< "${PROGRAM_GROUP_PACKAGES[group_index]}"
for package_name in "${group_packages[@]}"; do
[[ -n "${seen[$package_name]:-}" ]] && continue
seen[$package_name]=yes
requested+=("$package_name")
done
done
for package_name in "${requested[@]}"; do
if apt-cache show "$package_name" >/dev/null 2>&1; then
available+=("$package_name")
package_is_installed "$package_name" || newly_installed+=("$package_name")
else
warn "$(msg package_missing "$package_name")"
fi
done
((${#available[@]} > 0)) || die "$(msg no_packages)"
export DEBIAN_FRONTEND=noninteractive
apt-get install -y "${available[@]}"
record_new_packages "$PROGRAM_PACKAGES" "${newly_installed[@]}"
log "$(msg programs_installed)"
}
choose_numbered_option() {
local prompt="$1" result_variable="$2" answer index
shift 2
local -a options=("$@")
((${#options[@]} > 0)) || die "No options are available for: ${prompt}"
((${#options[@]} > 0)) || die "$(msg no_options "$prompt")"
while :; do
printf '\n%s\n' "$prompt"
for index in "${!options[@]}"; do
printf ' %d) %s\n' "$((index + 1))" "${options[index]}"
done
read -rp "Enter the option number: " answer || die "Input was interrupted"
read -rp "$(msg select_option)" answer || die "$(msg input_interrupted)"
if [[ "$answer" =~ ^[0-9]+$ ]] && ((answer >= 1 && answer <= ${#options[@]})); then
printf -v "$result_variable" '%s' "${options[answer - 1]}"
return 0
fi
warn "Enter a number from 1 to ${#options[@]}"
warn "$(msg invalid_number 1 "${#options[@]}")"
done
}
@ -35,15 +594,15 @@ choose_timezone_from_full_list() {
local -a regions region_timezones
mapfile -t regions < <(timedatectl list-timezones | awk -F/ 'NF > 1 && !seen[$1]++ { print $1 }')
choose_numbered_option "Select a region:" region "${regions[@]}"
choose_numbered_option "$(msg select_region)" region "${regions[@]}"
mapfile -t region_timezones < <(timedatectl list-timezones | awk -F/ -v selected_region="$region" '$1 == selected_region')
choose_numbered_option "Select a timezone in ${region}:" selected_timezone "${region_timezones[@]}"
choose_numbered_option "$(msg select_timezone "$region")" selected_timezone "${region_timezones[@]}"
printf -v "$result_variable" '%s' "$selected_timezone"
}
choose_timezone() {
local current_timezone timezone candidate
local full_list_option="Other timezone (select by region)"
local full_list_option="$(msg other_timezone)"
local -a timezone_options=()
current_timezone="$(timedatectl show --property=Timezone --value 2>/dev/null || true)"
@ -70,24 +629,26 @@ choose_timezone() {
done
timezone_options+=("$full_list_option")
choose_numbered_option "Select the server timezone (current: ${current_timezone}):" timezone "${timezone_options[@]}"
choose_numbered_option "$(msg timezone_heading "$current_timezone")" timezone "${timezone_options[@]}"
if [[ "$timezone" == "$full_list_option" ]]; then
choose_timezone_from_full_list timezone
fi
timedatectl list-timezones | grep -Fqx -- "$timezone" || die "Unknown timezone: $timezone"
timedatectl list-timezones | grep -Fqx -- "$timezone" || die "$(msg unknown_timezone "$timezone")"
timedatectl set-timezone "$timezone"
log "Timezone set to ${timezone}"
log "$(msg timezone_set "$timezone")"
}
[[ "$EUID" -eq 0 ]] || die "Run this script as root"
for command_name in awk blkid fallocate grep mkswap swapon sysctl timedatectl; do
command -v "$command_name" >/dev/null 2>&1 || die "Required command not found: $command_name"
command -v "$command_name" >/dev/null 2>&1 || die "$(msg required_missing "$command_name")"
done
choose_timezone
choose_terminal_language
choose_and_install_programs
log "Configuring ${SWAPSIZE} swap file"
log "$(msg swap_configuring "$SWAPSIZE")"
if [[ ! -e "$SWAPFILE" ]]; then
fallocate -l "$SWAPSIZE" "$SWAPFILE"
chmod 600 "$SWAPFILE"
@ -116,7 +677,7 @@ EOF
chmod 644 "$SYSCTL_FILE"
sysctl -p "$SYSCTL_FILE" >/dev/null
log "Server memory setup is complete"
log "$(msg setup_complete)"
printf 'Timezone: %s\n' "$(timedatectl show --property=Timezone --value)"
swapon --show
printf '\nNow run ochenstarik-server-2.sh as root.\n'
printf '\n%s\n' "$(msg next_script)"

34
ochenstarik-server-2.sh Normal file → Executable file
View file

@ -87,7 +87,7 @@ choose_ip_mode() {
esac
printf '\nВыберите семейства IP для входящих подключений:\n'
printf ' 1) Только IPv4 — IPv6 отключается системно и в UFW\n'
printf ' 1) Только IPv4 — входящие IPv6-порты закрываются через UFW\n'
printf ' 2) Только IPv6 — открытые входящие порты доступны лишь по IPv6\n'
printf ' 3) IPv4 + IPv6\n'
while :; do
@ -206,19 +206,6 @@ set_ufw_ipv6_setting() {
fi
}
disable_ipv6_systemwide() {
cat > "$IPV6_SYSCTL_FILE" <<'EOF'
# Managed by ochenstarik-server-2.sh
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 1
EOF
chmod 644 "$IPV6_SYSCTL_FILE"
sysctl -p "$IPV6_SYSCTL_FILE" >/dev/null
[[ "$(cat /proc/sys/net/ipv6/conf/all/disable_ipv6 2>/dev/null || true)" == 1 ]] \
|| die "Не удалось отключить IPv6"
}
enable_ipv6_systemwide() {
local sysctl_path
local -a sysctl_paths=(/proc/sys/net/ipv6/conf/*/disable_ipv6)
@ -230,6 +217,11 @@ enable_ipv6_systemwide() {
done
}
restore_ipv6_stack_if_managed() {
[[ -e "$IPV6_SYSCTL_FILE" ]] || return 0
enable_ipv6_systemwide
}
check_session_safety() {
local client_ip client_port server_ip server_port
@ -284,9 +276,10 @@ apply_ip_mode_and_firewall() {
check_session_safety
if [[ "$IP_MODE" == ipv4 ]]; then
restore_ipv6_stack_if_managed
set_ufw_ipv6_setting yes
ufw reload >/dev/null 2>&1 || true
delete_managed_ufw_rules
set_ufw_ipv6_setting no
disable_ipv6_systemwide
else
enable_ipv6_systemwide
[[ "$IP_MODE" != ipv6 ]] || verify_ipv6_connectivity
@ -326,14 +319,9 @@ if [[ "$ACTION" == install ]]; then
apt-get update
apt-get upgrade -y
log "Устанавливаю серверные пакеты и средства обработки документов"
log "Устанавливаю обязательные пакеты SSH, брандмауэра и защиты"
apt-get install -y \
sudo ufw fail2ban curl ca-certificates openssl openssh-server logrotate iproute2 \
poppler-utils qpdf ghostscript ocrmypdf \
tesseract-ocr tesseract-ocr-eng tesseract-ocr-rus \
libreoffice pandoc antiword catdoc imagemagick libimage-exiftool-perl webp \
ffmpeg mediainfo p7zip-full unzip zip unrar jq yq csvkit sqlite3 \
python3-pip python3-venv mc
sudo ufw fail2ban curl ca-certificates openssl openssh-server logrotate iproute2
for command_name in ip sed sysctl ufw; do
require_command "$command_name"

View file

@ -0,0 +1,239 @@
#!/usr/bin/env bash
set -Eeuo pipefail
readonly CONFIG_DIR="/etc/ochenstarik-server"
readonly AGENT_CONFIG="${CONFIG_DIR}/ai-agents.conf"
readonly STEP3_ENV="/root/setup-data/env.txt"
readonly -a AGENT_IDS=(hermes openclaw openhands opencode aider autogpt pi)
readonly -a AGENT_NAMES=(
"Hermes Agent"
"OpenClaw"
"OpenHands"
"OpenCode"
"Aider"
"AutoGPT"
"Pi Coding Agent"
)
readonly -a AGENT_DESCRIPTIONS=(
"Personal self-improving agent with memory, skills, schedules, and messaging gateways"
"Personal assistant with persistent memory and Telegram, WhatsApp, Discord, Slack, and other channels"
"Software-development agent with terminal, headless, web, and IDE modes"
"Terminal coding agent with multiple model providers and parallel sessions"
"Git-oriented coding agent for editing and reviewing an existing repository"
"Docker-based platform for building, deploying, and running autonomous agents; may request sudo"
"Minimal and extensible terminal coding agent with skills, extensions, and multiple providers"
)
readonly -a AGENT_INSTALL_URLS=(
"https://hermes-agent.nousresearch.com/install.sh"
"https://openclaw.ai/install.sh"
"https://install.openhands.dev/install.sh"
"https://opencode.ai/install"
"https://aider.chat/install.sh"
"https://setup.agpt.co/install.sh"
"https://pi.dev/install.sh"
)
readonly -a AGENT_COMMANDS=(hermes openclaw openhands opencode aider docker pi)
readonly -a AGENT_SETUP_HINTS=(
"hermes setup"
"openclaw onboard --install-daemon"
"openhands"
"opencode"
"aider"
"Open the local AutoGPT address printed by the official installer"
"pi, then use /login to connect a provider"
)
TEMP_INSTALLER=""
log() { printf '[+] %s\n' "$*"; }
warn() { printf '[!] %s\n' "$*" >&2; }
die() { printf '[x] %s\n' "$*" >&2; exit 1; }
cleanup() {
if [[ -n "$TEMP_INSTALLER" && "$TEMP_INSTALLER" == /tmp/ochenstarik-ai-installer.* \
&& -f "$TEMP_INSTALLER" && ! -L "$TEMP_INSTALLER" ]]; then
rm -f -- "$TEMP_INSTALLER"
fi
}
trap cleanup EXIT
require_command() {
command -v "$1" >/dev/null 2>&1 || die "Required command not found: $1"
}
default_target_user() {
local username=""
if [[ -f "$STEP3_ENV" && ! -L "$STEP3_ENV" ]]; then
username="$(awk -F= '$1 == "NEW_USERNAME" && !found { sub(/\r$/, "", $2); print $2; found=1 }' "$STEP3_ENV")"
if [[ "$username" =~ ^[a-z_][a-z0-9_-]{0,31}$ ]] && id "$username" >/dev/null 2>&1; then
printf '%s' "$username"
return 0
fi
fi
getent passwd | awk -F: '$3 >= 1000 && $3 != 65534 && $1 != "nobody" && !found { print $1; found=1 }'
}
select_target_user() {
local default_user username uid home owner
default_user="$(default_target_user)"
printf '\nAI agents must be installed for a regular user, never for root.\n'
while :; do
if [[ -n "$default_user" ]]; then
read -rp "Installation user [${default_user}]: " username || die "Input was interrupted"
username="${username:-$default_user}"
else
read -rp 'Installation user: ' username || die "Input was interrupted"
fi
[[ "$username" =~ ^[a-z_][a-z0-9_-]{0,31}$ ]] || { warn "Invalid username"; continue; }
[[ "$username" != root ]] || { warn "Root cannot be used for AI agents"; continue; }
id "$username" >/dev/null 2>&1 || { warn "User does not exist: $username"; continue; }
uid="$(id -u "$username")"
((uid >= 1000 && uid != 65534)) || { warn "System accounts are not allowed"; continue; }
home="$(getent passwd "$username" | cut -d: -f6)"
[[ -n "$home" && "$home" == /* && -d "$home" && ! -L "$home" ]] \
|| { warn "A safe home directory was not found for $username"; continue; }
owner="$(stat -c %U "$home")"
[[ "$owner" == "$username" ]] || { warn "$home must be owned by $username"; continue; }
TARGET_USER="$username"
TARGET_HOME="$home"
TARGET_GROUP="$(id -gn "$username")"
return 0
done
}
select_agents() {
local input token index
local -A seen=()
SELECTED_INDEXES=()
printf '\nSelect one or more AI agents to install:\n'
for index in "${!AGENT_IDS[@]}"; do
printf ' %d) %s\n %s\n Official installer: %s\n' \
"$((index + 1))" "${AGENT_NAMES[index]}" "${AGENT_DESCRIPTIONS[index]}" \
"${AGENT_INSTALL_URLS[index]}"
done
printf ' 0) Do not install AI agents\n'
printf '\nEnter numbers separated by spaces, for example: 1 2 4.\n'
while :; do
read -rp 'Selection [0]: ' input || die "Input was interrupted"
input="${input//,/ }"
input="${input:-0}"
if [[ "$input" =~ ^[[:space:]]*0[[:space:]]*$ ]]; then
return 0
fi
SELECTED_INDEXES=()
seen=()
for token in $input; do
if [[ ! "$token" =~ ^[1-7]$ ]]; then
warn "Enter agent numbers from 1 to 7, or 0 to skip"
SELECTED_INDEXES=()
break
fi
index="$((token - 1))"
if [[ -z "${seen[$index]:-}" ]]; then
SELECTED_INDEXES+=("$index")
seen["$index"]=1
fi
done
((${#SELECTED_INDEXES[@]} > 0)) && return 0
done
}
install_agent() {
local index="$1" name url command_name path_value
name="${AGENT_NAMES[index]}"
url="${AGENT_INSTALL_URLS[index]}"
command_name="${AGENT_COMMANDS[index]}"
path_value="${TARGET_HOME}/.local/bin:${TARGET_HOME}/.opencode/bin:${TARGET_HOME}/.hermes/bin:${TARGET_HOME}/.local/share/pnpm:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
log "Downloading the official ${name} installer"
TEMP_INSTALLER="$(mktemp /tmp/ochenstarik-ai-installer.XXXXXX)"
chmod 600 "$TEMP_INSTALLER"
curl -fL --retry 5 --retry-delay 5 --connect-timeout 30 \
--proto '=https' --tlsv1.2 "$url" -o "$TEMP_INSTALLER"
[[ -s "$TEMP_INSTALLER" && ! -L "$TEMP_INSTALLER" ]] \
|| die "The downloaded ${name} installer is empty or unsafe"
bash -n "$TEMP_INSTALLER" || die "The official ${name} installer failed Bash syntax validation"
# Keep the validated file root-owned until execution so the target account
# cannot replace it between validation and launch.
chown root:root "$TEMP_INSTALLER"
chmod 555 "$TEMP_INSTALLER"
log "Installing ${name} for ${TARGET_USER}"
runuser -u "$TARGET_USER" -- env \
HOME="$TARGET_HOME" USER="$TARGET_USER" LOGNAME="$TARGET_USER" SHELL=/bin/bash PATH="$path_value" \
bash -c 'cd "$HOME" && exec bash "$1"' ochenstarik-agent-installer "$TEMP_INSTALLER"
rm -f -- "$TEMP_INSTALLER"
TEMP_INSTALLER=""
if runuser -u "$TARGET_USER" -- env HOME="$TARGET_HOME" PATH="$path_value" \
bash -lc "command -v '$command_name' >/dev/null 2>&1"; then
log "${name} command is available: ${command_name}"
else
warn "${name} installer completed, but ${command_name} is not visible in a new login shell yet"
warn "Log in again or reload the user's shell configuration before running it"
fi
}
write_state() {
local ids="" index
install -d -m 700 -o root -g root "$CONFIG_DIR"
[[ ! -L "$AGENT_CONFIG" ]] || die "$AGENT_CONFIG must not be a symbolic link"
for index in "${SELECTED_INDEXES[@]}"; do
ids+="${ids:+ }${AGENT_IDS[index]}"
done
umask 077
printf 'TARGET_USER=%s\nAGENTS=%s\n' "$TARGET_USER" "$ids" > "$AGENT_CONFIG"
chown root:root "$AGENT_CONFIG"
chmod 600 "$AGENT_CONFIG"
}
[[ "$EUID" -eq 0 ]] || die "Run this script as root"
require_command awk
require_command apt-get
require_command bash
require_command getent
require_command runuser
require_command stat
((${#AGENT_IDS[@]} == 7 && ${#AGENT_NAMES[@]} == 7 \
&& ${#AGENT_DESCRIPTIONS[@]} == 7 && ${#AGENT_INSTALL_URLS[@]} == 7 \
&& ${#AGENT_COMMANDS[@]} == 7 && ${#AGENT_SETUP_HINTS[@]} == 7)) \
|| die "Invalid AI agent catalog"
select_target_user
select_agents
if ((${#SELECTED_INDEXES[@]} == 0)); then
log "AI agent installation was skipped"
exit 0
fi
export DEBIAN_FRONTEND=noninteractive
apt-get update
apt-get install -y curl ca-certificates git
require_command curl
printf '\nThe selected agents can execute commands, access files, and use network services.\n'
printf 'Review permissions, tools, plugins, mounted directories, and API-key access during onboarding.\n'
read -rp "Type INSTALL to continue for user ${TARGET_USER}: " confirmation || die "Input was interrupted"
[[ "$confirmation" == INSTALL ]] || die "AI agent installation was cancelled"
for selected_index in "${SELECTED_INDEXES[@]}"; do
install_agent "$selected_index"
done
write_state
printf '\nInstalled AI agents for %s:\n' "$TARGET_USER"
for selected_index in "${SELECTED_INDEXES[@]}"; do
printf ' - %s\n Next step: %s\n' \
"${AGENT_NAMES[selected_index]}" "${AGENT_SETUP_HINTS[selected_index]}"
done
printf '\nRun the setup commands while logged in as %s, not as root.\n' "$TARGET_USER"
printf 'API keys are managed by each agent and were not requested or stored by this script.\n'

View file

@ -0,0 +1,323 @@
#!/usr/bin/env bash
set -Eeuo pipefail
readonly REPO_RAW_BASE="https://raw.githubusercontent.com/ochenstarik-ui/lightweight-server/main"
readonly SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd -P)"
declare -a STEP_FILES=(
"ochenstarik-server-1.sh"
"ochenstarik-server-2.sh"
"ochenstarik-server-user-3.sh"
"ochenstarik-server-tg-4.sh"
"ochenstarik-server-vpn-5.sh"
"ochenstarik-server-panel-warp-6.sh"
"ochenstarik-server-backup-7.sh"
"ochenstarik-server-ai-agents-8.sh"
)
declare -a STEP_TITLES=(
"Timezone, terminal language, programs, and swap"
"Base packages, SSH port, IPv4/IPv6, and UFW"
"Administrator, SSH migration, and fail2ban"
"Telegram notifications for SSH logins"
"System VPN through Xray"
"3x-ui panel and Cloudflare WARP"
"Initial snapshot and backup schedules"
"Optional AI agents for a regular user"
)
TEMP_FILE=""
UI_LANG=en
declare -A MASTER_TEXT=()
load_master_text() {
MASTER_TEXT=(
[intro_title]="Unified lightweight-server installer"
[intro_body]="Steps are shown in order. Each step can be installed, skipped, or used to exit the installer. Keep the current SSH session open while changing the SSH port and test the new login in another terminal."
[step]="Step %s of %s: %s"
[install]="Install / run this step"
[skip]="Skip and continue to the next step"
[finish]="Exit the installer"
[action]="Select an action: "
[invalid]="Enter 1, 2, or 3"
[interrupted]="Input was interrupted"
[failed]="The step failed."
[retry]="Run this step again"
[summary]="Installer completed."
[completed]="Steps completed successfully: %s"
[skipped]="Steps skipped: %s"
[rerun]="You can run the installer again; every step supports reconfiguration."
[reset]="For a complete reset, use ochenstarik-server-uninstall.sh."
[launch]="Running step %s: %s"
[success]="Step %s completed successfully"
[error]="Step %s returned an error"
)
case "$UI_LANG" in
ru) MASTER_TEXT+=(
[intro_title]="Единый мастер установки lightweight-server"
[intro_body]="Этапы будут показаны по порядку. Каждый можно установить, пропустить или завершить мастер. Не закрывайте текущую SSH-сессию при изменении порта и проверьте новый вход во втором терминале."
[step]="Этап %s из %s: %s" [install]="Установить / запустить этот этап"
[skip]="Пропустить и перейти к следующему" [finish]="Завершить мастер"
[action]="Выберите действие: " [invalid]="Введите 1, 2 или 3" [interrupted]="Ввод прерван"
[failed]="Этап завершился с ошибкой." [retry]="Запустить этот этап повторно"
[summary]="Мастер установки завершён." [completed]="Успешно выполнено этапов: %s"
[skipped]="Пропущено этапов: %s"
[rerun]="Мастер можно запустить повторно: каждый этап поддерживает повторную настройку."
[reset]="Для полного сброса используйте ochenstarik-server-uninstall.sh."
[launch]="Запуск этапа %s: %s" [success]="Этап %s успешно завершён" [error]="Этап %s вернул ошибку"
) ;;
es) MASTER_TEXT+=(
[intro_title]="Instalador unificado de lightweight-server"
[intro_body]="Los pasos se muestran en orden. Puede instalar, omitir o salir en cada paso. Mantenga abierta la sesión SSH actual al cambiar el puerto y pruebe el nuevo acceso en otro terminal."
[step]="Paso %s de %s: %s" [install]="Instalar / ejecutar este paso" [skip]="Omitir y continuar"
[finish]="Salir del instalador" [action]="Seleccione una acción: " [invalid]="Introduzca 1, 2 o 3"
[interrupted]="Entrada interrumpida" [failed]="El paso terminó con un error." [retry]="Ejecutar este paso de nuevo"
[summary]="Instalación finalizada." [completed]="Pasos completados: %s" [skipped]="Pasos omitidos: %s"
[rerun]="Puede ejecutar de nuevo el instalador; cada paso admite reconfiguración."
[reset]="Para un reinicio completo, use ochenstarik-server-uninstall.sh."
[launch]="Ejecutando el paso %s: %s" [success]="Paso %s completado" [error]="El paso %s devolvió un error"
) ;;
de) MASTER_TEXT+=(
[intro_title]="Einheitlicher lightweight-server-Installer"
[intro_body]="Die Schritte werden nacheinander angezeigt. Jeder Schritt kann installiert, übersprungen oder zum Beenden verwendet werden. Lassen Sie beim Ändern des SSH-Ports die aktuelle Sitzung geöffnet und testen Sie die neue Anmeldung in einem zweiten Terminal."
[step]="Schritt %s von %s: %s" [install]="Diesen Schritt installieren / ausführen" [skip]="Überspringen und fortfahren"
[finish]="Installer beenden" [action]="Aktion auswählen: " [invalid]="Geben Sie 1, 2 oder 3 ein"
[interrupted]="Eingabe unterbrochen" [failed]="Der Schritt ist fehlgeschlagen." [retry]="Diesen Schritt erneut ausführen"
[summary]="Installation abgeschlossen." [completed]="Erfolgreiche Schritte: %s" [skipped]="Übersprungene Schritte: %s"
[rerun]="Der Installer kann erneut ausgeführt werden; jeder Schritt unterstützt eine Neukonfiguration."
[reset]="Für ein vollständiges Zurücksetzen verwenden Sie ochenstarik-server-uninstall.sh."
[launch]="Schritt %s wird ausgeführt: %s" [success]="Schritt %s erfolgreich abgeschlossen" [error]="Schritt %s meldete einen Fehler"
) ;;
fr) MASTER_TEXT+=(
[intro_title]="Programme dinstallation unifié lightweight-server"
[intro_body]="Les étapes sont présentées dans lordre. Chacune peut être installée, ignorée ou utilisée pour quitter. Gardez la session SSH actuelle ouverte pendant le changement de port et testez la nouvelle connexion dans un autre terminal."
[step]="Étape %s sur %s : %s" [install]="Installer / exécuter cette étape" [skip]="Ignorer et continuer"
[finish]="Quitter linstallation" [action]="Sélectionnez une action : " [invalid]="Saisissez 1, 2 ou 3"
[interrupted]="Saisie interrompue" [failed]="Létape a échoué." [retry]="Relancer cette étape"
[summary]="Installation terminée." [completed]="Étapes réussies : %s" [skipped]="Étapes ignorées : %s"
[rerun]="Vous pouvez relancer linstallation ; chaque étape accepte une reconfiguration."
[reset]="Pour une réinitialisation complète, utilisez ochenstarik-server-uninstall.sh."
[launch]="Exécution de létape %s : %s" [success]="Étape %s réussie" [error]="Létape %s a renvoyé une erreur"
) ;;
pt) MASTER_TEXT+=(
[intro_title]="Instalador unificado do lightweight-server"
[intro_body]="As etapas são mostradas em ordem. Cada etapa pode ser instalada, ignorada ou usada para sair. Mantenha a sessão SSH atual aberta ao alterar a porta e teste o novo acesso em outro terminal."
[step]="Etapa %s de %s: %s" [install]="Instalar / executar esta etapa" [skip]="Ignorar e continuar"
[finish]="Sair do instalador" [action]="Selecione uma ação: " [invalid]="Digite 1, 2 ou 3"
[interrupted]="Entrada interrompida" [failed]="A etapa falhou." [retry]="Executar esta etapa novamente"
[summary]="Instalação concluída." [completed]="Etapas concluídas: %s" [skipped]="Etapas ignoradas: %s"
[rerun]="O instalador pode ser executado novamente; todas as etapas aceitam reconfiguração."
[reset]="Para redefinir tudo, use ochenstarik-server-uninstall.sh."
[launch]="Executando a etapa %s: %s" [success]="Etapa %s concluída" [error]="A etapa %s retornou um erro"
) ;;
zh) MASTER_TEXT+=(
[intro_title]="lightweight-server 统一安装程序" [intro_body]="各步骤按顺序显示。每一步都可以安装、跳过或退出。更改 SSH 端口时请保持当前会话,并在另一个终端测试新连接。"
[step]="第 %s/%s 步:%s" [install]="安装 / 运行此步骤" [skip]="跳过并继续" [finish]="退出安装程序"
[action]="请选择操作:" [invalid]="请输入 1、2 或 3" [interrupted]="输入已中断"
[failed]="此步骤执行失败。" [retry]="重新运行此步骤" [summary]="安装程序已完成。"
[completed]="成功完成的步骤:%s" [skipped]="跳过的步骤:%s" [rerun]="可以再次运行安装程序;每个步骤都支持重新配置。"
[reset]="如需完全重置,请使用 ochenstarik-server-uninstall.sh。" [launch]="正在运行步骤 %s%s"
[success]="步骤 %s 已成功完成" [error]="步骤 %s 返回错误"
) ;;
ja) MASTER_TEXT+=(
[intro_title]="lightweight-server 統合インストーラー" [intro_body]="手順は順番に表示されます。各手順は実行、スキップ、または終了できます。SSH ポートの変更中は現在のセッションを維持し、別のターミナルで新しい接続を確認してください。"
[step]="ステップ %s/%s: %s" [install]="このステップをインストール / 実行" [skip]="スキップして次へ"
[finish]="インストーラーを終了" [action]="操作を選択してください: " [invalid]="1、2、3 のいずれかを入力してください"
[interrupted]="入力が中断されました" [failed]="ステップが失敗しました。" [retry]="このステップを再実行"
[summary]="インストールが完了しました。" [completed]="成功したステップ: %s" [skipped]="スキップしたステップ: %s"
[rerun]="インストーラーは再実行でき、各ステップを再設定できます。" [reset]="完全にリセットするには ochenstarik-server-uninstall.sh を使用してください。"
[launch]="ステップ %s を実行中: %s" [success]="ステップ %s が完了しました" [error]="ステップ %s でエラーが発生しました"
) ;;
ar) MASTER_TEXT+=(
[intro_title]="مثبت lightweight-server الموحد" [intro_body]="تظهر المراحل بالترتيب. يمكن تثبيت كل مرحلة أو تخطيها أو الخروج. أبقِ جلسة SSH الحالية مفتوحة عند تغيير المنفذ واختبر الاتصال الجديد في طرفية أخرى."
[step]="المرحلة %s من %s: %s" [install]="تثبيت / تشغيل هذه المرحلة" [skip]="تخطي والمتابعة"
[finish]="إنهاء المثبت" [action]="اختر إجراءً: " [invalid]="أدخل 1 أو 2 أو 3" [interrupted]="تم إيقاف الإدخال"
[failed]="فشلت المرحلة." [retry]="تشغيل هذه المرحلة مرة أخرى" [summary]="اكتمل المثبت."
[completed]="المراحل المكتملة: %s" [skipped]="المراحل المتخطاة: %s" [rerun]="يمكن تشغيل المثبت مرة أخرى؛ كل مرحلة تدعم إعادة الإعداد."
[reset]="لإعادة الضبط الكامل استخدم ochenstarik-server-uninstall.sh." [launch]="تشغيل المرحلة %s: %s"
[success]="اكتملت المرحلة %s" [error]="أعادت المرحلة %s خطأ"
) ;;
hi) MASTER_TEXT+=(
[intro_title]="एकीकृत lightweight-server इंस्टॉलर" [intro_body]="चरण क्रम से दिखाए जाते हैं। प्रत्येक चरण को चलाया, छोड़ा या इंस्टॉलर बंद किया जा सकता है। SSH पोर्ट बदलते समय वर्तमान सत्र खुला रखें और दूसरे टर्मिनल में नया लॉगिन जाँचें।"
[step]="चरण %s/%s: %s" [install]="यह चरण इंस्टॉल / चलाएँ" [skip]="छोड़ें और आगे बढ़ें"
[finish]="इंस्टॉलर बंद करें" [action]="कार्रवाई चुनें: " [invalid]="1, 2 या 3 दर्ज करें" [interrupted]="इनपुट बाधित हुआ"
[failed]="चरण विफल हुआ।" [retry]="यह चरण फिर चलाएँ" [summary]="इंस्टॉलर पूरा हुआ।"
[completed]="सफल चरण: %s" [skipped]="छोड़े गए चरण: %s" [rerun]="इंस्टॉलर फिर चलाया जा सकता है; हर चरण पुनः कॉन्फ़िगर हो सकता है।"
[reset]="पूर्ण रीसेट के लिए ochenstarik-server-uninstall.sh का उपयोग करें।" [launch]="चरण %s चल रहा है: %s"
[success]="चरण %s सफल रहा" [error]="चरण %s में त्रुटि हुई"
) ;;
esac
}
master_msg() {
local key="$1"
shift
printf "${MASTER_TEXT[$key]}" "$@"
}
localize_step_titles() {
case "$UI_LANG" in
ru) STEP_TITLES=("Часовой пояс, язык терминала, программы и swap" "Базовые пакеты, SSH-порт, IPv4/IPv6 и UFW" "Администратор, перенос SSH и fail2ban" "Telegram-уведомления о входах по SSH" "Системный VPN через Xray" "Панель 3x-ui и Cloudflare WARP" "Первичный снимок и расписания бэкапов" "AI-агенты на выбор для обычного пользователя") ;;
es) STEP_TITLES=("Zona horaria, idioma del terminal, programas y swap" "Paquetes base, puerto SSH, IPv4/IPv6 y UFW" "Administrador, migración SSH y fail2ban" "Notificaciones de Telegram para accesos SSH" "VPN del sistema mediante Xray" "Panel 3x-ui y Cloudflare WARP" "Instantánea inicial y copias programadas" "Agentes de IA opcionales para un usuario normal") ;;
de) STEP_TITLES=("Zeitzone, Terminalsprache, Programme und Swap" "Basispakete, SSH-Port, IPv4/IPv6 und UFW" "Administrator, SSH-Umstellung und fail2ban" "Telegram-Benachrichtigungen für SSH-Anmeldungen" "System-VPN über Xray" "3x-ui und Cloudflare WARP" "Erstsicherung und Sicherungspläne" "Optionale KI-Agenten für einen normalen Benutzer") ;;
fr) STEP_TITLES=("Fuseau horaire, langue du terminal, programmes et swap" "Paquets de base, port SSH, IPv4/IPv6 et UFW" "Administrateur, migration SSH et fail2ban" "Notifications Telegram des connexions SSH" "VPN système via Xray" "Panneau 3x-ui et Cloudflare WARP" "Instantané initial et sauvegardes planifiées" "Agents IA facultatifs pour un utilisateur standard") ;;
pt) STEP_TITLES=("Fuso horário, idioma do terminal, programas e swap" "Pacotes básicos, porta SSH, IPv4/IPv6 e UFW" "Administrador, migração SSH e fail2ban" "Notificações Telegram de logins SSH" "VPN do sistema via Xray" "Painel 3x-ui e Cloudflare WARP" "Snapshot inicial e backups agendados" "Agentes de IA opcionais para um usuário comum") ;;
zh) STEP_TITLES=("时区、终端语言、程序和交换文件" "基础软件包、SSH 端口、IPv4/IPv6 和 UFW" "管理员、SSH 迁移和 fail2ban" "SSH 登录的 Telegram 通知" "通过 Xray 的系统 VPN" "3x-ui 面板和 Cloudflare WARP" "初始快照和备份计划" "为普通用户安装可选 AI 代理") ;;
ja) STEP_TITLES=("タイムゾーン、端末言語、プログラム、スワップ" "基本パッケージ、SSH ポート、IPv4/IPv6、UFW" "管理者、SSH 移行、fail2ban" "SSH ログインの Telegram 通知" "Xray によるシステム VPN" "3x-ui パネルと Cloudflare WARP" "初回スナップショットとバックアップ予定" "一般ユーザー向けのオプション AI エージェント") ;;
ar) STEP_TITLES=("المنطقة الزمنية ولغة الطرفية والبرامج وswap" "الحزم الأساسية ومنفذ SSH وIPv4/IPv6 وUFW" "المسؤول ونقل SSH وfail2ban" "إشعارات Telegram لدخول SSH" "VPN للنظام عبر Xray" "لوحة 3x-ui وCloudflare WARP" "النسخة الأولية وجداول النسخ الاحتياطي" "وكلاء ذكاء اصطناعي اختياريون لمستخدم عادي") ;;
hi) STEP_TITLES=("समय क्षेत्र, टर्मिनल भाषा, प्रोग्राम और swap" "मूल पैकेज, SSH पोर्ट, IPv4/IPv6 और UFW" "प्रशासक, SSH स्थानांतरण और fail2ban" "SSH लॉगिन की Telegram सूचनाएँ" "Xray द्वारा सिस्टम VPN" "3x-ui पैनल और Cloudflare WARP" "प्रारंभिक स्नैपशॉट और बैकअप अनुसूचियाँ" "सामान्य उपयोगकर्ता के लिए वैकल्पिक AI एजेंट") ;;
esac
}
choose_dialog_language() {
local answer
printf '\nSelect the installer language / Выберите язык установщика:\n'
printf '%s\n' ' 1) English' ' 2) Русский' ' 3) Español' ' 4) Deutsch' \
' 5) Français' ' 6) Português' ' 7) 中文' ' 8) 日本語' ' 9) العربية' ' 10) हिन्दी'
while :; do
read -rp 'Language [1 — English]: ' answer || die "Input was interrupted"
answer="${answer:-1}"
case "$answer" in
1) UI_LANG=en ;; 2) UI_LANG=ru ;; 3) UI_LANG=es ;; 4) UI_LANG=de ;; 5) UI_LANG=fr ;;
6) UI_LANG=pt ;; 7) UI_LANG=zh ;; 8) UI_LANG=ja ;; 9) UI_LANG=ar ;; 10) UI_LANG=hi ;;
*) warn "Enter a number from 1 to 10"; continue ;;
esac
break
done
export OCHENSTARIK_UI_LANG="$UI_LANG"
load_master_text
localize_step_titles
}
load_master_text
log() { printf '[+] %s\n' "$*"; }
warn() { printf '[!] %s\n' "$*" >&2; }
die() { printf '[x] %s\n' "$*" >&2; exit 1; }
cleanup() {
if [[ -n "$TEMP_FILE" && "$TEMP_FILE" == "$SCRIPT_DIR"/.ochenstarik-server-* \
&& -f "$TEMP_FILE" && ! -L "$TEMP_FILE" ]]; then
rm -f -- "$TEMP_FILE"
fi
}
trap cleanup EXIT
ensure_step_script() {
local filename="$1" result_variable="$2" target
target="${SCRIPT_DIR}/${filename}"
if [[ -e "$target" ]]; then
[[ -f "$target" && ! -L "$target" ]] \
|| die "Отказ от запуска: $target должен быть обычным файлом"
else
command -v curl >/dev/null 2>&1 \
|| die "Не найден curl: установите его командой apt-get install -y curl ca-certificates"
[[ -d "$SCRIPT_DIR" && ! -L "$SCRIPT_DIR" && -w "$SCRIPT_DIR" ]] \
|| die "Каталог $SCRIPT_DIR недоступен для безопасной загрузки"
log "Файл ${filename} отсутствует; загружаю его из основного репозитория"
TEMP_FILE="$(mktemp "${SCRIPT_DIR}/.ochenstarik-server-download.XXXXXX")"
chmod 600 "$TEMP_FILE"
curl -fL --retry 5 --retry-delay 5 --connect-timeout 30 \
--proto '=https' --tlsv1.2 "${REPO_RAW_BASE}/${filename}" -o "$TEMP_FILE"
bash -n "$TEMP_FILE" || die "Загруженный файл ${filename} не прошёл проверку Bash"
chmod 700 "$TEMP_FILE"
mv -- "$TEMP_FILE" "$target"
TEMP_FILE=""
fi
chmod 700 "$target"
bash -n "$target" || die "Синтаксическая ошибка в $target"
printf -v "$result_variable" '%s' "$target"
}
choose_step_action() {
local step_number="$1" title="$2" result_variable="$3" answer
while :; do
printf '\n============================================================\n'
printf '%s\n' "$(master_msg step "$step_number" "${#STEP_FILES[@]}" "$title")"
printf ' 1) %s\n' "$(master_msg install)"
printf ' 2) %s\n' "$(master_msg skip)"
printf ' 3) %s\n' "$(master_msg finish)"
read -rp "$(master_msg action)" answer || die "$(master_msg interrupted)"
case "$answer" in
1|2|3) printf -v "$result_variable" '%s' "$answer"; return 0 ;;
*) warn "$(master_msg invalid)" ;;
esac
done
}
choose_after_failure() {
local result_variable="$1" answer
while :; do
printf '\n%s\n' "$(master_msg failed)"
printf ' 1) %s\n' "$(master_msg retry)"
printf ' 2) %s\n' "$(master_msg skip)"
printf ' 3) %s\n' "$(master_msg finish)"
read -rp "$(master_msg action)" answer || die "$(master_msg interrupted)"
case "$answer" in
1|2|3) printf -v "$result_variable" '%s' "$answer"; return 0 ;;
*) warn "$(master_msg invalid)" ;;
esac
done
}
print_summary() {
local completed="$1" skipped="$2"
printf '\n============================================================\n'
printf '%s\n' "$(master_msg summary)"
printf '%s\n' "$(master_msg completed "$completed")"
printf '%s\n' "$(master_msg skipped "$skipped")"
printf '\n%s\n' "$(master_msg rerun)"
printf '%s\n' "$(master_msg reset)"
}
[[ "$EUID" -eq 0 ]] || die "Запустите мастер от имени root: sudo ./ochenstarik-server-install.sh"
((${#STEP_FILES[@]} == ${#STEP_TITLES[@]})) || die "Некорректное описание этапов"
choose_dialog_language
printf '\n%s\n\n%s\n' "$(master_msg intro_title)" "$(master_msg intro_body)"
completed=0
skipped=0
for index in "${!STEP_FILES[@]}"; do
step_number="$((index + 1))"
action=""
choose_step_action "$step_number" "${STEP_TITLES[index]}" action
case "$action" in
2)
skipped="$((skipped + 1))"
continue
;;
3)
print_summary "$completed" "$skipped"
exit 0
;;
esac
script_path=""
ensure_step_script "${STEP_FILES[index]}" script_path
while :; do
log "$(master_msg launch "$step_number" "${STEP_TITLES[index]}")"
if bash -- "$script_path"; then
completed="$((completed + 1))"
log "$(master_msg success "$step_number")"
break
fi
warn "$(master_msg error "$step_number")"
failure_action=""
choose_after_failure failure_action
case "$failure_action" in
1) continue ;;
2) skipped="$((skipped + 1))"; break ;;
3) print_summary "$completed" "$skipped"; exit 1 ;;
esac
done
done
print_summary "$completed" "$skipped"

View file

@ -0,0 +1,373 @@
#!/usr/bin/env bash
set -Eeuo pipefail
readonly CONFIG_DIR="/etc/ochenstarik-server"
readonly MANAGED_PORTS_CONFIG="${CONFIG_DIR}/ufw-managed-ports.conf"
readonly IP_FAMILY_CONFIG="${CONFIG_DIR}/ip-family.conf"
readonly SSH_PORT_CONFIG="${CONFIG_DIR}/ssh-port.conf"
readonly IPV6_SYSCTL_FILE="/etc/sysctl.d/99-zz-ochenstarik-disable-ipv6.conf"
readonly SWAPFILE="/swapfile"
readonly SWAP_SYSCTL_FILE="/etc/sysctl.d/60-hermes-swap.conf"
readonly SSHD_DROPIN="/etc/ssh/sshd_config.d/00-hermes-hardening.conf"
readonly FAIL2BAN_JAIL="/etc/fail2ban/jail.d/hermes.local"
readonly SETUP_ENV="/root/setup-data/env.txt"
readonly SETUP_HASH="/root/setup-data/password.hash"
readonly TELEGRAM_CONFIG="${CONFIG_DIR}/telegram.conf"
readonly TELEGRAM_SCRIPT="/usr/local/libexec/ochenstarik-ssh-login-telegram.sh"
readonly TELEGRAM_LEGACY_SCRIPT="/usr/local/libexec/ssh-login-telegram.sh"
readonly TELEGRAM_LOG="/var/log/ochenstarik-ssh-login-telegram.log"
readonly TELEGRAM_LOGROTATE="/etc/logrotate.d/ochenstarik-ssh-login-telegram"
readonly PAM_SSHD="/etc/pam.d/sshd"
readonly XRAY_STATE_DIR="/etc/ochenstarik-xray"
readonly XRAY_CONFIG_DIR="/usr/local/etc/xray"
readonly XRAY_SHARE_DIR="/usr/local/share/xray"
readonly XRAY_LOG_DIR="/var/log/xray"
readonly XRAY_HELPER="/usr/local/sbin/ochenstarik-xray-routing"
readonly XRAY_SERVICE="/etc/systemd/system/ochenstarik-xray-routing.service"
readonly BACKUP_CONFIG="${CONFIG_DIR}/backup.conf"
readonly BACKUP_RUNNER="/usr/local/sbin/ochenstarik-server-backup"
readonly LOCALE_BACKUP="${CONFIG_DIR}/locale-before-selection.conf"
readonly LOCALE_ABSENT_MARKER="${CONFIG_DIR}/locale-before-selection.absent"
readonly LOCALE_PACKAGES="${CONFIG_DIR}/locale-installed-packages.list"
readonly LEGACY_LOCALE_BACKUP="${CONFIG_DIR}/locale-before-russian.conf"
readonly LEGACY_LOCALE_ABSENT_MARKER="${CONFIG_DIR}/locale-before-russian.absent"
readonly LEGACY_LOCALE_PACKAGES="${CONFIG_DIR}/russian-locale-installed-packages.list"
readonly PROGRAM_PACKAGES="${CONFIG_DIR}/step1-installed-packages.list"
log() { printf '[+] %s\n' "$*"; }
warn() { printf '[!] %s\n' "$*" >&2; }
die() { printf '[x] %s\n' "$*" >&2; exit 1; }
ask_yes_no() {
local prompt="$1" default_answer="${2:-no}" answer suffix
[[ "$default_answer" == yes || "$default_answer" == no ]] \
|| die "Некорректный ответ по умолчанию"
[[ "$default_answer" == yes ]] && suffix='[Y/n]' || suffix='[y/N]'
while :; do
read -rp "$prompt $suffix " answer || die "Ввод прерван"
answer="${answer:-$default_answer}"
case "${answer,,}" in
y|yes|д|да) return 0 ;;
n|no|н|нет) return 1 ;;
*) warn "Ответьте yes/no или да/нет" ;;
esac
done
}
service_exists() {
command -v systemctl >/dev/null 2>&1 && systemctl cat "$1" >/dev/null 2>&1
}
stop_disable_service() {
local unit="$1"
service_exists "$unit" || return 0
systemctl disable --now "$unit" >/dev/null 2>&1 || true
systemctl reset-failed "$unit" >/dev/null 2>&1 || true
}
is_valid_port() {
[[ "$1" =~ ^[0-9]{1,5}$ ]] && ((10#$1 >= 1 && 10#$1 <= 65535))
}
current_ssh_port() {
local port=""
if [[ -n "${SSH_CONNECTION:-}" ]]; then
port="${SSH_CONNECTION##* }"
is_valid_port "$port" && printf '%s' "$((10#$port))"
fi
}
remove_ufw_rules() {
local rule port protocol active_port
command -v ufw >/dev/null 2>&1 || return 0
active_port="$(current_ssh_port)"
if LANG=C ufw status 2>/dev/null | grep -q '^Status: active'; then
ufw allow 22/tcp >/dev/null
log "Порт 22/tcp открыт перед сбросом SSH"
fi
[[ -f "$MANAGED_PORTS_CONFIG" && ! -L "$MANAGED_PORTS_CONFIG" ]] || return 0
while IFS= read -r rule || [[ -n "$rule" ]]; do
[[ "$rule" =~ ^([0-9]{1,5})/(tcp|udp)$ ]] || continue
port="$((10#${BASH_REMATCH[1]}))"
protocol="${BASH_REMATCH[2]}"
if [[ "$protocol" == tcp && "$port" == 22 ]]; then
continue
fi
if [[ -n "$active_port" && "$protocol" == tcp && "$port" == "$active_port" ]]; then
warn "Правило текущего SSH-порта ${port}/tcp оставлено для защиты соединения"
continue
fi
while ufw --force delete allow from 0.0.0.0/0 to any port "$port" proto "$protocol" \
>/dev/null 2>&1; do :; done
while ufw --force delete allow from ::/0 to any port "$port" proto "$protocol" \
>/dev/null 2>&1; do :; done
while ufw --force delete allow "${port}/${protocol}" >/dev/null 2>&1; do :; done
done < "$MANAGED_PORTS_CONFIG"
ufw reload >/dev/null 2>&1 || true
log "Управляемые правила UFW удалены"
}
restore_ipv6_defaults() {
rm -f -- "$IPV6_SYSCTL_FILE"
if command -v sysctl >/dev/null 2>&1; then
sysctl -w net.ipv6.conf.all.disable_ipv6=0 >/dev/null 2>&1 || true
sysctl -w net.ipv6.conf.default.disable_ipv6=0 >/dev/null 2>&1 || true
fi
if [[ -f /etc/default/ufw && ! -L /etc/default/ufw ]]; then
if grep -q '^IPV6=' /etc/default/ufw; then
sed -i 's/^IPV6=.*/IPV6=yes/' /etc/default/ufw
else
printf '\nIPV6=yes\n' >> /etc/default/ufw
fi
fi
command -v ufw >/dev/null 2>&1 && ufw reload >/dev/null 2>&1 || true
}
reset_ssh_and_user() {
local ssh_service="" configured_user="" login_user="${SUDO_USER:-}"
if [[ -f "$SETUP_ENV" && ! -L "$SETUP_ENV" ]]; then
configured_user="$(sed -n 's/^NEW_USERNAME=//p' "$SETUP_ENV" | head -n1 | tr -d '\r')"
fi
rm -f -- "$SSHD_DROPIN" "$FAIL2BAN_JAIL"
if command -v sshd >/dev/null 2>&1; then
sshd -t || die "После удаления drop-in конфигурация SSH не прошла проверку"
fi
for ssh_service in ssh sshd; do
if service_exists "$ssh_service"; then
systemctl restart "$ssh_service"
log "SSH перезапущен; проверьте новый вход через порт 22"
break
fi
done
service_exists fail2ban && systemctl restart fail2ban >/dev/null 2>&1 || true
if [[ -n "$configured_user" && "$configured_user" != root ]] && id "$configured_user" >/dev/null 2>&1; then
rm -f -- "/etc/sudoers.d/${configured_user}"
if ask_yes_no "Удалить созданного администратора ${configured_user} вместе с домашней папкой?" no; then
if [[ -n "$login_user" && "$configured_user" == "$login_user" ]]; then
warn "Нельзя удалить пользователя текущей sudo/SSH-сессии: ${configured_user}"
else
read -rp "Для удаления введите имя пользователя ${configured_user}: " confirmation
if [[ "$confirmation" == "$configured_user" ]]; then
userdel --remove "$configured_user"
log "Пользователь ${configured_user} удалён"
else
warn "Имя не совпало; пользователь оставлен"
fi
fi
else
warn "Пользователь ${configured_user} и его членство в группах оставлены"
fi
fi
rm -f -- "$SETUP_ENV" "$SETUP_HASH"
rmdir /root/setup-data >/dev/null 2>&1 || true
}
remove_telegram() {
if [[ -f "$PAM_SSHD" && ! -L "$PAM_SSHD" ]]; then
cp -a -- "$PAM_SSHD" "${PAM_SSHD}.bak.before-ochenstarik-uninstall.$(date +%F-%H%M%S)"
sed -i "\|${TELEGRAM_SCRIPT}|d;\|${TELEGRAM_LEGACY_SCRIPT}|d" "$PAM_SSHD"
fi
rm -f -- "$TELEGRAM_CONFIG" "$TELEGRAM_SCRIPT" "$TELEGRAM_LEGACY_SCRIPT" \
"$TELEGRAM_LOG" "$TELEGRAM_LOGROTATE"
find "$CONFIG_DIR" -maxdepth 1 -type f -name 'telegram.conf.bak.*' -delete 2>/dev/null || true
command -v sshd >/dev/null 2>&1 && sshd -t \
|| warn "Не удалось проверить SSH после удаления Telegram PAM-hook"
}
remove_xray() {
stop_disable_service ochenstarik-xray-rollback.timer
stop_disable_service ochenstarik-xray-rollback.service
stop_disable_service ochenstarik-xray-routing.service
[[ ! -x "$XRAY_HELPER" ]] || "$XRAY_HELPER" stop >/dev/null 2>&1 || true
if command -v nft >/dev/null 2>&1; then
nft list table ip ochenstarik_xray >/dev/null 2>&1 \
&& nft delete table ip ochenstarik_xray || true
nft list table ip6 ochenstarik_xray6 >/dev/null 2>&1 \
&& nft delete table ip6 ochenstarik_xray6 || true
fi
stop_disable_service xray.service
stop_disable_service xray@.service
rm -f -- "$XRAY_HELPER" "$XRAY_SERVICE" /etc/systemd/system/xray.service \
/etc/systemd/system/xray@.service /usr/local/bin/xray
rm -rf -- "$XRAY_STATE_DIR" "$XRAY_CONFIG_DIR" "$XRAY_SHARE_DIR" "$XRAY_LOG_DIR"
}
remove_panel_and_warp() {
stop_disable_service x-ui.service
if command -v warp-cli >/dev/null 2>&1; then
warp-cli --accept-tos disconnect >/dev/null 2>&1 || true
warp-cli --accept-tos registration delete >/dev/null 2>&1 || true
fi
stop_disable_service warp-svc.service
rm -f -- /etc/systemd/system/x-ui.service /usr/bin/x-ui /usr/local/bin/x-ui
rm -rf -- /usr/local/x-ui /etc/x-ui
if command -v apt-get >/dev/null 2>&1 && dpkg-query -W -f='${Status}' cloudflare-warp \
2>/dev/null | grep -Fqx 'install ok installed'; then
DEBIAN_FRONTEND=noninteractive apt-get purge -y cloudflare-warp
fi
rm -f -- /etc/apt/sources.list.d/cloudflare-client.list \
/usr/share/keyrings/cloudflare-warp-archive-keyring.gpg
}
read_backup_root() {
local value="/var/backups/ochenstarik-server"
if [[ -f "$BACKUP_CONFIG" && ! -L "$BACKUP_CONFIG" ]]; then
[[ "$(stat -c '%u' "$BACKUP_CONFIG")" == 0 ]] \
|| die "Файл $BACKUP_CONFIG должен принадлежать root"
value="$(bash -c 'set -efu; source "$1"; printf "%s" "$BACKUP_ROOT"' \
_ "$BACKUP_CONFIG")"
fi
[[ "$value" == /* && "$value" != / ]] || die "Опасный путь резервных копий: $value"
case "$value" in
/var|/var/backups|/home|/root|/mnt|/media|/etc|/usr|/opt)
warn "Общий каталог ${value} не будет очищен автоматически"
printf ''
return 0
;;
esac
printf '%s' "$value"
}
remove_backup_automation() {
local schedule backup_root
for schedule in daily weekly monthly; do
stop_disable_service "ochenstarik-backup-${schedule}.timer"
done
rm -f -- /etc/systemd/system/ochenstarik-backup@.service \
/etc/systemd/system/ochenstarik-backup-daily.timer \
/etc/systemd/system/ochenstarik-backup-weekly.timer \
/etc/systemd/system/ochenstarik-backup-monthly.timer "$BACKUP_RUNNER"
backup_root="$(read_backup_root)"
if [[ -n "$backup_root" && -d "$backup_root" ]] \
&& ask_yes_no "Удалить все архивы из ${backup_root}?" no; then
read -rp 'Для необратимого удаления введите УДАЛИТЬ БЭКАПЫ: ' confirmation
if [[ "$confirmation" == 'УДАЛИТЬ БЭКАПЫ' ]]; then
if command -v chattr >/dev/null 2>&1; then
find "$backup_root" -xdev -type f -exec chattr -i -- {} + 2>/dev/null || true
fi
rm -rf -- "$backup_root/initial" "$backup_root/daily" \
"$backup_root/weekly" "$backup_root/monthly"
log "Архивы проекта удалены"
else
warn "Фраза не совпала; архивы оставлены"
fi
else
warn "Архивы оставлены; они не помешают повторной установке"
fi
rm -f -- "$BACKUP_CONFIG"
}
restore_locale() {
local package_name package_file
local -a packages=()
if [[ -f "$LOCALE_BACKUP" && ! -L "$LOCALE_BACKUP" ]]; then
cp -a -- "$LOCALE_BACKUP" /etc/default/locale
log "Предыдущая системная локаль восстановлена"
elif [[ -f "$LEGACY_LOCALE_BACKUP" && ! -L "$LEGACY_LOCALE_BACKUP" ]]; then
cp -a -- "$LEGACY_LOCALE_BACKUP" /etc/default/locale
log "Предыдущая системная локаль восстановлена"
elif [[ -f "$LOCALE_ABSENT_MARKER" && ! -L "$LOCALE_ABSENT_MARKER" ]]; then
rm -f -- /etc/default/locale
log "Созданный скриптом файл локали удалён"
elif [[ -f "$LEGACY_LOCALE_ABSENT_MARKER" \
&& ! -L "$LEGACY_LOCALE_ABSENT_MARKER" ]]; then
rm -f -- /etc/default/locale
log "Созданный скриптом файл локали удалён"
fi
for package_file in "$LOCALE_PACKAGES" "$LEGACY_LOCALE_PACKAGES"; do
[[ -f "$package_file" && ! -L "$package_file" ]] || continue
while IFS= read -r package_name || [[ -n "$package_name" ]]; do
[[ "$package_name" =~ ^[a-z0-9][a-z0-9+.-]*$ ]] \
&& [[ ! " ${packages[*]} " =~ " ${package_name} " ]] \
&& packages+=("$package_name")
done < "$package_file"
done
if ((${#packages[@]} > 0)) && ask_yes_no \
"Удалить языковые пакеты, которые ранее отсутствовали?" yes; then
DEBIAN_FRONTEND=noninteractive apt-get purge -y "${packages[@]}"
fi
rm -f -- "$LOCALE_BACKUP" "$LOCALE_ABSENT_MARKER" "$LOCALE_PACKAGES" \
"$LEGACY_LOCALE_BACKUP" "$LEGACY_LOCALE_ABSENT_MARKER" \
"$LEGACY_LOCALE_PACKAGES"
}
remove_step1_programs() {
local package_name
local -a packages=()
[[ -f "$PROGRAM_PACKAGES" && ! -L "$PROGRAM_PACKAGES" ]] || return 0
while IFS= read -r package_name || [[ -n "$package_name" ]]; do
[[ "$package_name" =~ ^[a-z0-9][a-z0-9+.-]*$ ]] && packages+=("$package_name")
done < "$PROGRAM_PACKAGES"
if ((${#packages[@]} > 0)) && ask_yes_no \
"Удалить дополнительные программы, установленные первым этапом?" yes; then
DEBIAN_FRONTEND=noninteractive apt-get purge -y "${packages[@]}"
fi
rm -f -- "$PROGRAM_PACKAGES"
}
remove_swap() {
[[ -e "$SWAP_SYSCTL_FILE" || -e "$SWAPFILE" ]] || return 0
ask_yes_no "Отключить и удалить ${SWAPFILE}?" yes || return 0
swapoff "$SWAPFILE" >/dev/null 2>&1 || true
if [[ -f /etc/fstab && ! -L /etc/fstab ]]; then
cp -a -- /etc/fstab "/etc/fstab.bak.before-ochenstarik-uninstall.$(date +%F-%H%M%S)"
sed -i '\|^[[:space:]]*/swapfile[[:space:]].*[[:space:]]swap[[:space:]]|d' /etc/fstab
fi
rm -f -- "$SWAP_SYSCTL_FILE"
if [[ -f "$SWAPFILE" && ! -L "$SWAPFILE" ]]; then
rm -f -- "$SWAPFILE"
else
warn "${SWAPFILE} не является обычным файлом и не удалён"
fi
sysctl --system >/dev/null 2>&1 || true
}
[[ "$EUID" -eq 0 ]] || die "Запустите скрипт от имени root"
cat <<'WARNING'
Этот скрипт удалит настройки, службы и данные, созданные проектом lightweight-server.
Он не откатывает обновления Ubuntu и не удаляет обязательные общие пакеты второго
этапа (OpenSSH, UFW и другие), потому что они нужны для доступа к серверу. Пакеты,
которые первый этап зафиксировал как новые, можно удалить отдельным подтверждением.
Сначала будет открыт SSH-порт 22. Не закрывайте текущую сессию, пока не проверите
новый вход после очистки.
WARNING
read -rp 'Для продолжения введите СБРОСИТЬ СЕРВЕР: ' final_confirmation
[[ "$final_confirmation" == 'СБРОСИТЬ СЕРВЕР' ]] || die "Очистка отменена"
remove_ufw_rules
restore_ipv6_defaults
reset_ssh_and_user
remove_telegram
remove_xray
remove_panel_and_warp
remove_backup_automation
restore_locale
remove_step1_programs
remove_swap
rm -f -- "$SSH_PORT_CONFIG" "$IP_FAMILY_CONFIG" "$MANAGED_PORTS_CONFIG"
find "$CONFIG_DIR" -maxdepth 1 -type f -name '*.bak.*' -delete 2>/dev/null || true
rmdir "$CONFIG_DIR" >/dev/null 2>&1 || true
systemctl daemon-reload >/dev/null 2>&1 || true
log "Очистка компонентов lightweight-server завершена"
printf '\nПроверьте вход в новой сессии: ssh -p 22 root@IP_СЕРВЕРА\n'
printf 'После проверки можно снова запускать скрипты установки по порядку.\n'
if [[ -n "$(current_ssh_port)" && "$(current_ssh_port)" != 22 ]]; then
printf 'Правило текущего старого SSH-порта %s/tcp оставлено. Удалите его после проверки нового входа.\n' \
"$(current_ssh_port)"
fi

View file

@ -60,6 +60,13 @@ require_command() {
command -v "$1" >/dev/null 2>&1 || die "Required command not found: $1"
}
read_sshd_setting() {
local setting="$1" config="$2" value
value="$(awk -v setting="$setting" '$1 == setting { print $2; exit }' <<< "$config")"
[[ -n "$value" ]] || die "SSH setting was not found in effective configuration: $setting"
printf '%s' "$value"
}
is_valid_port() {
local port="$1"
[[ "$port" =~ ^[0-9]{1,5}$ ]] || return 1
@ -465,9 +472,14 @@ EOF
chmod 644 "$SSHD_DROPIN"
sshd -t || die "sshd syntax validation failed"
effective_port="$(sshd -T | awk '$1 == "port" { print $2; exit }')"
effective_root="$(sshd -T | awk '$1 == "permitrootlogin" { print $2; exit }')"
effective_password="$(sshd -T | awk '$1 == "passwordauthentication" { print $2; exit }')"
# Capture the complete output once. With pipefail enabled, piping `sshd -T`
# into an awk program that exits after the first match can terminate sshd with
# SIGPIPE (status 141) and abort the script even though the configuration is
# valid.
effective_sshd_config="$(sshd -T)" || die "Could not read effective SSH configuration"
effective_port="$(read_sshd_setting port "$effective_sshd_config")"
effective_root="$(read_sshd_setting permitrootlogin "$effective_sshd_config")"
effective_password="$(read_sshd_setting passwordauthentication "$effective_sshd_config")"
[[ "$effective_port" == "$SSH_PORT" ]] || die "Effective SSH port is $effective_port, expected $SSH_PORT"
[[ "$effective_root" == no ]] || die "Effective PermitRootLogin is $effective_root, expected no"
[[ "$effective_password" == "$PASSWORD_AUTH" ]] || die "Effective PasswordAuthentication is $effective_password, expected $PASSWORD_AUTH"

View file

@ -44,17 +44,107 @@ stop_rollback_timer() {
ochenstarik-xray-rollback.service >/dev/null 2>&1 || true
}
[[ "$EUID" -eq 0 ]] || die "Запустите этот скрипт от имени root"
vpn_is_configured() {
[[ -f "$XRAY_CONFIG" && ! -L "$XRAY_CONFIG" \
&& -x "$ROUTE_HELPER" && ! -L "$ROUTE_HELPER" \
&& -f "$ROUTE_SERVICE" && ! -L "$ROUTE_SERVICE" ]]
}
if [[ "${1:-}" == "--disable" ]]; then
select_existing_action() {
local choice
printf '\nОбнаружена существующая конфигурация системного VPN.\n'
printf ' 1) Подключить VPN с сохранёнными настройками\n'
printf ' 2) Отключить VPN, сохранив настройки\n'
printf ' 3) Переустановить VPN или заменить подписку\n'
printf ' 4) Показать состояние VPN\n'
while :; do
read -rp 'Выберите действие [1]: ' choice || die "Ввод прерван"
choice="${choice:-1}"
case "$choice" in
1) ACTION=enable; return 0 ;;
2) ACTION=disable; return 0 ;;
3) ACTION=reconfigure; return 0 ;;
4) ACTION=status; return 0 ;;
*) warn "Введите номер от 1 до 4" ;;
esac
done
}
disable_vpn() {
stop_rollback_timer
systemctl disable --now ochenstarik-xray-routing.service >/dev/null 2>&1 || true
[[ ! -x "$ROUTE_HELPER" ]] || "$ROUTE_HELPER" stop
log "Системная маршрутизация через Xray отключена"
[[ ! -x "$ROUTE_HELPER" || -L "$ROUTE_HELPER" ]] || "$ROUTE_HELPER" stop
log "Системная маршрутизация через Xray отключена; конфигурация сохранена"
if command -v curl >/dev/null 2>&1; then
printf 'Текущий внешний IP: %s\n' "$(public_ipv4)"
fi
exit 0
fi
}
enable_vpn() {
local direct_ip vpn_ip
vpn_is_configured || die "Сохранённая конфигурация VPN не найдена; выполните установку"
command -v xray >/dev/null 2>&1 || die "Xray не установлен"
xray run -test -config "$XRAY_CONFIG" || die "Xray отклонил сохранённую конфигурацию"
systemctl stop ochenstarik-xray-routing.service >/dev/null 2>&1 || true
"$ROUTE_HELPER" stop
direct_ip="$(public_ipv4)"
valid_ipv4 "$direct_ip" || die "Не удалось определить прямой внешний IPv4 перед подключением"
systemctl daemon-reload
systemctl enable --now xray.service
systemctl restart xray.service
systemctl enable --now ochenstarik-xray-routing.service
systemctl restart ochenstarik-xray-routing.service
systemctl is-active --quiet xray.service || die "Служба Xray не запущена"
systemctl is-active --quiet ochenstarik-xray-routing.service \
|| die "Системная маршрутизация через VPN не запущена"
vpn_ip="$(public_ipv4)"
if ! valid_ipv4 "$vpn_ip" || [[ "$vpn_ip" == "$direct_ip" ]]; then
systemctl stop ochenstarik-xray-routing.service >/dev/null 2>&1 || true
"$ROUTE_HELPER" stop
die "Проверка внешнего IP не пройдена; VPN автоматически отключён"
fi
log "Системный VPN подключён с сохранёнными настройками"
printf 'Прямой внешний IP: %s\n' "$direct_ip"
printf 'Внешний IP через VPN: %s\n' "$vpn_ip"
}
show_vpn_status() {
if systemctl is-active --quiet ochenstarik-xray-routing.service; then
printf 'Системный VPN: подключён\n'
else
printf 'Системный VPN: отключён\n'
fi
printf 'Xray: %s\n' "$(systemctl is-active xray.service 2>/dev/null || true)"
printf 'Маршрутизация: %s\n' \
"$(systemctl is-active ochenstarik-xray-routing.service 2>/dev/null || true)"
if [[ -x "$ROUTE_HELPER" && ! -L "$ROUTE_HELPER" ]] \
&& systemctl is-active --quiet ochenstarik-xray-routing.service; then
"$ROUTE_HELPER" status || warn "Не удалось полностью прочитать правила маршрутизации"
fi
if command -v curl >/dev/null 2>&1; then
printf 'Текущий внешний IP: %s\n' "$(public_ipv4)"
fi
}
[[ "$EUID" -eq 0 ]] || die "Запустите этот скрипт от имени root"
ACTION=""
case "${1:-}" in
"") vpn_is_configured && select_existing_action || ACTION=reconfigure ;;
--enable) ACTION=enable ;;
--disable) ACTION=disable ;;
--reconfigure) ACTION=reconfigure ;;
--status) ACTION=status ;;
*) die "Использование: $0 [--enable|--disable|--reconfigure|--status]" ;;
esac
case "$ACTION" in
enable) enable_vpn; exit 0 ;;
disable) disable_vpn; exit 0 ;;
status) show_vpn_status; exit 0 ;;
reconfigure) ;;
*) die "Некорректное действие VPN: $ACTION" ;;
esac
export DEBIAN_FRONTEND=noninteractive
log "Установка системных зависимостей"
@ -359,7 +449,8 @@ printf 'IP через локальный прокси Xray: %s\n' "$PROXY_IP"
[[ "$PROXY_IP" != "$BEFORE_IP" ]] \
|| die "IP через Xray совпадает с исходным; системная маршрутизация не включена"
SSH_PORT="$(sshd -T 2>/dev/null | awk '$1 == "port" { print $2; exit }' || true)"
SSHD_EFFECTIVE_CONFIG="$(sshd -T 2>/dev/null || true)"
SSH_PORT="$(awk '$1 == "port" { print $2; exit }' <<< "$SSHD_EFFECTIVE_CONFIG")"
[[ "$SSH_PORT" =~ ^[0-9]+$ ]] || SSH_PORT="20202"
install -d -m 700 -o root -g root "$STATE_DIR"
@ -516,5 +607,8 @@ printf 'IP через Xray: %s\n' "$PROXY_IP"
printf 'IP после VPN: %s\n' "$AFTER_IP"
printf 'SSH-порт %s и ответы сервисов 443/63636 оставлены напрямую.\n' "$SSH_PORT"
printf 'Публичный IPv6 заблокирован, чтобы исключить обход VPN.\n'
printf '\nОтключить системный VPN:\n %s --disable\n' "$0"
printf '\nПодключить VPN повторно:\n %s --enable\n' "$0"
printf 'Отключить VPN с сохранением настроек:\n %s --disable\n' "$0"
printf 'Заменить подписку:\n %s --reconfigure\n' "$0"
printf 'Показать состояние VPN:\n %s --status\n' "$0"
printf 'Проверить маршрутизацию:\n %s status\n' "$ROUTE_HELPER"

View file

@ -0,0 +1,26 @@
#!/usr/bin/env bash
set -Eeuo pipefail
cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.."
source <(awk '/^\[\[ "\$EUID"/{exit} {print}' ochenstarik-server-install.sh)
declare -a expected=(en ru es de fr pt zh ja ar hi)
for index in "${!expected[@]}"; do
choose_dialog_language <<< "$((index + 1))" >/dev/null
[[ "$UI_LANG" == "${expected[index]}" ]]
[[ "$OCHENSTARIK_UI_LANG" == "${expected[index]}" ]]
done
choose_dialog_language <<< '' >/dev/null
[[ "$UI_LANG" == en ]]
[[ "$(master_msg install)" == 'Install / run this step' ]]
choose_dialog_language <<< '2' >/dev/null
[[ "$(master_msg install)" == 'Установить / запустить этот этап' ]]
[[ "${STEP_TITLES[0]}" == 'Часовой пояс, язык терминала, программы и swap' ]]
[[ "${#STEP_FILES[@]}" == 8 ]]
[[ "${#STEP_TITLES[@]}" == 8 ]]
[[ "${STEP_FILES[7]}" == 'ochenstarik-server-ai-agents-8.sh' ]]
[[ "${STEP_TITLES[7]}" == 'AI-агенты на выбор для обычного пользователя' ]]
printf 'Master language selection tests passed.\n'

View file

@ -0,0 +1,31 @@
#!/usr/bin/env bash
set -Eeuo pipefail
cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.."
readmes=(
README.md
docs/readme/README.ru.md
docs/readme/README.es.md
docs/readme/README.de.md
docs/readme/README.fr.md
docs/readme/README.pt.md
docs/readme/README.zh-CN.md
docs/readme/README.ja.md
docs/readme/README.ar.md
docs/readme/README.hi.md
)
[[ "${#readmes[@]}" == 10 ]]
grep -Fqx '# Lightweight Ubuntu Server Setup' README.md
for readme in "${readmes[@]}"; do
[[ -s "$readme" ]]
grep -Fq 'https://github.com/ochenstarik-ui/server-monitor-manager' "$readme"
grep -Fq 'English' "$readme"
grep -Fq 'Русский' "$readme"
grep -Fq 'Español' "$readme"
grep -Fq 'हिन्दी' "$readme"
done
printf 'README language and monitoring links passed.\n'

View file

@ -0,0 +1,57 @@
#!/usr/bin/env bash
set -Eeuo pipefail
cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.."
source <(awk '/^\[\[ "\$EUID"/{exit} {print}' ochenstarik-server-1.sh)
declare -a MOCK_APT_PACKAGES=()
MOCK_LOCALE=""
MOCK_UPDATE_LOCALE=""
ensure_apt_updated() { :; }
apt-cache() { return 0; }
package_is_installed() { return 1; }
record_new_packages() { :; }
save_original_locale() { :; }
apt-get() {
[[ "${1:-}" == install ]] || return 0
shift
[[ "${1:-}" != -y ]] || shift
MOCK_APT_PACKAGES=("$@")
}
locale-gen() { MOCK_LOCALE="$1"; }
update-locale() { MOCK_UPDATE_LOCALE="$*"; }
contains_package() {
local expected="$1" package_name
for package_name in "${MOCK_APT_PACKAGES[@]}"; do
[[ "$package_name" != "$expected" ]] || return 0
done
return 1
}
choose_and_install_programs <<< '1 3' >/dev/null
contains_package git
contains_package iotop
! contains_package nmap
! contains_package ffmpeg
MOCK_APT_PACKAGES=()
choose_and_install_programs <<< '' >/dev/null
contains_package nmap
contains_package ffmpeg
contains_package restic
contains_package postgresql-client
MOCK_APT_PACKAGES=()
choose_and_install_programs <<< '0' >/dev/null
((${#MOCK_APT_PACKAGES[@]} == 0))
choose_terminal_language <<< '2' >/dev/null
[[ "$MOCK_LOCALE" == ru_RU.UTF-8 ]]
[[ "$MOCK_UPDATE_LOCALE" == *'LANG=ru_RU.UTF-8'* ]]
[[ "$MOCK_UPDATE_LOCALE" == *'LANGUAGE=ru_RU:ru'* ]]
printf 'Step 1 selection tests passed.\n'

View file

@ -0,0 +1,44 @@
#!/usr/bin/env bash
set -Eeuo pipefail
script_path="${1:-ochenstarik-server-2.sh}"
[[ -f "$script_path" ]] || {
printf '[x] Missing script: %s\n' "$script_path" >&2
exit 1
}
if grep -q 'disable_ipv6 = 1' "$script_path"; then
printf '[x] Step 2 must not disable the IPv6 kernel stack for IPv4 mode\n' >&2
exit 1
fi
if grep -q 'disable_ipv6_systemwide' "$script_path"; then
printf '[x] Step 2 still references the old system-wide IPv6 disable helper\n' >&2
exit 1
fi
if ! grep -q 'restore_ipv6_stack_if_managed' "$script_path"; then
printf '[x] Step 2 should restore IPv6 when a legacy managed sysctl file exists\n' >&2
exit 1
fi
ipv4_block="$(
awk '
/if \[\[ "\$IP_MODE" == ipv4 \]\]; then/ { capture=1 }
capture { print }
capture && /^ else$/ { exit }
' "$script_path"
)"
if [[ "$ipv4_block" != *'set_ufw_ipv6_setting yes'* ]]; then
printf '[x] IPv4 mode must keep UFW IPv6 support enabled so deny incoming applies to IPv6\n' >&2
exit 1
fi
if [[ "$ipv4_block" != *'delete_managed_ufw_rules'* ]]; then
printf '[x] IPv4 mode must delete old managed IPv6 allow rules before applying IPv4-only rules\n' >&2
exit 1
fi
printf '[+] Step 2 IPv4 firewall behavior looks correct\n'

View file

@ -0,0 +1,18 @@
#!/usr/bin/env bash
set -Eeuo pipefail
cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.."
source <(awk '/^\[\[ "\$EUID"/{exit} {print}' ochenstarik-server-user-3.sh)
effective_config=$'port 20202\npermitrootlogin no\npasswordauthentication no\npubkeyauthentication yes'
[[ "$(read_sshd_setting port "$effective_config")" == 20202 ]]
[[ "$(read_sshd_setting permitrootlogin "$effective_config")" == no ]]
[[ "$(read_sshd_setting passwordauthentication "$effective_config")" == no ]]
if grep -E 'sshd[[:space:]]+-T[[:space:]]*\|' ochenstarik-server-user-3.sh; then
printf 'Unsafe sshd -T pipeline found.\n' >&2
exit 1
fi
printf 'Step 3 SSH configuration parsing tests passed.\n'

View file

@ -0,0 +1,22 @@
#!/usr/bin/env bash
set -Eeuo pipefail
cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.."
source <(awk '/^\[\[ "\$EUID"/{exit} {print}' ochenstarik-server-vpn-5.sh)
ACTION=""
select_existing_action <<< '' >/dev/null
[[ "$ACTION" == enable ]]
for pair in '1 enable' '2 disable' '3 reconfigure' '4 status'; do
read -r input expected <<< "$pair"
ACTION=""
select_existing_action <<< "$input" >/dev/null
[[ "$ACTION" == "$expected" ]]
done
for option in --enable --disable --reconfigure --status; do
grep -Fq -- "$option" ochenstarik-server-vpn-5.sh
done
printf 'Step 5 repeated-run action tests passed.\n'

View file

@ -0,0 +1,34 @@
#!/usr/bin/env bash
set -Eeuo pipefail
cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.."
source <(awk '/^\[\[ "\$EUID"/{exit} {print}' ochenstarik-server-ai-agents-8.sh)
[[ "${#AGENT_IDS[@]}" == 7 ]]
[[ "${AGENT_IDS[*]}" == 'hermes openclaw openhands opencode aider autogpt pi' ]]
expected_urls=(
'https://hermes-agent.nousresearch.com/install.sh'
'https://openclaw.ai/install.sh'
'https://install.openhands.dev/install.sh'
'https://opencode.ai/install'
'https://aider.chat/install.sh'
'https://setup.agpt.co/install.sh'
'https://pi.dev/install.sh'
)
for index in "${!expected_urls[@]}"; do
[[ "${AGENT_INSTALL_URLS[index]}" == "${expected_urls[index]}" ]]
done
select_agents <<< '1 3 5 6 7' >/dev/null
[[ "${SELECTED_INDEXES[*]}" == '0 2 4 5 6' ]]
select_agents <<< '' >/dev/null
[[ "${#SELECTED_INDEXES[@]}" == 0 ]]
if grep -E 'curl[^|]*\|[[:space:]]*(ba)?sh' ochenstarik-server-ai-agents-8.sh; then
printf 'Direct curl-to-shell execution found.\n' >&2
exit 1
fi
printf 'Step 8 AI agent catalog tests passed.\n'