Проект переехал в Gitea StackWorks: обновления, установка одной командой и ISO берут код с git.stackworks.ru, запасной ZIP скачивается оттуда же, ноды со старым адресом GitHub переключаются сами
Этот коммит содержится в:
+5
-2
@@ -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.
|
||||
|
||||
+8
-9
@@ -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 тоже понимает, они заработают, если раннер появится.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -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, когда будет подключён раннер.
|
||||
|
||||
+9
-15
@@ -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**.
|
||||
|
||||
@@ -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 эти пороги не применяет: диск и память проверяются мастером настройки, который покажет предупреждение, но не остановит установку.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
+2
-2
@@ -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}"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 <repo>/archive/<branch or tag>.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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
</article>
|
||||
|
||||
<div class="alert info" data-update-running hidden>{{ icon('refresh-cw') }}<div><strong>Идёт обновление</strong><span data-update-state></span></div></div>
|
||||
{% 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 %}
|
||||
|
||||
<article class="card">
|
||||
<div class="card-header"><div><h2>Что нового</h2><p>{{ 'Изменения, которые вы получите' if info.missing_versions else 'Последние изменения' }}</p></div></div>
|
||||
|
||||
@@ -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()
|
||||
|
||||
Ссылка в новой задаче
Block a user