Проект переехал в Gitea StackWorks: обновления, установка одной командой и ISO берут код с git.stackworks.ru, запасной ZIP скачивается оттуда же, ноды со старым адресом GitHub переключаются сами

Этот коммит содержится в:
2026-09-27 15:31:43 +09:00
родитель 83511d6b09
Коммит 440d8fc562
15 изменённых файлов: 150 добавлений и 49 удалений
+5 -2
Просмотреть файл
@@ -7,6 +7,9 @@ Virtuality — простая замена Proxmox для дома и малог
**Общайся с владельцем проекта по-русски.** Весь текст интерфейса — на простом русском, без жаргона **Общайся с владельцем проекта по-русски.** Весь текст интерфейса — на простом русском, без жаргона
(термины вроде qcow2, libvirt, nvram — только в раскрывающихся блоках «Для специалистов»). (термины вроде 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, образы дисков, сеть, журналы, обновления, консоль). - `web/app.py` — основное приложение FastAPI (машины, ISO, образы дисков, сеть, журналы, обновления, консоль).
@@ -55,8 +58,8 @@ make iso # ISO в dist/ (нужны xorriso, git, curl, p
- Работаем прямо в `main` (так решил владелец). Перед каждым пушем — `make check`; не пушить - Работаем прямо в `main` (так решил владелец). Перед каждым пушем — `make check`; не пушить
недоделанное: ноды на канале «Ранний доступ» (`main`) подтягивают main каждую ночь. недоделанное: ноды на канале «Ранний доступ» (`main`) подтягивают main каждую ночь.
- Узлы на канале `stable` (по умолчанию) обновляются только до тегов `vX.Y.Z`. Релиз: поднять `VERSION`, - Узлы на канале `stable` (по умолчанию) обновляются только до тегов `vX.Y.Z`. Релиз: поднять `VERSION`,
добавить запись в `updates/versions.json`, запушить, поставить тег `vX.Y.Z` на main — тег же запускает добавить запись в `updates/versions.json`, запушить, поставить тег `vX.Y.Z` на main. Раннера Actions нет:
сборку ISO в GitHub Actions (artifacts `virtuality-iso-amd64/arm64`). ISO собирается вручную `make iso`, CI заменяет `make check` перед пушем.
- Секреты и пароли не логировать; пароль cloud-init хранится только как SHA-512 crypt. - Секреты и пароли не логировать; пароль cloud-init хранится только как SHA-512 crypt.
- Не возвращать удалённое: Cockpit, консольный dashboard, эмуляцию ARM64 на x86, PXE, смену - Не возвращать удалённое: Cockpit, консольный dashboard, эмуляцию ARM64 на x86, PXE, смену
архитектуры машины, скрипты bootstrap/clean_install/setup_bridge_br0/create_test_vm. архитектуры машины, скрипты bootstrap/clean_install/setup_bridge_br0/create_test_vm.
+8 -9
Просмотреть файл
@@ -2,7 +2,7 @@
**Virtuality** — простая замена Proxmox для дома и малого офиса. Один сервер, виртуальные машины и понятная панель в браузере на русском языке. Внутри — проверенные KVM, QEMU и libvirt, снаружи — интерфейс, в котором не нужна командная строка. **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 не нужен): - **Собрать самому** на любой Linux-машине (root не нужен):
```bash ```bash
sudo apt install -y xorriso git curl python3-pip 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 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). Дальше: записать образ на флешку (Rufus, balenaEtcher или `dd`), загрузиться с неё, выбрать **Установить Virtuality**, ответить на три экрана установщика и дождаться перезагрузки. Через минуту после неё панель уже открывается в браузере — адрес показан на экране сервера. Все подробности, параметры сборки и полностью автоматическая установка — в [docs/IMAGE.md](docs/IMAGE.md).
@@ -87,7 +86,7 @@
Подходит для уже установленного Ubuntu Server 24.04 / 26.04 или Debian 13. Работает и под `root`, и под обычным пользователем с `sudo`: Подходит для уже установленного Ubuntu Server 24.04 / 26.04 или Debian 13. Работает и под `root`, и под обычным пользователем с `sudo`:
```bash ```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 <пользователь>`. Скрипт проверит требования, поставит пакеты KVM/QEMU/libvirt, команду `vhealth` и панель, а в конце покажет адрес панели. Логин — тот пользователь, от которого запущена установка (через `sudo` — ваш пользователь, под `root` — `root`); пароль — его пароль Linux. Если пароль ещё не задан: `sudo passwd <пользователь>`.
@@ -105,7 +104,7 @@ VIRTUALITY_INSTALL_VERBOSE=1 # показывать вывод команд вм
Пример: Пример:
```bash ```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. При первом входе в панель откроется мастер настройки — тот же, что после установки с ISO.
@@ -116,7 +115,7 @@ curl -fsSL https://raw.githubusercontent.com/viktor138irk/virtuality/main/instal
```bash ```bash
sudo mkdir -p /opt/virtuality 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 cd /opt/virtuality/source
sudo bash install_virtuality_node.sh # пакеты KVM/QEMU/libvirt, пулы, firewall sudo bash install_virtuality_node.sh # пакеты KVM/QEMU/libvirt, пулы, firewall
sudo bash scripts/install_healthcheck_command.sh # команда sudo vhealth 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`. - **Автоматически**: таймер `virtuality-auto-update.timer` проверяет обновления каждую ночь около 4:00 по времени сервера. Включается и выключается в «Настройках» или командой `sudo virtuality-ctl set VIRTUALITY_AUTO_UPDATE=0`.
- **Каналы**: `stable` — только выпущенные версии (последний тег `vX.Y.Z`; пока тегов нет, стабильный канал следует ветке `main`), `main` — все изменения ветки `main`. Нода никогда не откатывается на более старую версию сама. - **Каналы**: `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`. - **Откат**: перед каждым обновлением предыдущая версия панели сохраняется в `/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). Устройство кода описано в [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 тоже понимает, они заработают, если раннер появится.
--- ---
+4 -2
Просмотреть файл
@@ -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`. 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` целевой версии. 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`. 4. **Откат**: `virtuality-ctl rollback` меняет местами `web` и `web.prev`.
Установщики и скрипты вне `web/`: Установщики и скрипты вне `web/`:
@@ -169,4 +171,4 @@ image/build-iso.sh, autoinstall.yaml, files/, grub/ сборка ISO
## Тесты и CI ## Тесты и 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: Образ собирается вручную командой `make iso` (см. ниже): раннер Actions в Gitea не подключён. Workflow `.github/workflows/image.yml` остаётся в репозитории и заработает, как только раннер появится: он собирает ISO по тегу `v*` и вручную, проверяет загрузочную запись UEFI, `autoinstall.yaml` и пункт Virtuality в `grub.cfg` и сохраняет `.iso` и `.sha256` как artifacts запуска.
- автоматически при выпуске — пуш тега `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`.
--- ---
@@ -41,7 +35,7 @@ sudo apt install -y xorriso git curl python3-pip
Сборка: Сборка:
```bash ```bash
git clone https://github.com/viktor138irk/virtuality.git git clone https://git.stackworks.ru/viktor/virtuality.git
cd virtuality cd virtuality
make iso # amd64 make iso # amd64
make iso ARCH=arm64 # ARM64-серверы с UEFI 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/`: `image/build-iso.sh` сам находит последний Ubuntu 26.04.x live-server ISO, скачивает его (около 3 ГБ), проверяет SHA256 и кладёт результат в `dist/`:
```text ```text
dist/virtuality-0.11.0-ubuntu-26.04-amd64.iso dist/virtuality-0.11.1-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.sha256
``` ```
Скачанный ISO Ubuntu кешируется в `.cache/iso` (или в `VIRTUALITY_ISO_CACHE`). Уже скачанный образ можно передать явно: Скачанный 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: Сценарий `image/autoinstall.yaml` оставляет интерактивными только три экрана установщика Ubuntu:
@@ -165,14 +159,14 @@ cat /var/log/virtuality/firstboot.log
Сверьте контрольную сумму скачанного или собранного образа: Сверьте контрольную сумму скачанного или собранного образа:
```bash ```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): Убедиться, что образ загрузочный и содержит нужные файлы (то же делает CI):
```bash ```bash
xorriso -indev virtuality-0.11.0-ubuntu-26.04-amd64.iso -report_el_torito plain 2>/dev/null | grep UEFI 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.0-ubuntu-26.04-amd64.iso -extract /autoinstall.yaml autoinstall.yaml 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`): - **Linux**: balenaEtcher или `dd`, где `/dev/sdX` — устройство флешки (проверьте через `lsblk`):
```bash ```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**. Загрузитесь с флешки (меню загрузки — обычно F8, F11, F12 или Esc при включении), выберите **Установить Virtuality**.
+1 -1
Просмотреть файл
@@ -59,7 +59,7 @@ VIRTUALITY_SKIP_REQUIREMENTS=1 # отключить проверки совс
Например, для тестового стенда с маленьким диском: Например, для тестового стенда с маленьким диском:
```bash ```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 эти пороги не применяет: диск и память проверяются мастером настройки, который покажет предупреждение, но не остановит установку. Установка с ISO эти пороги не применяет: диск и память проверяются мастером настройки, который покажет предупреждение, но не остановит установку.
+1 -1
Просмотреть файл
@@ -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 или домашняя сеть с автооткатом), отдельный диск под машины, обновления. - Мастер настройки: часовой пояс, пароль, HTTPS, сеть машин (NAT или домашняя сеть с автооткатом), отдельный диск под машины, обновления.
- Страница «Настройки», команда `virtuality-ctl`, установка одной командой на Ubuntu 24.04 / 26.04 и Debian 13. - Страница «Настройки», команда `virtuality-ctl`, установка одной командой на Ubuntu 24.04 / 26.04 и Debian 13.
+2 -2
Просмотреть файл
@@ -21,7 +21,7 @@ UBUNTU_ISO=""
CACHE_DIR="${VIRTUALITY_ISO_CACHE:-${REPO_ROOT}/.cache/iso}" CACHE_DIR="${VIRTUALITY_ISO_CACHE:-${REPO_ROOT}/.cache/iso}"
OUTPUT_DIR="${REPO_ROOT}/dist" OUTPUT_DIR="${REPO_ROOT}/dist"
GIT_REF="HEAD" GIT_REF="HEAD"
REPO_URL="https://github.com/viktor138irk/virtuality.git" REPO_URL="https://git.stackworks.ru/viktor/virtuality.git"
WEB_PORT="8088" WEB_PORT="8088"
AUTO_UPDATE="1" AUTO_UPDATE="1"
UNATTENDED="0" UNATTENDED="0"
@@ -47,7 +47,7 @@ Options:
--ref GIT_REF Git ref of Virtuality to bundle (default: ${GIT_REF}) --ref GIT_REF Git ref of Virtuality to bundle (default: ${GIT_REF})
--repo-url URL Update remote configured on installed nodes (default: ${REPO_URL}) --repo-url URL Update remote configured on installed nodes (default: ${REPO_URL})
--web-port PORT Web panel port on installed nodes (default: ${WEB_PORT}) --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}) --locale LOCALE Installer/system locale (default: ${LOCALE})
--keyboard LAYOUT Keyboard layout (default: ${KEYBOARD}) --keyboard LAYOUT Keyboard layout (default: ${KEYBOARD})
--timezone TZ System timezone (default: ${TIMEZONE} — detected by the installer; the wizard can change it) --timezone TZ System timezone (default: ${TIMEZONE} — detected by the installer; the wizard can change it)
+1 -1
Просмотреть файл
@@ -1,6 +1,6 @@
[Unit] [Unit]
Description=Virtuality first boot setup 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 Wants=network-online.target
After=network-online.target After=network-online.target
ConditionPathExists=!/var/lib/virtuality/.firstboot-done ConditionPathExists=!/var/lib/virtuality/.firstboot-done
+2 -2
Просмотреть файл
@@ -6,7 +6,7 @@ set -euo pipefail
# Public install entrypoint for clean Ubuntu/Debian servers. # 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 [[ "${EUID}" -ne 0 ]]; then
if command -v sudo >/dev/null 2>&1; then if command -v sudo >/dev/null 2>&1; then
@@ -27,7 +27,7 @@ if [[ "${EUID}" -ne 0 ]]; then
exit 1 exit 1
fi 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}" DEFAULT_INSTALL_USER="${SUDO_USER:-root}"
INSTALL_USER="${VIRTUALITY_USER:-$DEFAULT_INSTALL_USER}" INSTALL_USER="${VIRTUALITY_USER:-$DEFAULT_INSTALL_USER}"
PROJECT_BASE_DIR="${VIRTUALITY_PROJECT_BASE_DIR:-/opt/virtuality}" PROJECT_BASE_DIR="${VIRTUALITY_PROJECT_BASE_DIR:-/opt/virtuality}"
+9 -8
Просмотреть файл
@@ -10,11 +10,11 @@ BRANCH="${VIRTUALITY_UPDATE_BRANCH:-main}"
TMP_DIR="${STATE_DIR}/tmp" TMP_DIR="${STATE_DIR}/tmp"
# shellcheck source=scripts/update_target.sh # shellcheck source=scripts/update_target.sh
. "$(dirname "${BASH_SOURCE[0]}")/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 resolve_update_target
REPO_ZIP_URL="${VIRTUALITY_UPDATE_ZIP_URL:-$TARGET_ZIP_URL}" 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'; } now() { date '+%Y-%m-%d %H:%M:%S'; }
json_escape() { python3 -c 'import json,sys; print(json.dumps(sys.stdin.read())[1:-1])'; } json_escape() { python3 -c 'import json,sys; print(json.dumps(sys.stdin.read())[1:-1])'; }
write_state() { write_state() {
@@ -58,7 +58,7 @@ fetch_zip_update() {
log "Fallback update via ZIP started" log "Fallback update via ZIP started"
log "ZIP URL: $REPO_ZIP_URL" 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 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 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)" STARTED_AT="$(now)"
write_state "running" "Обновление запущено" write_state "running" "Обновление запущено"
log "============================================================" log "============================================================"
log "Virtuality GitHub update started" log "Virtuality update started"
[ -n "$MOVED_REMOTE" ] && log "$MOVED_REMOTE"
log "source: $SOURCE_DIR" log "source: $SOURCE_DIR"
log "remote: $REMOTE" log "remote: $REMOTE"
log "branch: $BRANCH" log "branch: $BRANCH"
@@ -129,7 +130,7 @@ log "before version: $BEFORE_VERSION"
GIT_UPDATED=0 GIT_UPDATED=0
if [ -d "$SOURCE_DIR/.git" ]; then if [ -d "$SOURCE_DIR/.git" ]; then
cd "$SOURCE_DIR" || exit 1 cd "$SOURCE_DIR" || exit 1
write_state "running" "Получаем изменения из GitHub через git" write_state "running" "Получаем изменения через git"
if try_run git fetch --tags --force "$REMOTE" "$BRANCH"; then if try_run git fetch --tags --force "$REMOTE" "$BRANCH"; then
resolve_update_target resolve_update_target
log "channel: ${UPDATE_CHANNEL}, target: ${TARGET_LABEL}" log "channel: ${UPDATE_CHANNEL}, target: ${TARGET_LABEL}"
@@ -140,7 +141,7 @@ if [ -d "$SOURCE_DIR/.git" ]; then
log "Local changes before update:" log "Local changes before update:"
printf '%s\n' "$LOCAL_STATUS" >> "$LOG_FILE" printf '%s\n' "$LOCAL_STATUS" >> "$LOG_FILE"
fi fi
write_state "running" "Синхронизируем исходники с GitHub через git reset" write_state "running" "Синхронизируем исходники через git reset"
log "Deploy mode: git checkout -B ${BRANCH} ${TARGET_LABEL}" 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 if try_run git checkout -q -f -B "$BRANCH" "$TARGET_COMMIT" && try_run git clean -fd; then
GIT_UPDATED=1 GIT_UPDATED=1
@@ -159,7 +160,7 @@ fi
if [ "$GIT_UPDATED" != "1" ]; then if [ "$GIT_UPDATED" != "1" ]; then
if ! fetch_zip_update; 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 exit 1
fi fi
fi fi
@@ -180,6 +181,6 @@ run_step bash scripts/install_web_panel.sh
write_state "running" "Перезапускаем virtuality-web" write_state "running" "Перезапускаем virtuality-web"
run_step systemctl restart 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}" write_state "success" "Обновление завершено: ${BEFORE_VERSION} → ${AFTER_VERSION}"
exit 0 exit 0
+9 -2
Просмотреть файл
@@ -35,10 +35,14 @@ JSON
} }
diag_network() { 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: host=$(hostname 2>/dev/null || echo unknown)"
log "diagnostics: date=$(date -Is 2>/dev/null || date)" log "diagnostics: date=$(date -Is 2>/dev/null || date)"
if command -v getent >/dev/null 2>&1; then 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 fi
if command -v git >/dev/null 2>&1; then if command -v git >/dev/null 2>&1; then
git --version >> "$LOG_FILE" 2>&1 || true git --version >> "$LOG_FILE" 2>&1 || true
@@ -47,7 +51,7 @@ diag_network() {
log "diagnostics: git not found" log "diagnostics: git not found"
fi fi
if command -v curl >/dev/null 2>&1; then 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 else
log "diagnostics: curl not found" log "diagnostics: curl not found"
fi fi
@@ -89,6 +93,9 @@ diag_network() {
current="unknown" current="unknown"
fi fi
moved="$(migrate_update_remote)"
[ -n "$moved" ] && log "$moved"
if [ -d "$SOURCE_DIR/.git" ]; then if [ -d "$SOURCE_DIR/.git" ]; then
if git fetch --quiet --tags --force "$REMOTE" "$BRANCH" >> "$LOG_FILE" 2>&1; then if git fetch --quiet --tags --force "$REMOTE" "$BRANCH" >> "$LOG_FILE" 2>&1; then
resolve_update_target resolve_update_target
+33 -3
Просмотреть файл
@@ -4,6 +4,10 @@
# main — every change on the main branch # main — every change on the main branch
# Needs SOURCE_DIR, REMOTE and BRANCH; sets UPDATE_CHANNEL, TARGET_REF, TARGET_LABEL, TARGET_ZIP_URL. # 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() { update_channel() {
local value="${VIRTUALITY_UPDATE_CHANNEL:-}" local value="${VIRTUALITY_UPDATE_CHANNEL:-}"
if [[ -z "$value" && -f /var/lib/virtuality/config/web.env ]]; then if [[ -z "$value" && -f /var/lib/virtuality/config/web.env ]]; then
@@ -12,18 +16,44 @@ update_channel() {
[[ "$value" == "main" ]] && echo main || echo stable [[ "$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() { resolve_update_target() {
local tag="" local tag="" web
web="$(repo_web_url)"
UPDATE_CHANNEL="$(update_channel)" UPDATE_CHANNEL="$(update_channel)"
TARGET_REF="${REMOTE}/${BRANCH}" TARGET_REF="${REMOTE}/${BRANCH}"
TARGET_LABEL="${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 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)" 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 if [[ -n "$tag" ]]; then
TARGET_REF="refs/tags/${tag}" TARGET_REF="refs/tags/${tag}"
TARGET_LABEL="$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
fi fi
} }
+54
Просмотреть файл
@@ -4,6 +4,7 @@ from pathlib import Path
import pytest import pytest
import network_core import network_core
from conftest import ROOT
import restore_network import restore_network
import update_core 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 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): def test_stable_channel_follows_release_tags(tmp_path, monkeypatch, data_dirs):
import subprocess import subprocess
+1 -1
Просмотреть файл
@@ -41,7 +41,7 @@
</article> </article>
<div class="alert info" data-update-running hidden>{{ icon('refresh-cw') }}<div><strong>Идёт обновление</strong><span data-update-state></span></div></div> <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"> <article class="card">
<div class="card-header"><div><h2>Что нового</h2><p>{{ 'Изменения, которые вы получите' if info.missing_versions else 'Последние изменения' }}</p></div></div> <div class="card-header"><div><h2>Что нового</h2><p>{{ 'Изменения, которые вы получите' if info.missing_versions else 'Последние изменения' }}</p></div></div>
+11
Просмотреть файл
@@ -14,6 +14,9 @@ DEFAULT_BRANCH = os.environ.get('VIRTUALITY_UPDATE_BRANCH', 'main')
REMOTE = os.environ.get('VIRTUALITY_UPDATE_REMOTE', 'origin') REMOTE = os.environ.get('VIRTUALITY_UPDATE_REMOTE', 'origin')
NODE_ENV = Path(os.environ.get('VIRTUALITY_NODE_ENV', '/var/lib/virtuality/config/web.env')) NODE_ENV = Path(os.environ.get('VIRTUALITY_NODE_ENV', '/var/lib/virtuality/config/web.env'))
CHANNELS = {'stable': 'Стабильный — выпущенные версии', 'main': 'Ранний доступ — все изменения'} 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): class UpdateError(Exception):
@@ -193,6 +196,13 @@ def git_log_between(base: str, head: str, limit: int = 50) -> list[dict[str, str
return rows 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]: def check_updates(fetch: bool = True) -> dict[str, Any]:
ensure_state_dir() ensure_state_dir()
if not source_ready(): 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'} fetch_result = {'ok': True, 'stdout': '', 'stderr': '', 'cmd': 'skip'}
if fetch: if fetch:
migrate_remote()
fetch_result = run_cmd(['git', 'fetch', '--quiet', '--tags', '--force', REMOTE, DEFAULT_BRANCH], cwd=SOURCE_DIR, timeout=60) fetch_result = run_cmd(['git', 'fetch', '--quiet', '--tags', '--force', REMOTE, DEFAULT_BRANCH], cwd=SOURCE_DIR, timeout=60)
channel = update_channel() channel = update_channel()