Перейти к содержанию

Перенос стека магазина: Linux → Windows + Docker Desktop

Это пошаговое руководство по переносу Django-магазина со старого устройства на базе Linux (PostmarketOS ARM64) на Windows-машину с Docker Desktop.

В примере используется реальный сценарий: перенос с Xiaomi Redmi 4 (postmarketOS, Alpine, aarch64) на Windows 10/11 + Docker Desktop.


Что вы получите в итоге

Windows 10/11
├── Docker Desktop
│   ├── [db]      PostgreSQL 16
│   ├── [web]     Django + Gunicorn
│   └── [nginx]   статика + прокси → 127.0.0.1:8080
│
└── device_client_windows_x86_64.exe  (туннель dix.su)
        ↕ localhost:8080
        ↕
        dix.su → ваш-слаг.dix.su (HTTPS)

Магазин доступен в интернете по прежнему адресу eskimos.dix.su (или вашему кастомному домену). Все данные, загруженные изображения и история заказов сохранены.

Два клиента с одним slug одновременно не держать

Пока не убедитесь что новый стек работает, не останавливайте старый. Но два device_client с одинаковым slug одновременно держать нельзя — новый вытолкнет старый. Переключайтесь только после проверки.


Требования

  • Windows 10 (21H2+) или Windows 11
  • Docker Desktop для Windows с включённым WSL2-бэкендом
  • WSL2 Ubuntu (устанавливается вместе с Docker Desktop или через wsl --install -d Ubuntu)
  • Git в WSL2: sudo apt install git
  • Доступ к старому устройству (SSH или физически)

Шаг 1. Экспорт данных со старого устройства

На старом устройстве (в нашем примере — Xiaomi Redmi 4 с postmarketOS, доступный через туннель dix.su):

ssh -p 2222 \
  -o "ProxyCommand=bash -c '{ echo DIXSU:eskimos; cat; } | nc eskimos.dix.su 2222'" \
  root@eskimos.dix.su
ssh root@192.168.0.15

Находясь на старом устройстве, запустите скрипт экспорта из репозитория:

bash scripts/export-itdid3.sh

Скрипт делает три вещи:

  1. pg_dump --no-owner --no-privileges — дамп PostgreSQL без зависимостей от конкретного пользователя (важно при переносе на другую СУБД-инсталляцию)
  2. tar загрузок из static/images/ и static/upload/ (CKEditor)
  3. Упаковывает всё в shop-<дата>.bundle.tar с файлом MANIFEST

Что внутри архива

shop-2026-09-29.bundle.tar
├── MANIFEST        # версии, дата, хэши
├── db.sql.gz       # дамп PostgreSQL (gzip)
└── uploads.tar.gz  # загруженные файлы

Скопируйте архив на Windows-машину. Вариант через SCP (из WSL2):

scp -P 2222 \
  -o "ProxyCommand=bash -c '{ echo DIXSU:eskimos; cat; } | nc eskimos.dix.su 2222'" \
  root@eskimos.dix.su:/root/shop-*.bundle.tar ~/

Или скопируйте через USB-флешку, если нет прямого доступа.


Шаг 2. Клонировать репозиторий в WSL2

Откройте терминал WSL2 (Ubuntu) и клонируйте репозиторий:

git clone https://gitflic.ru/ramanzes/deploy-django.git ~/django_shop
cd ~/django_shop

Почему именно WSL2, а не Windows-путь?

Скрипты (restore.sh, backup.sh) — bash-скрипты с Unix-окончаниями строк. Запускайте их из WSL2, не из PowerShell. Репозиторий держите в ~/django_shop (файловая система WSL2), не в /mnt/c/… — Docker Desktop на Windows-путях работает значительно медленнее.


Шаг 3. Заполнить .env

cp .env.template .env
nano .env  # или любой редактор

Минимальный набор переменных:

# БД
DB_PASSWORD=сильный_пароль_сюда

# Django
SECRET_KEY=длинная-случайная-строка-минимум-50-символов
ALLOWED_HOSTS=eskimos.dix.su,localhost
SITE_URL=https://eskimos.dix.su
CSRF_TRUSTED_ORIGINS=https://eskimos.dix.su

# Суперпользователь (создаётся при первом запуске)
ADMIN_EMAIL=ваш@email.ru
ADMIN_PASSWORD=пароль_администратора

# HTTP-порт nginx на хосте Windows
HTTP_PORT=8080

DIXSU_SLUG и DIXSU_TOKEN

Эти переменные больше не нужны в compose-файле — туннель запускается отдельным exe-файлом на Windows. Оставьте их в .env если хотите, но сервис tunnel по умолчанию не стартует.


Шаг 4. Восстановить данные

Из WSL2, в директории ~/django_shop:

bash scripts/restore.sh ~/shop-2026-09-29.bundle.tar

Скрипт:

  1. Собирает Docker-образ web (docker compose build)
  2. Поднимает только db и дожидается готовности PostgreSQL
  3. Загружает db.sql.gz через psql
  4. Распаковывает uploads.tar.gz в том uploads
  5. Поднимает web и nginx
  6. web при старте запускает collectstatic и migrate

Если база данных уже содержит данные (например, при повторном запуске), скрипт откажется без флага --force:

bash scripts/restore.sh --force ~/shop-2026-09-29.bundle.tar

Проверьте что сайт работает локально:

curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/
# Ожидаемый ответ: 200

Или откройте в браузере Windows: http://localhost:8080


Шаг 5. Запустить туннель dix.su (Windows-бинарь)

Почему не контейнер

Docker Desktop на Windows не поддерживает network_mode: host. Контейнер с этим режимом видит свой собственный localhost, а не Windows-хост. Из-за этого туннель не может достучаться до nginx на порту 8080.

Решение: запускать туннель как обычный exe-файл прямо на Windows. Он нативно обращается к localhost:8080, куда nginx прокидывает порт через 127.0.0.1:8080:80.

Docker Desktop:
  [nginx] — слушает :80 внутри контейнера
             ↕
  [127.0.0.1:8080:80] — порт-маппинг Docker Desktop

Windows Host:
  device_client.exe → localhost:8080 → nginx → web → db

Скачать бинарь

# В PowerShell:
Invoke-WebRequest -Uri "https://dix.su/downloads/device_client_windows_x86_64" `
    -OutFile "device_client_windows_x86_64.exe"

Запустить туннель

.\device_client_windows_x86_64.exe start --slug eskimos --token ВАШ_ТОКЕН --port 8080

Убедитесь что eskimos.dix.su отвечает:

curl https://eskimos.dix.su/
# Или откройте в браузере

Автозапуск через Планировщик задач

Чтобы туннель запускался автоматически при входе в Windows:

$action = New-ScheduledTaskAction `
    -Execute "$PWD\device_client_windows_x86_64.exe" `
    -Argument "start --slug eskimos --token ВАШ_ТОКЕН --port 8080" `
    -WorkingDirectory $PWD

$trigger = New-ScheduledTaskTrigger -AtLogOn
$settings = New-ScheduledTaskSettingsSet -ExecutionTimeLimit 0

Register-ScheduledTask `
    -TaskName "dixsu-tunnel" `
    -Action $action `
    -Trigger $trigger `
    -Settings $settings `
    -RunLevel Highest `
    -Force

# Запустить сразу, не дожидаясь перезагрузки:
Start-ScheduledTask -TaskName "dixsu-tunnel"

Шаг 6. Остановить старый стек

Только после того как убедились что новый стек работает и туннель отвечает:

# На старом устройстве (eskimos):
docker compose \
  -f /root/deploy-django/itdid3/docker-compose/docker-compose.yaml \
  down

# Остановить хостовый device_client:
rc-service dixsu-device-client stop

Не держите два клиента одновременно

Два device_client с одним и тем же slug — один выталкивает другой. Остановите старый только после того как новый проверен.


Резервное копирование на новом стеке

Создание бэкапа (из WSL2):

cd ~/django_shop
bash scripts/backup.sh
# Создаёт shop-<дата>.bundle.tar в текущей директории

Автоматический бэкап через Планировщик задач Windows (один раз):

$wslAction = New-ScheduledTaskAction `
    -Execute "wsl.exe" `
    -Argument '-d Ubuntu -- bash -lc "cd ~/django_shop && bash scripts/backup.sh"'

$dailyTrigger = New-ScheduledTaskTrigger -Daily -At "03:00"
Register-ScheduledTask -TaskName "django-shop-backup" `
    -Action $wslAction -Trigger $dailyTrigger -Force

Adminer (доступ к БД)

Adminer не запускается по умолчанию. Поднять его временно:

# В WSL2:
docker compose --profile adminer up -d adminer

Adminer слушает только на 127.0.0.1:8081 (не проброшен наружу). Откройте в браузере: http://localhost:8081 Сервер: db, логин и пароль из .env.

По завершении работы остановите:

docker compose stop adminer

Проблемы и решения

exec /entrypoint.sh: no such file or directory

Git на Windows конвертирует окончания строк LF → CRLF. Bash в контейнере видит #!/bin/bash\r — невалидный shebang — и не может запустить скрипт.

Решение: в репозитории уже есть .gitattributes с правилом *.sh text eol=lf. При первом клонировании этот файл применяется автоматически. Если репозиторий уже был склонирован до добавления .gitattributes, выполните:

git rm --cached -r .
git reset --hard

nginx возвращает 502 сразу после старта

web контейнер стартует несколько секунд — collectstatic + migrate. Подождите 10–15 секунд и обновите страницу.

Загруженные изображения возвращают 404

Убедитесь что архив восстановлен правильно и том uploads не пустой:

docker compose exec web ls /app/media/images/ | head -5

Если пустой — повторите restore.sh --force с правильным путём к архиву.

Туннель запустился, но сайт не открывается по slug-адресу

Проверьте что nginx работает и порт 8080 занят:

netstat -ano | findstr :8080

Порт должен принадлежать процессу Docker Desktop.