From 440d8fc5627fe0681d29591e1cb0ffc8dbb9c8f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80?= Date: Sun, 27 Sep 2026 15:31:43 +0900 Subject: [PATCH] =?UTF-8?q?=D0=9F=D1=80=D0=BE=D0=B5=D0=BA=D1=82=20=D0=BF?= =?UTF-8?q?=D0=B5=D1=80=D0=B5=D0=B5=D1=85=D0=B0=D0=BB=20=D0=B2=20Gitea=20S?= =?UTF-8?q?tackWorks:=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F,=20=D1=83=D1=81=D1=82=D0=B0=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=BE=D0=B4=D0=BD=D0=BE=D0=B9=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=BC=D0=B0=D0=BD=D0=B4=D0=BE=D0=B9=20=D0=B8=20ISO=20=D0=B1?= =?UTF-8?q?=D0=B5=D1=80=D1=83=D1=82=20=D0=BA=D0=BE=D0=B4=20=D1=81=20git.st?= =?UTF-8?q?ackworks.ru,=20=D0=B7=D0=B0=D0=BF=D0=B0=D1=81=D0=BD=D0=BE=D0=B9?= =?UTF-8?q?=20ZIP=20=D1=81=D0=BA=D0=B0=D1=87=D0=B8=D0=B2=D0=B0=D0=B5=D1=82?= =?UTF-8?q?=D1=81=D1=8F=20=D0=BE=D1=82=D1=82=D1=83=D0=B4=D0=B0=20=D0=B6?= =?UTF-8?q?=D0=B5,=20=D0=BD=D0=BE=D0=B4=D1=8B=20=D1=81=D0=BE=20=D1=81?= =?UTF-8?q?=D1=82=D0=B0=D1=80=D1=8B=D0=BC=20=D0=B0=D0=B4=D1=80=D0=B5=D1=81?= =?UTF-8?q?=D0=BE=D0=BC=20GitHub=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BA=D0=BB?= =?UTF-8?q?=D1=8E=D1=87=D0=B0=D1=8E=D1=82=D1=81=D1=8F=20=D1=81=D0=B0=D0=BC?= =?UTF-8?q?=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CLAUDE.md | 7 ++- README.md | 17 ++++---- docs/ARCHITECTURE.md | 6 ++- docs/IMAGE.md | 24 ++++------- docs/REQUIREMENTS.md | 2 +- docs/ROADMAP.md | 2 +- image/build-iso.sh | 4 +- image/files/virtuality-firstboot.service | 2 +- install.sh | 4 +- scripts/apply_github_update.sh | 17 ++++---- scripts/auto_update_check.sh | 11 ++++- scripts/update_target.sh | 36 ++++++++++++++-- tests/test_core.py | 54 ++++++++++++++++++++++++ web/templates/update.html | 2 +- web/update_core.py | 11 +++++ 15 files changed, 150 insertions(+), 49 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index f752ee0..b914168 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -7,6 +7,9 @@ Virtuality — простая замена Proxmox для дома и малог **Общайся с владельцем проекта по-русски.** Весь текст интерфейса — на простом русском, без жаргона (термины вроде qcow2, libvirt, nvram — только в раскрывающихся блоках «Для специалистов»). +Репозиторий: https://git.stackworks.ru/viktor/virtuality (Gitea StackWorks). С GitHub проект переехал в сентябре 2026 года; +ноды со старым адресом переключаются сами (`update_core.migrate_remote`, `migrate_update_remote` в `scripts/update_target.sh`). + ## Устройство репозитория - `web/app.py` — основное приложение FastAPI (машины, ISO, образы дисков, сеть, журналы, обновления, консоль). @@ -55,8 +58,8 @@ make iso # ISO в dist/ (нужны xorriso, git, curl, p - Работаем прямо в `main` (так решил владелец). Перед каждым пушем — `make check`; не пушить недоделанное: ноды на канале «Ранний доступ» (`main`) подтягивают main каждую ночь. - Узлы на канале `stable` (по умолчанию) обновляются только до тегов `vX.Y.Z`. Релиз: поднять `VERSION`, - добавить запись в `updates/versions.json`, запушить, поставить тег `vX.Y.Z` на main — тег же запускает - сборку ISO в GitHub Actions (artifacts `virtuality-iso-amd64/arm64`). + добавить запись в `updates/versions.json`, запушить, поставить тег `vX.Y.Z` на main. Раннера Actions нет: + ISO собирается вручную `make iso`, CI заменяет `make check` перед пушем. - Секреты и пароли не логировать; пароль cloud-init хранится только как SHA-512 crypt. - Не возвращать удалённое: Cockpit, консольный dashboard, эмуляцию ARM64 на x86, PXE, смену архитектуры машины, скрипты bootstrap/clean_install/setup_bridge_br0/create_test_vm. diff --git a/README.md b/README.md index 1db196c..a54f073 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ **Virtuality** — простая замена Proxmox для дома и малого офиса. Один сервер, виртуальные машины и понятная панель в браузере на русском языке. Внутри — проверенные KVM, QEMU и libvirt, снаружи — интерфейс, в котором не нужна командная строка. -Текущая версия: **0.11.0** (файл `VERSION`, журнал изменений — `updates/versions.json`). +Текущая версия: **0.11.1** (файл `VERSION`, журнал изменений — `updates/versions.json`). Что умеет: @@ -70,14 +70,13 @@ Где взять образ: -- **GitHub Actions.** Откройте вкладку Actions репозитория → workflow **Build ISO image** → последний успешный запуск → раздел Artifacts → `virtuality-iso-amd64` (или `virtuality-iso-arm64`). Внутри архива — `.iso` и файл `.sha256`. Артефакты хранятся 30 дней; новый образ собирается при каждом выпуске (тег `vX.Y.Z`), запустить сборку вручную можно кнопкой Run workflow. - **Собрать самому** на любой Linux-машине (root не нужен): ```bash sudo apt install -y xorriso git curl python3-pip - git clone https://github.com/viktor138irk/virtuality.git + git clone https://git.stackworks.ru/viktor/virtuality.git cd virtuality - make iso # dist/virtuality-0.11.0-ubuntu-26.04-amd64.iso + make iso # dist/virtuality-0.11.1-ubuntu-26.04-amd64.iso ``` Дальше: записать образ на флешку (Rufus, balenaEtcher или `dd`), загрузиться с неё, выбрать **Установить Virtuality**, ответить на три экрана установщика и дождаться перезагрузки. Через минуту после неё панель уже открывается в браузере — адрес показан на экране сервера. Все подробности, параметры сборки и полностью автоматическая установка — в [docs/IMAGE.md](docs/IMAGE.md). @@ -87,7 +86,7 @@ Подходит для уже установленного Ubuntu Server 24.04 / 26.04 или Debian 13. Работает и под `root`, и под обычным пользователем с `sudo`: ```bash -curl -fsSL https://raw.githubusercontent.com/viktor138irk/virtuality/main/install.sh | bash +curl -fsSL https://git.stackworks.ru/viktor/virtuality/raw/branch/main/install.sh | bash ``` Скрипт проверит требования, поставит пакеты KVM/QEMU/libvirt, команду `vhealth` и панель, а в конце покажет адрес панели. Логин — тот пользователь, от которого запущена установка (через `sudo` — ваш пользователь, под `root` — `root`); пароль — его пароль Linux. Если пароль ещё не задан: `sudo passwd <пользователь>`. @@ -105,7 +104,7 @@ VIRTUALITY_INSTALL_VERBOSE=1 # показывать вывод команд вм Пример: ```bash -curl -fsSL https://raw.githubusercontent.com/viktor138irk/virtuality/main/install.sh | VIRTUALITY_USER=admin bash +curl -fsSL https://git.stackworks.ru/viktor/virtuality/raw/branch/main/install.sh | VIRTUALITY_USER=admin bash ``` При первом входе в панель откроется мастер настройки — тот же, что после установки с ISO. @@ -116,7 +115,7 @@ curl -fsSL https://raw.githubusercontent.com/viktor138irk/virtuality/main/instal ```bash sudo mkdir -p /opt/virtuality -sudo git clone https://github.com/viktor138irk/virtuality.git /opt/virtuality/source +sudo git clone https://git.stackworks.ru/viktor/virtuality.git /opt/virtuality/source cd /opt/virtuality/source sudo bash install_virtuality_node.sh # пакеты KVM/QEMU/libvirt, пулы, firewall sudo bash scripts/install_healthcheck_command.sh # команда sudo vhealth @@ -265,7 +264,7 @@ sudo virtuality-ctl uninstall [--purge] [--yes] # удалить Virtuality - **Из панели**: раздел **Обновления** → «Проверить сейчас» → «Обновить». Машины во время обновления продолжают работать. Что изменилось — видно на той же странице. - **Автоматически**: таймер `virtuality-auto-update.timer` проверяет обновления каждую ночь около 4:00 по времени сервера. Включается и выключается в «Настройках» или командой `sudo virtuality-ctl set VIRTUALITY_AUTO_UPDATE=0`. - **Каналы**: `stable` — только выпущенные версии (последний тег `vX.Y.Z`; пока тегов нет, стабильный канал следует ветке `main`), `main` — все изменения ветки `main`. Нода никогда не откатывается на более старую версию сама. -- **Вручную из терминала**: `sudo bash /opt/virtuality/source/scripts/apply_github_update.sh` — тот же сценарий, что и в панели (git fetch, при недоступности git — ZIP с GitHub, затем `scripts/install_web_panel.sh` и перезапуск панели). Журнал: `/var/log/virtuality/update.log`. +- **Вручную из терминала**: `sudo bash /opt/virtuality/source/scripts/apply_github_update.sh` — тот же сценарий, что и в панели (git fetch, при недоступности git — ZIP-архив с git.stackworks.ru, затем `scripts/install_web_panel.sh` и перезапуск панели). Журнал: `/var/log/virtuality/update.log`. - **Откат**: перед каждым обновлением предыдущая версия панели сохраняется в `/opt/virtuality/web.prev`. Если новая версия не запустилась: `sudo virtuality-ctl rollback`. --- @@ -353,7 +352,7 @@ VIRTUALITY_DEV_SETUP=finished python3 tests/dev_server.py # настроен Устройство кода описано в [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md). Интерфейс: шаблоны `web/templates` (каркас `base.html`, компоненты `_ui.html`), стили `web/static/app.css`, поведение `web/static/panel.js`; шрифт и иконки лежат в `web/static`. Скриншоты страниц: `node tests/screenshots.mjs` (нужен Playwright). -GitHub Actions: `ci.yml` запускает pyflakes, shellcheck и тесты на Python 3.11–3.14; `image.yml` собирает ISO по тегу `v*` и вручную. Ветка `main` — рабочая ветка нод на канале `main`; выпуск для стабильного канала — тег `vX.Y.Z`. +Репозиторий живёт в Gitea StackWorks: https://git.stackworks.ru/viktor/virtuality (до сентября 2026 года был на GitHub; ноды со старым адресом переключаются на новый сами при первой проверке обновлений). Ветка `main` — рабочая ветка нод на канале `main`; выпуск для стабильного канала — тег `vX.Y.Z`. Раннер Actions не подключён: перед каждым пушем запускается `make check`, ISO собирается вручную `make iso`. Файлы `.github/workflows` (`ci.yml` — проверки на Python 3.11–3.14, `image.yml` — сборка ISO) Gitea тоже понимает, они заработают, если раннер появится. --- diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 8b93a0f..e25b78c 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -146,9 +146,11 @@ libvirt-сеть с мостом `virbr100`, подсеть `192.168.100.0/24`, ## Обновления +Ноды обновляются из `origin` своего `/opt/virtuality/source` — по умолчанию https://git.stackworks.ru/viktor/virtuality.git. Проект переехал туда с GitHub в сентябре 2026 года; ноду со старым адресом GitHub `update_core.migrate_remote()` и `migrate_update_remote` в `scripts/update_target.sh` переключают на новый перед `git fetch`. + 1. **Канал** (`VIRTUALITY_UPDATE_CHANNEL` в `web.env`): `stable` — последний тег `vX.Y.Z` (пока тегов нет — ветка `main`); `main` — ветка `main`. Логика одна и та же в `web/update_core.py` и `scripts/update_target.sh`. 2. **Проверка** (`/update/check`, ночной `scripts/auto_update_check.sh`): `git fetch --tags`, сравнение текущего коммита с целевым; обновление предлагается, только если целевой коммит — потомок текущего (нода никогда не откатывается сама). Список изменений берётся из `updates/versions.json` целевой версии. -3. **Применение** (`scripts/apply_github_update.sh`, запускается через `systemd-run`): `git checkout -B main <цель>` и `git clean`; если git недоступен — скачивание ZIP-архива с GitHub и `rsync`; затем `scripts/install_web_panel.sh` (панель копируется в `web.new`, старая переименовывается в `web.prev`, venv обновляется) и `systemctl restart virtuality-web`. Состояние — `/var/lib/virtuality/update/state.json`, журнал — `/var/log/virtuality/update.log`. +3. **Применение** (`scripts/apply_github_update.sh`, запускается через `systemd-run`): `git checkout -B main <цель>` и `git clean`; если git недоступен — скачивание ZIP-архива того же репозитория (`<адрес>/archive/<ветка или тег>.zip`, так отдают и Gitea, и GitHub) и `rsync`; затем `scripts/install_web_panel.sh` (панель копируется в `web.new`, старая переименовывается в `web.prev`, venv обновляется) и `systemctl restart virtuality-web`. Состояние — `/var/lib/virtuality/update/state.json`, журнал — `/var/log/virtuality/update.log`. 4. **Откат**: `virtuality-ctl rollback` меняет местами `web` и `web.prev`. Установщики и скрипты вне `web/`: @@ -169,4 +171,4 @@ image/build-iso.sh, autoinstall.yaml, files/, grub/ сборка ISO ## Тесты и CI -`tests/conftest.py` подменяет `run_cmd`, так что `virsh`, `systemctl`, `qemu-img` и остальные команды имитируются — тесты идут на любой машине без libvirt. `tests/dev_server.py` поднимает демо-панель с теми же подменами. `make check` = pyflakes + shellcheck + pytest; `.github/workflows/ci.yml` запускает это на Python 3.11–3.14, `.github/workflows/image.yml` собирает ISO. +`tests/conftest.py` подменяет `run_cmd`, так что `virsh`, `systemctl`, `qemu-img` и остальные команды имитируются — тесты идут на любой машине без libvirt. `tests/dev_server.py` поднимает демо-панель с теми же подменами. `make check` = pyflakes + shellcheck + pytest, запускается перед каждым пушем. `.github/workflows/ci.yml` (то же на Python 3.11–3.14) и `.github/workflows/image.yml` (сборка ISO) заработают в Gitea Actions, когда будет подключён раннер. diff --git a/docs/IMAGE.md b/docs/IMAGE.md index 2302572..3a81eee 100644 --- a/docs/IMAGE.md +++ b/docs/IMAGE.md @@ -20,13 +20,7 @@ ## Где взять готовый образ -Workflow `.github/workflows/image.yml` собирает ISO в GitHub Actions: - -- автоматически при выпуске — пуш тега `v*` (amd64 и arm64); -- при pull request, который меняет `image/**`, `scripts/virtuality_firstboot.sh`, `web/requirements.txt` или сам workflow; -- вручную: Actions → **Build ISO image** → Run workflow → архитектура `amd64`, `arm64` или `both`. - -После сборки workflow проверяет, что в образе есть загрузочная запись UEFI, `autoinstall.yaml` разбирается как YAML, а в `grub.cfg` есть пункт Virtuality. Образы Ubuntu Server больше лимита GitHub Releases (2 ГБ на файл), поэтому ISO публикуются как **artifacts** запуска (`virtuality-iso-amd64`, `virtuality-iso-arm64`) и хранятся 30 дней. В артефакте — `.iso` и `.sha256`. +Образ собирается вручную командой `make iso` (см. ниже): раннер Actions в Gitea не подключён. Workflow `.github/workflows/image.yml` остаётся в репозитории и заработает, как только раннер появится: он собирает ISO по тегу `v*` и вручную, проверяет загрузочную запись UEFI, `autoinstall.yaml` и пункт Virtuality в `grub.cfg` и сохраняет `.iso` и `.sha256` как artifacts запуска. --- @@ -41,7 +35,7 @@ sudo apt install -y xorriso git curl python3-pip Сборка: ```bash -git clone https://github.com/viktor138irk/virtuality.git +git clone https://git.stackworks.ru/viktor/virtuality.git cd virtuality make iso # amd64 make iso ARCH=arm64 # ARM64-серверы с UEFI @@ -50,8 +44,8 @@ make iso ARCH=arm64 # ARM64-серверы с UEFI `image/build-iso.sh` сам находит последний Ubuntu 26.04.x live-server ISO, скачивает его (около 3 ГБ), проверяет SHA256 и кладёт результат в `dist/`: ```text -dist/virtuality-0.11.0-ubuntu-26.04-amd64.iso -dist/virtuality-0.11.0-ubuntu-26.04-amd64.iso.sha256 +dist/virtuality-0.11.1-ubuntu-26.04-amd64.iso +dist/virtuality-0.11.1-ubuntu-26.04-amd64.iso.sha256 ``` Скачанный ISO Ubuntu кешируется в `.cache/iso` (или в `VIRTUALITY_ISO_CACHE`). Уже скачанный образ можно передать явно: @@ -113,7 +107,7 @@ make iso ISO_ARGS="--unattended --username admin --password-hash '$(openssl pass ## Что спрашивает установщик -Меню загрузки: **Установить Virtuality 0.11.0** (выбирается сам через 10 секунд), тот же пункт с новым ядром HWE, загрузка с другого диска, настройки UEFI, проверка памяти. +Меню загрузки: **Установить Virtuality 0.11.1** (выбирается сам через 10 секунд), тот же пункт с новым ядром HWE, загрузка с другого диска, настройки UEFI, проверка памяти. Сценарий `image/autoinstall.yaml` оставляет интерактивными только три экрана установщика Ubuntu: @@ -165,14 +159,14 @@ cat /var/log/virtuality/firstboot.log Сверьте контрольную сумму скачанного или собранного образа: ```bash -sha256sum -c virtuality-0.11.0-ubuntu-26.04-amd64.iso.sha256 +sha256sum -c virtuality-0.11.1-ubuntu-26.04-amd64.iso.sha256 ``` Убедиться, что образ загрузочный и содержит нужные файлы (то же делает CI): ```bash -xorriso -indev virtuality-0.11.0-ubuntu-26.04-amd64.iso -report_el_torito plain 2>/dev/null | grep UEFI -xorriso -osirrox on -indev virtuality-0.11.0-ubuntu-26.04-amd64.iso -extract /autoinstall.yaml autoinstall.yaml +xorriso -indev virtuality-0.11.1-ubuntu-26.04-amd64.iso -report_el_torito plain 2>/dev/null | grep UEFI +xorriso -osirrox on -indev virtuality-0.11.1-ubuntu-26.04-amd64.iso -extract /autoinstall.yaml autoinstall.yaml ``` --- @@ -186,7 +180,7 @@ xorriso -osirrox on -indev virtuality-0.11.0-ubuntu-26.04-amd64.iso -extract /au - **Linux**: balenaEtcher или `dd`, где `/dev/sdX` — устройство флешки (проверьте через `lsblk`): ```bash - sudo dd if=dist/virtuality-0.11.0-ubuntu-26.04-amd64.iso of=/dev/sdX bs=4M status=progress conv=fsync + sudo dd if=dist/virtuality-0.11.1-ubuntu-26.04-amd64.iso of=/dev/sdX bs=4M status=progress conv=fsync ``` Загрузитесь с флешки (меню загрузки — обычно F8, F11, F12 или Esc при включении), выберите **Установить Virtuality**. diff --git a/docs/REQUIREMENTS.md b/docs/REQUIREMENTS.md index ca500f7..d5bd658 100644 --- a/docs/REQUIREMENTS.md +++ b/docs/REQUIREMENTS.md @@ -59,7 +59,7 @@ VIRTUALITY_SKIP_REQUIREMENTS=1 # отключить проверки совс Например, для тестового стенда с маленьким диском: ```bash -curl -fsSL https://raw.githubusercontent.com/viktor138irk/virtuality/main/install.sh | VIRTUALITY_MIN_VAR_FREE_MB=8192 bash +curl -fsSL https://git.stackworks.ru/viktor/virtuality/raw/branch/main/install.sh | VIRTUALITY_MIN_VAR_FREE_MB=8192 bash ``` Установка с ISO эти пороги не применяет: диск и память проверяются мастером настройки, который покажет предупреждение, но не остановит установку. diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 1c5171f..8d17eff 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -6,7 +6,7 @@ Virtuality — панель для одного домашнего сервер **Установка и настройка** -- Установочный ISO на базе Ubuntu Server 26.04 LTS (amd64, arm64), сборка `make iso` и в GitHub Actions, полностью автоматический режим. +- Установочный ISO на базе Ubuntu Server 26.04 LTS (amd64, arm64), сборка `make iso`, полностью автоматический режим. - Первая загрузка: панель ставится первой и показывает ход установки в браузере, при ошибке продолжает сама. - Мастер настройки: часовой пояс, пароль, HTTPS, сеть машин (NAT или домашняя сеть с автооткатом), отдельный диск под машины, обновления. - Страница «Настройки», команда `virtuality-ctl`, установка одной командой на Ubuntu 24.04 / 26.04 и Debian 13. diff --git a/image/build-iso.sh b/image/build-iso.sh index 22a396e..c8a79db 100755 --- a/image/build-iso.sh +++ b/image/build-iso.sh @@ -21,7 +21,7 @@ UBUNTU_ISO="" CACHE_DIR="${VIRTUALITY_ISO_CACHE:-${REPO_ROOT}/.cache/iso}" OUTPUT_DIR="${REPO_ROOT}/dist" GIT_REF="HEAD" -REPO_URL="https://github.com/viktor138irk/virtuality.git" +REPO_URL="https://git.stackworks.ru/viktor/virtuality.git" WEB_PORT="8088" AUTO_UPDATE="1" UNATTENDED="0" @@ -47,7 +47,7 @@ Options: --ref GIT_REF Git ref of Virtuality to bundle (default: ${GIT_REF}) --repo-url URL Update remote configured on installed nodes (default: ${REPO_URL}) --web-port PORT Web panel port on installed nodes (default: ${WEB_PORT}) - --no-auto-update Disable the nightly GitHub auto-update on installed nodes + --no-auto-update Disable the nightly auto-update on installed nodes --locale LOCALE Installer/system locale (default: ${LOCALE}) --keyboard LAYOUT Keyboard layout (default: ${KEYBOARD}) --timezone TZ System timezone (default: ${TIMEZONE} — detected by the installer; the wizard can change it) diff --git a/image/files/virtuality-firstboot.service b/image/files/virtuality-firstboot.service index 9129c5c..6a56498 100644 --- a/image/files/virtuality-firstboot.service +++ b/image/files/virtuality-firstboot.service @@ -1,6 +1,6 @@ [Unit] Description=Virtuality first boot setup -Documentation=https://github.com/viktor138irk/virtuality/blob/main/docs/IMAGE.md +Documentation=https://git.stackworks.ru/viktor/virtuality/src/branch/main/docs/IMAGE.md Wants=network-online.target After=network-online.target ConditionPathExists=!/var/lib/virtuality/.firstboot-done diff --git a/install.sh b/install.sh index 9e0fd6a..bf92117 100644 --- a/install.sh +++ b/install.sh @@ -6,7 +6,7 @@ set -euo pipefail # Public install entrypoint for clean Ubuntu/Debian servers. # ========================================================== -SELF_INSTALL_URL="${VIRTUALITY_INSTALL_URL:-https://raw.githubusercontent.com/viktor138irk/virtuality/main/install.sh}" +SELF_INSTALL_URL="${VIRTUALITY_INSTALL_URL:-https://git.stackworks.ru/viktor/virtuality/raw/branch/main/install.sh}" if [[ "${EUID}" -ne 0 ]]; then if command -v sudo >/dev/null 2>&1; then @@ -27,7 +27,7 @@ if [[ "${EUID}" -ne 0 ]]; then exit 1 fi -REPO_URL="${VIRTUALITY_REPO_URL:-https://github.com/viktor138irk/virtuality.git}" +REPO_URL="${VIRTUALITY_REPO_URL:-https://git.stackworks.ru/viktor/virtuality.git}" DEFAULT_INSTALL_USER="${SUDO_USER:-root}" INSTALL_USER="${VIRTUALITY_USER:-$DEFAULT_INSTALL_USER}" PROJECT_BASE_DIR="${VIRTUALITY_PROJECT_BASE_DIR:-/opt/virtuality}" diff --git a/scripts/apply_github_update.sh b/scripts/apply_github_update.sh index 711c438..94ff835 100644 --- a/scripts/apply_github_update.sh +++ b/scripts/apply_github_update.sh @@ -10,11 +10,11 @@ BRANCH="${VIRTUALITY_UPDATE_BRANCH:-main}" TMP_DIR="${STATE_DIR}/tmp" # shellcheck source=scripts/update_target.sh . "$(dirname "${BASH_SOURCE[0]}")/update_target.sh" +mkdir -p "$STATE_DIR" "$TMP_DIR" "$(dirname "$LOG_FILE")" +MOVED_REMOTE="$(migrate_update_remote)" resolve_update_target REPO_ZIP_URL="${VIRTUALITY_UPDATE_ZIP_URL:-$TARGET_ZIP_URL}" -mkdir -p "$STATE_DIR" "$TMP_DIR" "$(dirname "$LOG_FILE")" - now() { date '+%Y-%m-%d %H:%M:%S'; } json_escape() { python3 -c 'import json,sys; print(json.dumps(sys.stdin.read())[1:-1])'; } write_state() { @@ -58,7 +58,7 @@ fetch_zip_update() { log "Fallback update via ZIP started" log "ZIP URL: $REPO_ZIP_URL" - write_state "running" "GitHub git fetch не сработал, пробуем ZIP fallback" + write_state "running" "git fetch не сработал, пробуем скачать ZIP-архив" if command -v curl >/dev/null 2>&1; then if ! try_run curl -L --connect-timeout 20 --max-time 240 --retry 3 --retry-delay 3 -o "$zip_file" "$REPO_ZIP_URL"; then @@ -111,7 +111,8 @@ fetch_zip_update() { STARTED_AT="$(now)" write_state "running" "Обновление запущено" log "============================================================" -log "Virtuality GitHub update started" +log "Virtuality update started" +[ -n "$MOVED_REMOTE" ] && log "$MOVED_REMOTE" log "source: $SOURCE_DIR" log "remote: $REMOTE" log "branch: $BRANCH" @@ -129,7 +130,7 @@ log "before version: $BEFORE_VERSION" GIT_UPDATED=0 if [ -d "$SOURCE_DIR/.git" ]; then cd "$SOURCE_DIR" || exit 1 - write_state "running" "Получаем изменения из GitHub через git" + write_state "running" "Получаем изменения через git" if try_run git fetch --tags --force "$REMOTE" "$BRANCH"; then resolve_update_target log "channel: ${UPDATE_CHANNEL}, target: ${TARGET_LABEL}" @@ -140,7 +141,7 @@ if [ -d "$SOURCE_DIR/.git" ]; then log "Local changes before update:" printf '%s\n' "$LOCAL_STATUS" >> "$LOG_FILE" fi - write_state "running" "Синхронизируем исходники с GitHub через git reset" + write_state "running" "Синхронизируем исходники через git reset" log "Deploy mode: git checkout -B ${BRANCH} ${TARGET_LABEL}" if try_run git checkout -q -f -B "$BRANCH" "$TARGET_COMMIT" && try_run git clean -fd; then GIT_UPDATED=1 @@ -159,7 +160,7 @@ fi if [ "$GIT_UPDATED" != "1" ]; then if ! fetch_zip_update; then - write_state "error" "Не удалось обновиться ни через git, ни через ZIP fallback. Проверь доступ к github.com, curl/wget/unzip и DNS." + write_state "error" "Не удалось обновиться ни через git, ни через ZIP fallback. Проверьте доступ сервера к ${REPO_ZIP_URL%%/archive/*}, curl/wget/unzip и DNS." exit 1 fi fi @@ -180,6 +181,6 @@ run_step bash scripts/install_web_panel.sh write_state "running" "Перезапускаем virtuality-web" run_step systemctl restart virtuality-web -log "Virtuality GitHub update finished successfully" +log "Virtuality update finished successfully" write_state "success" "Обновление завершено: ${BEFORE_VERSION} → ${AFTER_VERSION}" exit 0 diff --git a/scripts/auto_update_check.sh b/scripts/auto_update_check.sh index 0d9ba61..1af109b 100644 --- a/scripts/auto_update_check.sh +++ b/scripts/auto_update_check.sh @@ -35,10 +35,14 @@ JSON } diag_network() { + local web repo_host + web="$(repo_web_url)" + repo_host="${web#https://}" + repo_host="${repo_host%%/*}" log "diagnostics: host=$(hostname 2>/dev/null || echo unknown)" log "diagnostics: date=$(date -Is 2>/dev/null || date)" if command -v getent >/dev/null 2>&1; then - getent hosts github.com >> "$LOG_FILE" 2>&1 || log "diagnostics: DNS github.com failed" + getent hosts "$repo_host" >> "$LOG_FILE" 2>&1 || log "diagnostics: DNS $repo_host failed" fi if command -v git >/dev/null 2>&1; then git --version >> "$LOG_FILE" 2>&1 || true @@ -47,7 +51,7 @@ diag_network() { log "diagnostics: git not found" fi if command -v curl >/dev/null 2>&1; then - curl -I -L --connect-timeout 10 --max-time 25 https://github.com/ >> "$LOG_FILE" 2>&1 || log "diagnostics: curl github.com failed" + curl -I -L --connect-timeout 10 --max-time 25 "$web" >> "$LOG_FILE" 2>&1 || log "diagnostics: curl $repo_host failed" else log "diagnostics: curl not found" fi @@ -89,6 +93,9 @@ diag_network() { current="unknown" fi + moved="$(migrate_update_remote)" + [ -n "$moved" ] && log "$moved" + if [ -d "$SOURCE_DIR/.git" ]; then if git fetch --quiet --tags --force "$REMOTE" "$BRANCH" >> "$LOG_FILE" 2>&1; then resolve_update_target diff --git a/scripts/update_target.sh b/scripts/update_target.sh index 9bfa11b..3f9dc59 100644 --- a/scripts/update_target.sh +++ b/scripts/update_target.sh @@ -4,6 +4,10 @@ # main — every change on the main branch # Needs SOURCE_DIR, REMOTE and BRANCH; sets UPDATE_CHANNEL, TARGET_REF, TARGET_LABEL, TARGET_ZIP_URL. +# Where the project lives. Nodes installed before the move from GitHub are switched over by migrate_update_remote. +DEFAULT_REPO_URL="https://git.stackworks.ru/viktor/virtuality.git" +LEGACY_REPO_URLS=("https://github.com/viktor138irk/virtuality.git" "https://github.com/viktor138irk/virtuality") + update_channel() { local value="${VIRTUALITY_UPDATE_CHANNEL:-}" if [[ -z "$value" && -f /var/lib/virtuality/config/web.env ]]; then @@ -12,18 +16,44 @@ update_channel() { [[ "$value" == "main" ]] && echo main || echo stable } +remote_url() { + git -C "$SOURCE_DIR" remote get-url "$REMOTE" 2>/dev/null || true +} + +migrate_update_remote() { + [[ -d "${SOURCE_DIR}/.git" ]] || return 0 + local url legacy + url="$(remote_url)" + for legacy in "${LEGACY_REPO_URLS[@]}"; do + if [[ "$url" == "$legacy" ]]; then + git -C "$SOURCE_DIR" remote set-url "$REMOTE" "$DEFAULT_REPO_URL" && echo "update remote moved: $url -> $DEFAULT_REPO_URL" + return 0 + fi + done +} + +# Web address of the repository for ZIP downloads: the remote itself when it is https, otherwise the default. +# Gitea and GitHub both serve /archive/.zip. +repo_web_url() { + local url="" + [[ -d "${SOURCE_DIR}/.git" ]] && url="$(remote_url)" + [[ "$url" == https://* ]] || url="$DEFAULT_REPO_URL" + echo "${url%.git}" +} + resolve_update_target() { - local tag="" + local tag="" web + web="$(repo_web_url)" UPDATE_CHANNEL="$(update_channel)" TARGET_REF="${REMOTE}/${BRANCH}" TARGET_LABEL="${BRANCH}" - TARGET_ZIP_URL="https://github.com/viktor138irk/virtuality/archive/refs/heads/${BRANCH}.zip" + TARGET_ZIP_URL="${web}/archive/${BRANCH}.zip" if [[ "$UPDATE_CHANNEL" == "stable" && -d "${SOURCE_DIR}/.git" ]]; then tag="$(git -C "$SOURCE_DIR" tag --list 'v[0-9]*' --sort=-v:refname | grep -E '^v[0-9]+(\.[0-9]+)*$' | head -n1 || true)" if [[ -n "$tag" ]]; then TARGET_REF="refs/tags/${tag}" TARGET_LABEL="$tag" - TARGET_ZIP_URL="https://github.com/viktor138irk/virtuality/archive/refs/tags/${tag}.zip" + TARGET_ZIP_URL="${web}/archive/${tag}.zip" fi fi } diff --git a/tests/test_core.py b/tests/test_core.py index 4f8f23f..e86905b 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -4,6 +4,7 @@ from pathlib import Path import pytest import network_core +from conftest import ROOT import restore_network import update_core @@ -104,6 +105,59 @@ def test_update_not_offered_when_local_is_newer(tmp_path, monkeypatch, data_dirs assert update_core.check_updates(fetch=True)["has_update"] is True +GITEA = "https://git.stackworks.ru/viktor/virtuality" + + +def make_source(path): + import subprocess + + def git(*args): + return subprocess.run(["git", "-c", "user.name=t", "-c", "user.email=t@t", *args], cwd=path, check=True, capture_output=True, text=True).stdout.strip() + + path.mkdir() + git("init", "-q", "-b", "main") + git("commit", "-q", "--allow-empty", "-m", "base") + git("remote", "add", "origin", "https://github.com/viktor138irk/virtuality.git") + return git + + +def test_migrate_remote_moves_only_the_old_github_address(tmp_path, monkeypatch, data_dirs): + git = make_source(tmp_path / "source") + monkeypatch.setattr(update_core, "SOURCE_DIR", tmp_path / "source") + assert update_core.migrate_remote() is True + assert git("remote", "get-url", "origin") == GITEA + ".git" + assert update_core.migrate_remote() is False + git("remote", "set-url", "origin", "https://example.org/me/fork.git") + assert update_core.migrate_remote() is False + assert git("remote", "get-url", "origin") == "https://example.org/me/fork.git" + + +def test_update_target_script_downloads_from_gitea(tmp_path): + import os + import subprocess + + source = tmp_path / "source" + git = make_source(source) + + def target(channel, script="resolve_update_target"): + env = {**os.environ, "VIRTUALITY_UPDATE_CHANNEL": channel} + code = f'SOURCE_DIR="{source}"; REMOTE=origin; BRANCH=main; . "{ROOT}/scripts/update_target.sh"; {script}; echo "$TARGET_LABEL $TARGET_ZIP_URL"' + return subprocess.run(["bash", "-c", code], env=env, check=True, capture_output=True, text=True).stdout.strip().splitlines() + + moved, result = target("main", "migrate_update_remote; resolve_update_target") + assert moved == f"update remote moved: https://github.com/viktor138irk/virtuality.git -> {GITEA}.git" + assert result == f"main {GITEA}/archive/main.zip" + assert git("remote", "get-url", "origin") == GITEA + ".git" + git("tag", "v0.11.1") + assert target("stable") == [f"v0.11.1 {GITEA}/archive/v0.11.1.zip"] + assert target("main") == [f"main {GITEA}/archive/main.zip"] + # A fork downloads from itself; a non-https remote falls back to the project's address. + git("remote", "set-url", "origin", "https://example.org/me/fork.git") + assert target("stable", "migrate_update_remote; resolve_update_target") == ["v0.11.1 https://example.org/me/fork/archive/v0.11.1.zip"] + git("remote", "set-url", "origin", "git@example.org:me/fork.git") + assert target("stable") == [f"v0.11.1 {GITEA}/archive/v0.11.1.zip"] + + def test_stable_channel_follows_release_tags(tmp_path, monkeypatch, data_dirs): import subprocess diff --git a/web/templates/update.html b/web/templates/update.html index 1abecb6..01a0acb 100644 --- a/web/templates/update.html +++ b/web/templates/update.html @@ -41,7 +41,7 @@ - {% if not info.fetch_ok %}{{ alert('warning', 'Не удалось связаться с GitHub: ' ~ (info.fetch_error or 'нет ответа') ~ '. Проверьте доступ сервера в интернет.', 'Проверка не удалась') }}{% endif %} + {% if not info.fetch_ok %}{{ alert('warning', 'Не удалось связаться с сервером обновлений: ' ~ (info.fetch_error or 'нет ответа') ~ '. Проверьте доступ сервера в интернет.', 'Проверка не удалась') }}{% endif %}

Что нового

{{ 'Изменения, которые вы получите' if info.missing_versions else 'Последние изменения' }}

diff --git a/web/update_core.py b/web/update_core.py index e146127..4539a69 100644 --- a/web/update_core.py +++ b/web/update_core.py @@ -14,6 +14,9 @@ DEFAULT_BRANCH = os.environ.get('VIRTUALITY_UPDATE_BRANCH', 'main') REMOTE = os.environ.get('VIRTUALITY_UPDATE_REMOTE', 'origin') NODE_ENV = Path(os.environ.get('VIRTUALITY_NODE_ENV', '/var/lib/virtuality/config/web.env')) CHANNELS = {'stable': 'Стабильный — выпущенные версии', 'main': 'Ранний доступ — все изменения'} +# Проект переехал с GitHub в Gitea StackWorks; ноды со старым адресом переключаются сами (как и scripts/update_target.sh). +DEFAULT_REPO_URL = 'https://git.stackworks.ru/viktor/virtuality.git' +LEGACY_REPO_URLS = ('https://github.com/viktor138irk/virtuality.git', 'https://github.com/viktor138irk/virtuality') class UpdateError(Exception): @@ -193,6 +196,13 @@ def git_log_between(base: str, head: str, limit: int = 50) -> list[dict[str, str return rows +def migrate_remote() -> bool: + result = run_cmd(['git', 'remote', 'get-url', REMOTE], cwd=SOURCE_DIR) + if not result['ok'] or result['stdout'] not in LEGACY_REPO_URLS: + return False + return run_cmd(['git', 'remote', 'set-url', REMOTE, DEFAULT_REPO_URL], cwd=SOURCE_DIR)['ok'] + + def check_updates(fetch: bool = True) -> dict[str, Any]: ensure_state_dir() if not source_ready(): @@ -200,6 +210,7 @@ def check_updates(fetch: bool = True) -> dict[str, Any]: fetch_result = {'ok': True, 'stdout': '', 'stderr': '', 'cmd': 'skip'} if fetch: + migrate_remote() fetch_result = run_cmd(['git', 'fetch', '--quiet', '--tags', '--force', REMOTE, DEFAULT_BRANCH], cwd=SOURCE_DIR, timeout=60) channel = update_channel()