Оновлення та міграція¶
Цей посібник -- оператор-протокол безпечного оновлення. Схема БД рухається тільки вперед -- автоматичної down-міграції немає. Якщо треба повернутись назад, відновлюйтесь із резервної копії.
Завжди робіть backup
data/(або томаbamdude_dataу Docker) перед будь-яким оновленням. А саме:bamdude.db(або dump PostgreSQL, якщо у вас PG), директоріяarchive/(3MF + мініатюри) та директоріяlibrary/.
1. Чек-ліст перед оновленням¶
Перед тим, як щось чіпати:
- Зупиніть сервіс BamDude (
sudo systemctl stop bamdudeабоdocker compose down). - Зробіть backup директорії з даними -- див. команди backup нижче.
- Запишіть свою поточну версію -- відкрийте
/system/healthу браузері або виконайтеcat pyproject.toml | grep versionдля нативних інсталяцій. Корисно, якщо доведеться відкочуватись. - Якщо ви за reverse proxy (nginx / Caddy / Traefik), скопіюйте конфіг убік, щоб перевірити його після оновлення.
- Перевірте розмір логів -- якщо
data/logs/величезна, це гарний момент її зротейтити.
Команди backup¶
# Том даних -- sqlite БД, архіви, мініатюри, аплоади
docker run --rm \
-v bamdude_data:/from \
-v "$(pwd)/backup":/to \
alpine tar czf /to/bamdude-data-$(date +%Y%m%d).tar.gz -C /from .
# Логи (опційно)
docker run --rm \
-v bamdude_logs:/from \
-v "$(pwd)/backup":/to \
alpine tar czf /to/bamdude-logs-$(date +%Y%m%d).tar.gz -C /from .
Відкрийте Налаштування → Backup → Локальна резервна копія → Створити резервну копію, потім Завантажити резервну копію, щоб зберегти zip собі на комп. Zip пакує SQLite БД, директорію архіву, мініатюри, аплоади та конфіг у тому самому layout-і, який install.sh розкладає на диску -- відновлення -- це просто "розпакувати в шлях інсталяції та перезапустити". Він також захоплює метадані ключа шифрування та стан запланованих бекапів, які tar сирого data/ лишає позаду.
2. Процедура оновлення -- Docker¶
cd bamdude
docker compose pull # якщо запіноване на :latest
docker compose up -d
docker compose logs -f # дивитись, як застосовуються міграції
Пінити конкретний тег у compose.yaml -- це ок і навіть рекомендовано для стабільних інсталяцій -- :0.4.1 ніколи не зрушить; :latest йде за main.
# Запіноване, рекомендовано
image: ghcr.io/kainpl/bamdude:0.4.1
# Rolling, йде за main
image: ghcr.io/kainpl/bamdude:latest
Слідкуйте за стартовим логом для прогресу міграцій. Довгі міграції логують пакетний прогрес (напр. m020 library_files: progress на m020/m022). Чекайте на "Migrations complete", перш ніж тестувати.
INFO [backend.app.migrations] Applying m019_archive_queue_batch_error
INFO [backend.app.migrations] Applied m019 (version 19)
INFO [backend.app.migrations] Applying m022_label_object_metadata_backfill
INFO [backend.app.migrations] m022 library_files: progress 100/847
INFO [backend.app.migrations] m022 library_files: progress 200/847
...
INFO [backend.app.migrations] Applied m022 (version 22)
INFO [backend.app.main] Startup complete
Sanity-check /system/health повертає 200.
3. Процедура оновлення -- Manual (Python venv)¶
cd /opt/bamdude
sudo systemctl stop bamdude
# Підтягнути сорси
sudo -u bamdude git fetch
sudo -u bamdude git checkout v0.4.1 # або який там тег
# Python-залежності
sudo -u bamdude ./venv/bin/pip install -r requirements.txt --upgrade
# Frontend-бандл (регенерує треканий каталог static/).
# Пропустіть цей крок, якщо завжди тягнете pre-built теги -- бандл їде
# in-tree на release-коміті. Потрібно лише якщо збираєте з кастомної гілки.
sudo -u bamdude bash -c 'cd frontend && npm ci && npm run build'
# Перезапустити та хвостити логи
sudo systemctl start bamdude
sudo journalctl -u bamdude -f
Поставлений install/update.sh автоматизує всю послідовність (stop → backup → git pull → pip install → npm build → start) і підтримує env-перевизначення:
| Змінна | Дефолт | Призначення |
|---|---|---|
INSTALL_DIR |
/opt/bamdude |
Де живе BamDude |
SERVICE_NAME |
bamdude |
systemd-юніт для рестарту |
BRANCH |
поточна вичекаутена гілка | Перемкнутись на іншу гілку під час оновлення |
BACKUP_MODE |
auto |
auto пропускає, коли нема чого бекапити, require обриває, якщо backup впав, skip вимикає |
FORCE |
0 |
Виставити 1, щоб обійти перевірки dirty-worktree / backup |
4. Процедура оновлення -- In-app апдейтер (сторінка Інформація)¶
Для native-інсталів найпростіший шлях -- in-app апдейтер. У боковому меню пункт Інформація (route /system) → Check for updates показує latest реліз; кнопка Install Update під ним виконує повну послідовність (git fetch --tags, git reset --hard refs/tags/<release>, pip install, npm run build) не виходячи з UI.
Toggles на тій самій панелі:
- Check for updates automatically -- вимкни щоб перестати polling-увати GitHub.
- Include beta updates -- показувати
vX.Y.ZbNрелізи теж. За замовчуванням off. Апдейтер поважає або version-name convention, або GitHub-prerelease-флаг -- реліз явно marked-prerelease у GitHub UI теж сховається за цей toggle.
Docker-інсталам in-app Install Update відмовлено -- запуск git fetch / pip install / npm build у запущеному контейнері зіпсує image. Замість того панель surface-ить дві бокові секції з resolved-тегом pre-filled: image-based (pull && up -d з правильним рядком image:, з beta-специфічним hint що :latest бети не трекає), і source-build (git fetch --tags && git checkout v<tag> && compose build --pull && up -d). Скопіюй один із блоків залежно від install-shape -- повний reference у System Info → Update checker.
In-app апдейтер захищений від pre-release tag mismatch, у який 0.4.3-та-раніше тихо потрапляли: клік Install Update на бета-релізі тихо no-op'ив, бо apply path робив hard-reset до
origin/main, що не несе бета-тег. Виправлено у 0.4.4.
5. Огляд міграцій -- що міняє кожна версія¶
BamDude трекає застосовані міграції в таблиці _migrations. Кожен реліз запускає всі очікуючі версії по порядку при першому завантаженні. Нові інсталяції спочатку запускають create_all() (створюючи таблиці з поточних означень моделей), потім m000 + m001 пре-проштамповуються як застосовані через bootstrap-крок, і фактично виконуються тільки пізніші міграції.
Міграції, помічені seed, містять DML-крок (бекфіл / нормалізація даних) і можуть займати помітний час на великих інсталяціях; чисто-DDL міграції (додавання колонок, swap FK) завершуються за мілісекунди.
| Версія | Назва | Що змінює | Seed | Вперше потрібна в |
|---|---|---|---|---|
| m000 | bambuddy_to_bamdude_301 |
Інертна з 0.5.6 -- не робить нічого. Раніше імпортувала спадковий bambuddy.db / bambutrack.db. Запис версії 0 збережений, бо bootstrap-крок штампує його на кожній наявній інсталяції. Повідомлення про файл Bambuddy -- це стартова перевірка, а не ця міграція: міграція виконується один раз, а повідомлення має повторюватись, поки файл лежить на місці. |
no | -- |
| m001 | bamdude_baseline |
Створює FTS-індекс для пошуку по архівах (FTS5 на SQLite, tsvector + GIN на PostgreSQL) та засіює початкові довідкові дані (каталог моделей принтерів, дефолтні групи тощо). | yes | Свіжі інсталяції BamDude |
| m002 | bamdude_311 |
Bump схеми BamDude 3.0.1 → 3.1.1. Додає printer_queues, macros, колонки swap mode, конфіг stagger, таблиці історії обслуговування, переробку черги (queue_id), printer_models на типах обслуговування. Дропає мертву таблицю filaments. |
yes | Оновлення з BamDude 3.0.x |
| m003 | enforce_admin_user |
Кодифікує always-on модель авторизації: штампує auth_enabled=true + setup_completed=true, якщо існує хоча б один admin; інакше чистить обидва прапорці, щоб наступне завантаження направило користувача через /setup. Схема не змінюється. |
yes | Усі інсталяції |
| m004 | m002_reconcile |
Перезапускає m002.upgrade() дослівно. Ловить інсталяції, що застрягли на ранній версії m002 (до правила frozen-migrations), де версія була позначена як застосована, але пізніші правки m002 ніколи не виконались. |
yes | Застряглі post-3.1.1 інсталяції |
| m005 | swap_profiles |
Друга вимірність на swap mode: printers.swap_profile + macros.swap_profile. Перепривʼязує наявні вбудовані макроси A1 Mini до swap_profile='a1mini_kit'; засіває порожні вбудовані для a1mini_stl + jobox-a1. |
yes | Усі інсталяції |
| m006 | mesh_mode_fast_check |
Додає print_queue.mesh_mode_fast_check BOOLEAN DEFAULT 1, щоб оператор міг відмовитись від bed-mesh fast-check probe для кожного елемента черги. |
no | Усі інсталяції |
| m007 | drop_vibration_cali |
Дропає print_queue.vibration_cali (Bambu Studio тепер хардкодить це у false для кожної моделі; живе тільки в калібрувальному візарді). MQTT-payload усе ще емітить ключ для сумісності з прошивкою. |
no | Усі інсталяції |
| m008 | swap_macro_queue_fields |
Додає print_queue.execute_swap_macros BOOLEAN DEFAULT 1 + swap_macro_events TEXT (JSON array), щоб кожен елемент черги міг перевизначити, які swap-події для нього стріляють. |
no | Усі інсталяції |
| m009 | archive_source_hash |
Додає print_archives.source_content_hash (SHA256 непатченого джерела) + applied_patches (JSON). Запити дедупу перемикаються на COALESCE(source_content_hash, content_hash), щоб BamDude-патчені архіви дедупились проти оригіналів з бібліотеки. |
no | Усі інсталяції |
| m010 | queue_reliability |
Додає print_archives.subtask_id VARCHAR(64) (advisory archive matching через перезапуски) + printers.awaiting_plate_clear BOOLEAN DEFAULT 0 (персистоване plate-clear ворота, переживає Auto Off power-cycle). |
no | Усі інсталяції |
| m011 | cloud_region |
Додає users.cloud_region VARCHAR(10), щоб per-user облікові дані Bambu Cloud несли свій регіон. Закриває cross-tenant витік регіону, який мав singleton-сервіс. |
no | Усі інсталяції |
| m012 | mfa |
Кластер MFA / 2FA / OIDC -- шість нових таблиць: user_totp, user_otp_codes, auth_ephemeral_tokens, auth_rate_limit_events, oidc_providers, user_oidc_links, плюс users.password_changed_at. Підтримує always-on модель авторизації з 0.4.0. |
no | 0.4.0 |
| m013 | library_file_print_count |
Додає library_files.print_count INTEGER DEFAULT 0. Per-file лічильник завершених друків, інкрементується в on_print_complete. |
no | 0.4.0 |
| m014 | archive_library_link |
Додає print_archives.library_file_id FK (ON DELETE SET NULL) + бекфілить його на кожному наявному архіві шляхом hash-метчингу проти library_files.file_hash. Перераховує library_files.print_count та last_printed_at з історії завершених архівів (перезаписує попередні значення -- історія архівів є авторитативною). |
yes | 0.4.0 |
| m015 | refresh_token_support |
Додає auth_ephemeral_tokens.used_at + family_id для підтримки sliding-session refresh-флоу (§18.14). Reuse-detection відкликає всю фемілі, якщо refresh-токен реплейнуть. |
no | 0.4.0 |
| m016 | project_print_plan |
Створює project_print_plan_items (per-project упорядкований список .3mf-файлів зі степером копій). Бекфілить один рядок на кожен наявний linked library_files.project_id з copies=1. |
yes | 0.4.0 |
| m017 | macro_action_type |
Додає macros.action_type + mqtt_action + delay_seconds. Дозволяє макросу інвокувати MQTT-команду (chamber_light_off, chamber_light_on) замість gcode на події print_started / print_finished з опційною затримкою. |
no | 0.4.0 |
| m018 | queue_library_fk_set_null |
Змінює FK print_queue.library_file_id з ON DELETE CASCADE на ON DELETE SET NULL. У комбінації з in-app каскадом у delete_file це дає SQLite ту саму поведінку, що PostgreSQL отримує нативно. |
no | 0.4.0 |
| m019 | archive_queue_batch_error |
Рефакторинг queue↔archive. Додає print_archives.queue_id (FK, indexed) + batch_id (VARCHAR(36), indexed) + error_message (TEXT). Дропає чотири кешовані лічильники з printer_queues (completed_count / failed_count / cancelled_count / total_count). Бекфілить queue_id/batch_id/error_message з наявних посилань print_queue.archive_id. Видаляє завершені елементи черги, що мають архівне посилання -- бекфіл-еквівалент нового авточищення в on_print_complete. |
yes | 0.4.0 |
| m020 | spool_purchase_date |
Додає три колонки в spool: purchase_date DATETIME, filament_diameter VARCHAR(8) NOT NULL DEFAULT '1.75', lot INTEGER. Бекфілить filament_diameter у '1.75' (дефолт Bambu). |
yes | 0.4.0 (post-b2) |
| m021 | drop_auto_light_off |
Дропає спадкову колонку printers.auto_light_off. Замінено фреймворком макросів (сконфігуруйте mqtt-action макрос chamber_light_off на події print_started для того самого ефекту, плюс опційний симетричний chamber_light_on на print_finished). |
no | 0.4.0 |
| m022 | label_object_metadata_backfill |
Відкриває кожен наявний на диску 3MF, витягає gcode_label_objects + exclude_object з Metadata/project_settings.config, вмерджує їх у library_files.file_metadata та print_archives.extra_data. Довгий старт на першому завантаженні, якщо у вас багато архівів -- див. §5 Помітні шляхи оновлення. |
yes | 0.4.1 |
| m023 | per_plate_metadata_backfill |
Відкриває кожен 3MF на диску ще раз і серіалізує повний per-plate breakdown (plates[] payload + is_multi_plate flag) у ті ж самі JSON-колонки library_files.file_metadata та print_archives.extra_data. Це робить можливою per-plate galleryв File Manager + multi-plate UI у PrintModal без перевідкривання 3MF на кожен запит списку. Той самий профіль довгого старту, що й m022 — запускається один раз. |
yes | 0.4.1 |
5. Помітні шляхи оновлення¶
З Bambuddy HE 3.0.x → BamDude 0.4.x¶
Ваш bambuddy.db -- це власний файл BamDude: стартове перейменування робить із нього bamdude.db, m002 адаптує схему, m005+ -- BamDude-нативні. (Видалення імпорту Bambuddy 2.2.2 у 0.5.6 цього не стосується -- воно завжди було лише про файли апстріму.)
Завжди оновлюйтесь до 0.4.0.1 або пізніше
Перехід зі спадкової інсталяції 3.0.1 одразу на 0.4.0 падав на m005_swap_profiles.seed() з no such column: printers.awaiting_plate_clear -- seed використовував ORM-овий select(Printer), який підвантажував кожну колонку з поточної моделі, включно з колонками, яких на момент m005 у ланцюжку ще не існує. Виправлено в 0.4.0.1 переписуванням seed на raw SQL з явними списками колонок.
Перехід з Bambuddy¶
Не підтримується з 0.5.6. BamDude відгалузився від Bambuddy на 2.2.2, і схеми розійшлись надто далеко, щоб одноразовому імпорту можна було довіряти, тож імпортер прибрано.
Стартуйте BamDude з порожньою текою даних і додайте принтери та котушки заново.
bambuddy.db, залишений у теці даних, не читається і не чіпається. Стартап BamDude називає його в лозі на кожному старті, тож ви бачите, що файл знайдено й проігноровано; видаліть його самі, коли він більше не потрібен.
Це не те саме, що оновлення з Bambuddy HE / BamDude 3.0.x
Це власна лінія BamDude, і вона й далі підтримується -- див. Помітні шляхи оновлення вище. bambuddy.db, записаний BamDude 3.0.1, розпізнається як власний файл BamDude і перейменовується на bamdude.db, точно як раніше.
Зміна способу інсталяції¶
Можна змінити спосіб інсталяції в будь-який момент без зачіпання даних -- просто наведіть нову інстанцію на існуючу директорію data/ або скопіюйте вміст тому.
Native → Docker¶
sudo systemctl stop bamdude
# Скопіюйте native-дані в Docker-том
docker volume create bamdude_data
docker run --rm \
-v /opt/bamdude/data:/from \
-v bamdude_data:/to \
alpine cp -a /from/. /to/
# Стартуйте GHCR-образ проти нового тому
docker run -d --name bamdude --network host \
-v bamdude_data:/app/data -v bamdude_logs:/app/logs \
--restart unless-stopped ghcr.io/kainpl/bamdude:latest
# Лише після того, як ви переконались, що Docker-інстанція працює, вимкніть/видаліть native-сервіс:
sudo systemctl disable bamdude
Docker → Native¶
docker compose down
# Скопіюйте том на диск
docker run --rm \
-v bamdude_data:/from \
-v "$(pwd)/extracted":/to \
alpine cp -a /from/. /to/
# Встановіть native, націлений на витягнуті дані
sudo ./install/install.sh --data-dir "$(pwd)/extracted" --yes
Docker Hub → GHCR (або навпаки)¶
Лише обмін реєстром, дані не торкаємо:
# docker-compose.yml
# image: kainpl/bamdude:latest ← Docker Hub
# image: ghcr.io/kainpl/bamdude:latest ← GitHub Container Registry
docker compose pull
docker compose up -d
Обидва реєстри публікують ті самі теги. GHCR -- основне джерело (білдиться в CI на кожному релізі); Docker Hub -- дзеркало.
6. Перевірка після оновлення¶
Коли сервіс знову на ходу:
/system/healthповертає 200.- Параметри → Система → версія відображає новий реліз.
- Підключіться до принтера, який працював до оновлення -- має реконектнутись за 30 секунд; перевірте картку принтера на сторінці Принтери.
- Відкрийте кілька найновіших архівів -- мініатюри мають усе ще рендеритись, 3D-перегляд -- працювати, клік на іконку принтера -- стрибати на принтер-власник.
- Тригерніть диспатч на двох принтерах одночасно -- тост у нижньому правому куті має показати, як обидва завдання прогресять паралельно. Фаза DB-insert ненадовго серіалізована (startup-lock), але FTP-завантаження + старт відбуваються одночасно. Див. Черги для кожного принтера → Поведінка диспатчу.
- Перелогіньтесь (якщо оновлюєтесь з 0.3.x → 0.4.x), щоб видали refresh-token cookie і перебрав на себе sliding-session флоу.
Фрагменти лог міграції, які корисно грепнути:
INFO [backend.app.migrations] Applied m019 (version 19)
INFO [backend.app.migrations] Applied m022 (version 22)
INFO [backend.app.main] Startup complete
Індикатори падіння:
ERROR [backend.app.migrations] Migration mXXX failed: ...
sqlite3.OperationalError: no such column: ...
no such column / no such table на старті майже завжди означає, що міграція не виконалась -- зазвичай це проблема дозволів файлової системи на data/. Полагодьте через sudo chown -R bamdude:bamdude /opt/bamdude/data і перезапустіть.
7. Відкат (якщо щось зламалось)¶
Оскільки схема рухається тільки вперед, план відкату завжди -- відновіть до-оновлювальний backup. Автоматичної down-міграції немає -- ви не можете, наприклад, "відмінити" m019 archive↔queue рефакторинг на місці. Відновлення з backup -- єдиний шлях.
docker compose down
# Витерти вміст нового тома
docker volume rm bamdude_data
docker volume create bamdude_data
# Відновити з backup-тарбола
docker run --rm \
-v "$(pwd)/backup":/from \
-v bamdude_data:/to \
alpine sh -c 'cd /to && tar xzf /from/bamdude-data-YYYYMMDD.tar.gz'
# Запіньте compose на попередній Docker-тег перед стартом:
# image: ghcr.io/kainpl/bamdude:0.4.0
docker compose up -d
На свіжій інсталяції старого тегу, після first-run setup, відкрийте Налаштування → Backup → Локальна резервна копія, Завантажте скачаний zip, потім перезапустіть сервіс. Zip відновлює БД + архіви + аплоади + конфіг одним заходом.
Версія, на яку ви відкочуєтесь, має бути тією, що створила backup -- інакше схема в БД буде новіша за те, що очікує цей код, і старт зафейлиться помилкою column-not-found на першому ж читанні.
Forward-only -- це навмисно
Down-міграції потребували б шляхів коду, яких BamDude не несе, -- відновлення з backup структурно простіше і завжди коректне. Docker-тег :0.4.0 лишається запіненим безстроково, тож ви завжди можете відкотитись на нього.
8. Нотатки про backend БД¶
SQLite (дефолт)¶
Файл БД живе за data/bamdude.db. Pragma SQLite: WAL journal, 15 с busy timeout, NORMAL synchronous. WAL означає, що поруч із головним файлом є ще bamdude.db-wal та bamdude.db-shm -- бекапте всі три разом (або зупиніть сервіс перед цим, щоб WAL було чекпойнтнуто в головний файл).
Якщо bambuddy.db (або bambutrack.db), записаний BamDude 3.0.1, існує в директорії даних, а bamdude.db -- ні, BamDude розпізнає його як власний і перейменовує на bamdude.db при першому завантаженні, перед тим як запуститься хоч одна міграція -- саме так нативні інсталяції, що міняють бінарник на місці, переносять свої дані далі. Файл апстрім-Bambuddy не перейменовується і не читається -- див. Перехід з Bambuddy.
PostgreSQL — вбудований або власний¶
DATABASE_URL має три стани, і перемикання між ними — це теж міграція:
DATABASE_URL |
Бекенд |
|---|---|
| порожньо / не задано | SQLite у data/bamdude.db |
embedded |
PostgreSQL 18, який іде разом із BamDude, у DATA_DIR/postgres/18 |
postgresql+asyncpg://… |
ваш власний сервер |
Наявні PG-інсталяції запускають той самий ланцюжок міграцій на кожному старті — та сама таблиця _migrations, ті самі версії, та сама послідовність. Хелпери діалекту маршрутизують DDL через PG-нативні шляхи там, де SQLite потребує recreate_table (зміни FK, дроп колонок), а PG ензорсить FK-обмеження, які SQLite пропускає мовчки (m018 — хороший приклад, де SET NULL реально спрацьовує лише на PG).
Перехід зі SQLite на PostgreSQL¶
Це одноразове автоматичне копіювання, однакове для вбудованого сервера і для власного.
- Спершу зробіть резервну копію (див. Команди backup). Це єдиний крок, який ніхто за вас не зробить.
- Задайте
DATABASE_URL—embeddedабо URL вашого сервера. Для зовнішнього сервера база вже має існувати; BamDude створює таблиці, а не бази. - Перезапустіть BamDude.
На цьому старті BamDude бачить PostgreSQL без даних (свіжу базу) поруч із наявним bamdude.db і переносить усе. У журналі:
Found local SQLite database at .../bamdude.db, migrating to PostgreSQL
...
SQLite -> PostgreSQL migration complete (78 tables). Original renamed to bamdude.db.migrated
Далі проти PostgreSQL відпрацьовує звичайний ланцюжок міграцій, і застосунок піднімається. На даних реальної ферми саме копіювання займає близько хвилини-двох; довше зазвичай іде ланцюжок схеми після нього.
Що саме переноситься
Усі таблиці й рядки, з конвертацією типів (SQLite 0/1 → boolean, рядки дат → timestamps). Послідовності автоінкременту скидаються у правильні значення, повнотекстовий індекс перебудовується як tsvector + GIN у PostgreSQL. Віртуальні таблиці FTS5, файли WAL/SHM і облік міграцій не копіюються — вони або специфічні для SQLite, або створюються наново.
Рядки-сироти видаляються, і це пишеться в журнал
PostgreSQL ензорсить зовнішні ключі, яких SQLite ніколи не перевіряв, тож рядки, що вказують на вже неіснуючі записи, перенести неможливо. Імпортер їх чистить і прямо каже, що саме видалив, наприклад:
Purging 3 orphan label_jobs rows before import
Purging 5 orphan spool_usage_history rows
Purging 51 orphan smart_plug_energy_snapshots rows
Так задумано — ці рядки й так були недосяжні. Але прочитайте ці рядки: якщо кількість виглядає дивно для вашої інсталяції, зупиніться й відновіться з резервної копії, а не йдіть далі.
Невдалий імпорт зупиняє старт, а не продовжує його
Якщо копіювання зламалося на півдорозі, BamDude зупиняється з помилкою, а не продовжує як свіжа інсталяція. PostgreSQL лишається як є, а bamdude.db не перейменовується, тож ваші дані у SQLite цілі: достатньо прибрати DATABASE_URL і перезапуститись, щоб опинитись рівно там, де були. (Старіші збірки могли мовчки продовжити з порожньою базою — саме тому перейменування відбувається лише після повністю успішного копіювання.)
Повернення на SQLite¶
Приберіть DATABASE_URL (або лишіть порожнім) і перейменуйте файл назад:
Усе, що з'явилося після переходу, живе лише в PostgreSQL, тож якщо це треба зберегти — спершу зробіть резервну копію з працюючого PostgreSQL. Формат копії з інтерфейсу переносний і відновлюється на будь-який бекенд.
Вбудований сервер (DATABASE_URL=embedded) під час оновлень¶
| Шлях | Що містить |
|---|---|
DATA_DIR/postgres/18/ |
кластер, у каталозі з назвою за мажором PostgreSQL |
DATA_DIR/postgres/password |
згенерований пароль (права 0600) |
DATA_DIR/postgres/port |
порт, на якому він піднявся, якщо ви не запінили EMBEDDED_PG_PORT |
Копіюйте це разом із рештою data/ — кластер це просто файли у вашому каталозі даних, тож tar каталогу data/ при зупиненому сервісі є валідною копією. Для переносної копії, яка відновиться на будь-який бекенд, користуйтесь Налаштування → Резервні копії.
Мажор PostgreSQL зафіксований
BamDude відмовиться відкривати кластер, створений іншим мажором PostgreSQL, і скаже про це прямо, не чіпаючи дані:
the data directory ... was created by PostgreSQL 17, but the bundled server is
PostgreSQL 18. Refusing to start: a major upgrade needs a migration step, not a
silent open.
Новий мажор вийде окремим релізом із явним кроком міграції. Звичайні оновлення BamDude у межах того самого мажора не потребують від вас нічого.
Оновлюйтесь при зупиненому сервері
На нативній інсталяції pip install -r requirements.txt не зможе замінити пакет вбудованого PostgreSQL, поки з нього працює сервер — зупиніть BamDude (він зупинить і сервер) перед оновленням залежностей. install/update.sh уже зупиняє службу першим кроком.
Windows: дві схеми служб¶
Windows-інсталятор пропонує вбудований сервер або окремою службою BamDudePostgres (менеджер служб піднімає її перед BamDude), або дочірнім процесом BamDude. Повторний запуск інсталятора лишає ту схему, якою ви вже користуєтесь — він читає поточний бекенд із середовища встановленої служби й передвибирає його, тож оновлення ніколи не поверне вас мовчки на SQLite. Деінсталяція зупиняє й видаляє обидві служби і окремо питає (за замовчуванням Ні), чи видаляти ваші дані.
Docker¶
DATABASE_URL=embeddedпіднімає вбудований сервер усередині контейнера BamDude, а його кластер лежить у наявному томіbamdude_dataв підкаталозіpostgres/. У штатному compose-файлі наdocker compose downдається 60 с, щоб він устиг зробити контрольну точку — не скорочуйте це значення.- Для PostgreSQL в окремому контейнері є штатний override
docker-compose.postgres.yml. Зверніть увагу на застереження про host-мережу в Підтримці PostgreSQL: зі штатнимnetwork_mode: hostBamDude не дістане інший контейнер за іменем сервісу Compose.
9. Персистентність даних -- "новий контейнер запустився порожнім"¶
Найпоширеніша катастрофа під час оновлення -- запустити свіжий BamDude-контейнер і виявити, що немає принтерів, немає архіву, немає налаштувань -- наче чиста інсталяція. Дані майже ніколи не пропадають насправді; вони все ще в Docker volume або container layer'і, з якого новий екземпляр просто не читає. Цей розділ покриває кожну причину, яку ми бачили, і фікс для кожної.
Швидка діагностика¶
Запустіть це на хості і прочитайте, що виведе. Скрипт перебирає BamDude-пов'язані контейнери, кожен volume, який потенційно тримає дані, розмір кожного volume і mount-розкладку будь-якого "старого" контейнера, який ви залишили як бекап.
echo "=== Контейнери ==="
docker ps -a --format "table {{.Names}}\t{{.Image}}\t{{.Status}}\t{{.CreatedAt}}" \
| grep -iE "bamdude|bambuddy"
echo
echo "=== Volume'и ==="
for v in $(docker volume ls -q | grep -iE "bamdude|bambuddy"); do
size=$(docker run --rm -v "$v":/d alpine du -sh /d 2>/dev/null | awk '{print $1}')
printf "%-50s %s\n" "$v" "$size"
done
echo
echo "=== Mount'и старого контейнера (заміни 'bamdude-old' на реальне ім'я) ==="
docker inspect bamdude-old --format '{{range .Mounts}}{{.Type}}: {{.Source}} → {{.Destination}}{{"\n"}}{{end}}'
echo
echo "=== Вміст DATA_DIR старого контейнера ==="
docker exec bamdude-old sh -c 'echo "DATA_DIR=$DATA_DIR"; ls -la "$DATA_DIR" 2>/dev/null | head'
Здоровий data-volume має щонайменше десятки MB (SQLite + thumbnails) і зазвичай GB з історією архівів. Свіжий / порожній volume -- максимум кілька сотень KB. Цей контраст зазвичай за дві секунди показує, де реально лежать дані.
Сценарій A -- змінилася назва Compose-проєкту (найпоширеніший)¶
Docker Compose v2 неймспейсує кожен named-volume за назвою проєкту, а та за замовчуванням -- basename директорії, де лежить compose-файл. Volume bamdude_data у вашому docker-compose.yml стає <project>_bamdude_data на диску:
| Розкладка | Реальна назва volume |
|---|---|
~/bambuddy/docker-compose.yml (upstream Bambuddy) |
bambuddy_bambuddy_data |
~/bamdude/docker-compose.yml |
bamdude_bamdude_data |
~/bamdude-new/docker-compose.yml |
bamdude-new_bamdude_data |
~/3d/bamdude/docker-compose.yml (з COMPOSE_PROJECT_NAME=3d) |
3d_bamdude_data |
Перейменування compose-папки (mv ~/bamdude ~/bamdude-old) і розпакування свіжого checkout'у на оригінальному шляху створює зовсім новий неймспейс, і docker compose up -d створює порожній bamdude_bamdude_data, поки ваші реальні дані сидять у bamdude-old_bamdude_data. Обидва volume'и видно через docker volume ls; дані тільки в одному.
Фікс -- спрямувати новий проєкт на існуючий volume:
Найчистіший шлях -- оголосити існуючий volume як external у новому compose-файлі, щоб Docker Compose не намагався ним керувати:
services:
bamdude:
# ... без змін ...
volumes:
- bamdude_data:/app/data
- bamdude_logs:/app/logs
volumes:
bamdude_data:
external: true
name: bamdude-old_bamdude_data # volume з вашими даними
bamdude_logs:
external: true
name: bamdude-old_bamdude_logs # аналогічно для логів
Після docker compose up -d новий контейнер читає/пише той самий фізичний volume, що й старий. Коли переконаєтесь, що все працює, можна зупинити старий контейнер і звільнити його ім'я.
Або -- скопіювати дані в новий volume:
Якщо хочете тримати неймспейсинг нового проєкту чистим і завершити з єдиним <new>_bamdude_data volume:
# Зупинити новий контейнер, щоб не гонявся з копіюванням.
docker compose down
# Перестворити (порожній) цільовий volume для безпеки.
docker volume rm <new>_bamdude_data
docker volume create <new>_bamdude_data
# Одноразова копія через одноразовий alpine-контейнер, який монтує обидва volume'и.
docker run --rm \
-v bamdude-old_bamdude_data:/from:ro \
-v <new>_bamdude_data:/to \
alpine sh -c 'cp -a /from/. /to/'
# Підняти новий compose-проєкт.
docker compose up -d
Сценарій B -- container layer (volume взагалі немає)¶
Якщо оригінальна інсталяція була через голий docker run ghcr.io/kainpl/bamdude:latest без -v bamdude_data:/app/data, всі записи приземлились у writable layer контейнера. Перейменування цього контейнера через docker rename зберігає layer (і відповідно дані), але запуск свіжого контейнера з того самого образу створює новий layer. Новий layer = немає bamdude.db, немає архіву.
Виявити це можна через docker inspect <old_container> і перевірити .Mounts. Якщо немає запису з mount'ом /app/data, дані в layer.
Фікс -- витягти дані, потім переїхати на нормальну volume-конфігурацію:
# Скопіювати дані з layer старого контейнера на хост.
docker cp bamdude-old:/app/data ./bamdude-data-recovered
# Створити нормальний named-volume і засіяти його.
docker volume create bamdude_bamdude_data # відповідно до неймспейсу нового проєкту
docker run --rm \
-v "$(pwd)/bamdude-data-recovered":/from:ro \
-v bamdude_bamdude_data:/to \
alpine sh -c 'cp -a /from/. /to/'
# Використати офіційний compose-файл -- він завжди декларує volume.
cd ~/bamdude
docker compose up -d
Надалі завжди декларуйте volume (named або bind-mount) для /app/data і /app/logs. Виданий docker-compose.yml це робить; голі docker run команди потребують явного -v.
Сценарій C -- змінився шлях bind-mount¶
Якщо ваш compose використовував bind-mount (./data:/app/data або /srv/bamdude:/app/data) замість named-volume, переміщення compose-папки також переміщує таргет bind-mount'а. Нова інсталяція приземлюється на свіжому порожньому шляху.
Виявити через docker inspect <container> --format '{{range .Mounts}}{{.Source}}{{"\n"}}{{end}}'. Якщо з'являється шлях /srv/... чи /home/..., це там реально лежать дані.
Фікс -- скопіюйте host-директорію в нове місце або наведіть новий compose на старий шлях:
# Варіант 1: перемістити bind-директорію.
mv ~/bamdude-old/data ~/bamdude/data
docker compose up -d
# Варіант 2: залишити дані де є, навести новий compose на той шлях.
# Відредагувати volumes: у docker-compose.yml на абсолютний старий шлях:
# - /home/user/bamdude-old/data:/app/data
docker compose up -d
Сценарій D -- розбіжність PUID / PGID (дані є, але app не може читати)¶
Виданий compose запускає контейнер як ${PUID:-1000}:${PGID:-1000}. Якщо ваш старий контейнер працював як 1000:1000, а новий запуск використовує інші ID (наприклад, ви виставили PUID=$(id -u) у shell, де id -u != 1000), файли volume належать UID, який новий процес не може читати чи писати. Симптоми різноманітні:
- Стартові логи показують
PermissionError: [Errno 13] Permission denied: '/app/data/bamdude.db' - Або app тихо відкочується на свіжу БД поряд із недоступною старою
- Або
init_dbпадає на першому записі міграції
Перевірте власника:
Числові UID / GID у третій і четвертій колонках мають збігатись із тим, що повертають id -u / id -g для юзера, як яким працює новий контейнер.
Фікс -- chown вмісту volume на нові ID:
docker compose down
docker run --rm -v bamdude_bamdude_data:/d alpine chown -R 1000:1000 /d
# Або який PUID:PGID у вашому compose зараз.
docker compose up -d
Dockerfile вже робить chmod 777 на /app/data під час білда, тож Docker bind-mounted директорії наслідують той ліберальний режим. Named-volume забирає ownership першого writer'а, тому одноразового chown достатньо.
Сценарій E -- docker compose down -v (volume'и видалено)¶
Прапорець -v на docker compose down назавжди видаляє named-volume'и проєкту. Якщо ви запустили це перед оновленням ("про всяк випадок"), дані зникли з точки зору Docker -- немає Recycle Bin. Перейменований старий контейнер допомагає тільки якщо він був запущений з bind-mount (host-директорія вижила) або тримав дані в container layer (Сценарій B).
Sanity-check: docker volume ls | grep bamdude має показати і старі volume'и, якщо вони існують. Якщо з'являються тільки щойно створені і жоден не має GB-розміру, дані видалено.
Відновлення -- з application-level бекапа:
Єдине надійне відновлення в цьому випадку -- BamDude UI backup zip, який ви мали зробити перед оновленням за §1. Флоу:
- Підняти новий BamDude (він приземлиться у setup-required стан).
- Пройти first-run setup з тимчасовими credentials -- наступний крок все одно замінить БД.
- Відкрити Settings → Backup → Local Backup → Upload Backup і вибрати pre-upgrade zip.
- Перезапустити контейнер, щоб система міграцій підлаштувалася під відновлену БД.
Це відновлює bamdude.db, архіви, library-файли, thumbnails, uploads і кожен Settings-рядок. Зашифровані секрети (TOTP, OIDC client_secret) відновлюються коректно, бо backup несе метадані того MFA_ENCRYPTION_KEY, яким вони були зашифровані -- той самий ключ має бути у environment нового контейнера, інакше ці рядки не розшифруються.
Якщо у вас немає бекапа і жоден з попередніх сценаріїв не підходить, дані не можна відновити.
Сценарій F -- GUI Docker-менеджер (Portainer / Dockge / Komodo) створює власний неймспейс¶
GUI Docker-менеджери часто створюють власний Compose-проєкт при імпорті -- "stacks" Portainer'а стають проєктами з іменем стека, Dockge монтує кожен compose під /opt/stacks/<name> і використовує те ім'я як назву проєкту. Імпорт того ж compose-файла під іншою назвою стека дає свіжий volume-неймспейс точно як у Сценарії A.
Фікс ідентичний Сценарію A: оголосити існуючий volume як external і навести на нього за реальним іменем. Запустіть docker volume ls, щоб побачити, яку назву створив GUI і яку використовувала ваша стара інсталяція.
Сценарій G -- Образ змінив DATA_DIR між версіями (рідко є причиною)¶
BamDude поставляється з ENV DATA_DIR=/app/data з першого Docker-релізу і ми ніколи його не міняли -- цей сценарій задокументований для повноти на випадок, якщо ви оновлюєтесь з приватного форка чи кастомного образу. Якщо ви колись перенесли дані в /data замість /app/data у власному образі, volume, змонтований на старому шляху, не підхопиться новим образом. Перенести дані:
docker run --rm \
-v <volume>:/v \
alpine sh -c 'mkdir -p /v/app/data && mv /v/data/* /v/app/data/ 2>/dev/null'
Або просто перемонтувати volume на новий шлях, який очікує образ:
Чому наш legacy-DB rename не завжди спрацьовує¶
Стартап BamDude (migrations/__init__.py) перейменовує bambuddy.db / bambutrack.db на bamdude.db -- але тільки тоді, коли файл є власною базою BamDude епохи 3.0.1, яку він розпізнає за таблицею telegram_chats усередині. Справжній файл апстрім-Bambuddy лишається недоторканим і лише називається в лозі; див. Перехід з Bambuddy. Для файла 3.0.1 перейменування далі спрацьовує тільки тоді, коли legacy-файл всередині /app/data нового контейнера -- тобто коли volume mount правильний. Якщо новий контейнер читає зі свіжого порожнього volume (Сценарії A, C, D, E), немає legacy-файла, який можна перейменувати в принципі; логіка перейменування взагалі не релевантна.
Фікс завжди має одну форму: змусити новий контейнер читати з volume, який тримає ваші дані -- або вказавши на існуючий volume (external: true), або скопіювавши дані в новий.
Траблшутінг¶
- Стартовий лог показує
setup_required503 на кожному ендпоінті - Перше завантаження не створює admin. Відкрийте
/у браузері, щоб пройти setup-флоу. Це нормально для свіжих інсталяцій і після кожногоcli reset_admin. no such column/no such tableна старті- Міграція не виконалась. Перевірте лог на стек-трейс; зазвичай це означає, що файлові дозволи на
data/не дають сервіс-юзеру писати. Полагодьте черезsudo chown -R bamdude:bamdude /opt/bamdude/data. - База Bambuddy лежить у
data/, і з нею нічого не сталось - Тепер це очікувана поведінка. Імпорт прибрано в 0.5.6 -- стартап BamDude називає файл у лозі на кожному старті й лишає його в спокої. Ніщо його не імпортує; видаліть, коли більше не потрібен.
- Копіювання Docker volume падає з
device or resource busy - Спочатку зупиніть і source, і destination контейнер. Контейнер alpine з
--rm, що монтує обидва тома, не може ділити файлову систему з працюючим сервісом, що тримає відкриті файли. - Native update лишає сервіс зламаним
update.shпише backup до того, як щось чіпає (/opt/bamdude/backups/pre-update-YYYYMMDD-HHMMSS/). Зупиніть сервіс, відновіть директорію backup поверхdata/та вичекаутіть попередній git-тег.- Довга пауза на першому завантаженні 0.4.1
- Це
m022обходить кожен 3MF на диску. Хвостіть лог -- ви маєте бачити рядкиm022 library_files: progress N/Mкожним пакетом по 100. Не вбивайте процес; перезапуск просто продовжить з того місця, де відкомітився останній пакет. database is lockedпосеред міграції- Ви запустили сервіс до того, як попередня інстанція повністю зупинилась. Зупиніть, дочекайтесь виходу старого процесу (перевірте через
pgrep -f bamdude/docker compose ps), потім стартуйте знову. Система міграцій ідемпотентна -- міграції, що впали посеред, чисто перезапускаються при наступному завантаженні.
Що нового в 0.4.x¶
| Можливість | Опис |
|---|---|
| Per-Printer Queues | Незалежна черга для кожного принтера з картковим UI; quantity > 1 пропускає кожну копію через чергу (без особливої "primary"). |
| Рефакторинг queue↔archive | Жива черга авточиститься; історія черги живе на print_archives (m019). |
| Паралельний диспатч | Кілька принтерів отримують завдання одночасно. Коротка фаза DB-write обгорнута в startup-lock (там залишається серіалізація), щоб SQLite не гонився на INSERT INTO print_archives; усе інше — FTP-завантаження, MQTT-команда старту — паралельно. Тимчасовий "один за раз через усю ферму" gate, що приземлився в середині 0.4.1, прибрали, як тільки startup-lock у дисптачер заїхав. |
| Sliding-session auth | TTL access JWT -- 1 г; ротуючий refresh-cookie тримає юзерів залогіненими прозоро. Remember-me опт-іниться на 30-денну персистентність. |
| MFA + OIDC | TOTP, email OTP, 10 backup-кодів, OIDC SSO з PKCE + JWKS + SSRF-захистом. Шифрується at rest з MFA_ENCRYPTION_KEY. |
| MQTT-action макроси | Макроси можуть інвокувати MQTT-команду (chamber_light_off / chamber_light_on) на print_started / print_finished з опційною затримкою. Заміняє спадковий прапорець auto_light_off. |
| Per-project print plan | Кожен проєкт несе впорядкований список своїх .3mf-файлів бібліотеки зі степером копій, per-row totals та смугою grand-totals. |
| Відновлення завантаження 3MF | Fallback-архіви авто-заповнюються через FTP, коли принтер був недосяжний на старті друку. |
| Метадані label-object | Прапорці підтримки skip-objects, витягнуті з Metadata/project_settings.config і збережені на кожному файлі бібліотеки + архіві (m022). |
| Server-side нарізання (0.4.2b3) | OrcaSlicer + BambuStudio sidecar-контейнери в одному Compose-проєкті (--profile orca / --profile bambu / --profile all), вибір слайсера на кожен запит у Slice-діалозі з live-індикаторами доступності, override типу столу (Cool / Engineering / High-Temp / Textured PEI / SuperTack), inline-вибір плити для мульти-плейт-файлів, owner-фільтр на пресетах. |
| Композитні file_tags (0.4.2b3) | JSON-колонка library_files.file_tags керує баджами + чіп-фільтром у File Manager: format (gcode / 3mf / stl / obj / step), readiness (sliced / project / geometry), modifiers (swap / multiplate), provenance (makerworld). m036 + m037 заповнюють історичні рядки. |
| Per-plate awareness в архівах (0.4.2b3) | Мульти-плейт-архіви тепер запам'ятовують, яка саме плита 3MF друкувалась; thumbnail, інфо про друк, G-code preview і 3D-модель — усе про цю плиту. m038 бекфілить plate_index на існуючих рядках і перепарсить 3MF, де plate_index > 1, щоб оновити slicer-derived колонки + thumbnail. |
| Library viewer capabilities + правильний стіл (0.4.2b3) | Новий ендпоінт /library/files/{id}/capabilities (дзеркало архівного) керує видимістю 3D / G-code вкладок через file_tags замість сканування суфіксів файлу; 3D-в'ювер тепер малює напівпрозорий вайрфрейм друкарського об'єму, що відповідає реальному столу принтера (раніше було захардкожено 256³). |
Див. CHANGELOG.md для детальностей по версіях.