# Auto-Deploy Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** One-command auto-deploy script that detects git changes and deploys only modified components. Clean up obsolete files. **Architecture:** One bash script (`deploy-auto.sh`) replaces the existing `deploy-ssh.sh`. It uses `git diff --name-only` against a saved commit hash (`.deployed-commit`) to decide what changed, then reuses existing rsync/tar transport logic. A markdown guide (`SERVER_SETUP.md`) replaces the bootstrap script. **Tech Stack:** Bash, git, rsync/tar, SSH, nginx, systemd, Node.js --- ### Task 1: Delete obsolete files **Files:** - Delete: `scripts/deploy-ssh.sh` - Delete: `scripts/deploy-ssh.ps1` - Delete: `scripts/deploy.env.example` - Delete: `scripts/read-deploy-env.ps1` - Delete: `scripts/register-ssh-key-for-root.ps1` - Delete: `scripts/complete-lan-deploy.ps1` - Delete: `scripts/craftshop-remote-lan.env` - Delete: `scripts/server-bootstrap.sh` - Delete: `docs/deploy-changes.md` - Delete: `docs/test-deploy-proxmox.md` - Delete: `docs/nginx-upload-limit.md` - [ ] **Step 1: Remove all obsolete files** ```bash git rm scripts/deploy-ssh.sh \ scripts/deploy-ssh.ps1 \ scripts/deploy.env.example \ scripts/read-deploy-env.ps1 \ scripts/register-ssh-key-for-root.ps1 \ scripts/complete-lan-deploy.ps1 \ scripts/craftshop-remote-lan.env \ scripts/server-bootstrap.sh \ docs/deploy-changes.md \ docs/test-deploy-proxmox.md \ docs/nginx-upload-limit.md ``` - [ ] **Step 2: Commit** ```bash git add -A && git commit -m "chore: remove obsolete deploy scripts and docs" ``` --- ### Task 2: Add .deployed-commit to .gitignore **Files:** - Modify: `.gitignore` - [ ] **Step 1: Add .deployed-commit to .gitignore** Add line at the end of `.gitignore`: ``` .deployed-commit ``` - [ ] **Step 2: Commit** ```bash git add .gitignore && git commit -m "chore: add .deployed-commit to gitignore" ``` --- ### Task 3: Write deploy-auto.sh **Files:** - Create: `scripts/deploy-auto.sh` - [ ] **Step 1: Create deploy-auto.sh with full implementation** ```bash #!/usr/bin/env bash # Auto-deploy: детект изменений через git diff, сборка и деплой только изменённых компонентов. # # Зависимости: bash, git, ssh; rsync (Linux/macOS) или tar (Git Bash). # Конфиг: scripts/deploy.env (скопируйте из deploy.env.example). # # Примеры: # ./scripts/deploy-auto.sh # деплой изменений # ./scripts/deploy-auto.sh --force # полный деплой всех компонентов # ./scripts/deploy-auto.sh -f # только фронт # ./scripts/deploy-auto.sh -b # только бэкенд set -euo pipefail case "$(uname -s 2>/dev/null)" in MINGW* | MSYS*) export MSYS2_ARG_CONV_EXCL="*" ;; esac SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" # --- Config --- if [[ -f "$SCRIPT_DIR/deploy.env" ]]; then set -a source "$SCRIPT_DIR/deploy.env" set +a fi DEPLOY_HOST="${DEPLOY_HOST:-}" DEPLOY_USER="${DEPLOY_USER:-root}" DEPLOY_PATH="${DEPLOY_PATH:-/opt/craftshop}" DEPLOY_FRONTEND_DIST="${DEPLOY_FRONTEND_DIST:-$DEPLOY_PATH/www}" DEPLOY_SSH_IDENTITY="${DEPLOY_SSH_IDENTITY:-}" DEPLOY_RESTART_CMD="${DEPLOY_RESTART_CMD:-systemctl restart craftshop-api}" DEPLOY_SERVER_OWNER="${DEPLOY_SERVER_OWNER:-deploy}" DEPLOY_SKIP_CHOWN="${DEPLOY_SKIP_CHOWN:-0}" RSYNC_OPTS=(-az --delete --human-readable --progress) SSH_OPTS=() if [[ -n "${DEPLOY_SSH_IDENTITY}" ]]; then SSH_OPTS+=(-i "$DEPLOY_SSH_IDENTITY") fi REMOTE="${DEPLOY_USER}@${DEPLOY_HOST}" SSH_BASE=(ssh "${SSH_OPTS[@]}" -o StrictHostKeyChecking=accept-new "$REMOTE") # --- Flags --- FORCE=false TARGET="auto" usage() { cat <&2; usage >&2; exit 1 ;; esac shift done if [[ -z "$DEPLOY_HOST" ]]; then echo "Укажите DEPLOY_HOST (или добавьте в scripts/deploy.env)" >&2 exit 1 fi # --- Helpers --- remote_exec() { "${SSH_BASE[@]}" "$@" } should_use_tar_transport() { case "$(uname -s 2>/dev/null)" in MINGW*|MSYS*|CYGWIN_NT*) return 0 ;; *) return 1 ;; esac } build_rsync_rsh() { printf '%q ' ssh "${SSH_OPTS[@]}" -o StrictHostKeyChecking=accept-new } # --- Diff detection --- changed_client=false changed_server=false if [[ "$TARGET" == "frontend" ]]; then changed_client=true elif [[ "$TARGET" == "backend" ]]; then changed_server=true elif [[ "$FORCE" == true ]]; then changed_client=true changed_server=true else DEPLOYED_FILE="$ROOT/.deployed-commit" if [[ -f "$DEPLOYED_FILE" ]]; then LAST_DEPLOYED=$(cat "$DEPLOYED_FILE") else LAST_DEPLOYED="HEAD~1" fi echo ">>> Diff: $LAST_DEPLOYED..HEAD" CHANGED_FILES=$(git -C "$ROOT" diff --name-only "$LAST_DEPLOYED" HEAD 2>/dev/null || true) if echo "$CHANGED_FILES" | grep -q "^client/"; then changed_client=true fi if echo "$CHANGED_FILES" | grep -q "^server/"; then changed_server=true fi if echo "$CHANGED_FILES" | grep -q "^shared/"; then changed_client=true changed_server=true fi if [[ "$changed_client" == false && "$changed_server" == false ]]; then echo ">>> Ничего не изменилось с последнего деплоя." exit 0 fi fi echo ">>> Клиент: $changed_client, Сервер: $changed_server" # --- Deploy: Client --- if [[ "$changed_client" == true ]]; then echo ">>> Сборка клиента..." (cd "$ROOT/client" && npm ci && npm run build) remote_exec mkdir -p "$DEPLOY_FRONTEND_DIST" if should_use_tar_transport; then remote_exec "find ${DEPLOY_FRONTEND_DIST} -mindepth 1 -delete 2>/dev/null || true" (cd "$ROOT/client/dist" && tar -czf - .) | \ "${SSH_BASE[@]}" "mkdir -p ${DEPLOY_FRONTEND_DIST} && tar xzf - -C ${DEPLOY_FRONTEND_DIST}" else local rsh rsh="$(build_rsync_rsh)" rsync "${RSYNC_OPTS[@]}" -e "$rsh" \ "$ROOT/client/dist/" "${REMOTE}:${DEPLOY_FRONTEND_DIST}/" fi echo ">>> Клиент задеплоен" fi # --- Deploy: Server --- if [[ "$changed_server" == true ]]; then remote_exec mkdir -p "$DEPLOY_PATH/server" "$DEPLOY_PATH/shared" if should_use_tar_transport; then (cd "$ROOT/server" && tar -czf - \ --exclude=node_modules --exclude=uploads --exclude=.git \ --exclude='*.db' --exclude=.env --exclude=.dev_env \ .) | "${SSH_BASE[@]}" "mkdir -p ${DEPLOY_PATH}/server && tar xzf - -C ${DEPLOY_PATH}/server" (cd "$ROOT/shared" && tar -czf - \ --exclude=.git \ .) | "${SSH_BASE[@]}" "mkdir -p ${DEPLOY_PATH}/shared && tar xzf - -C ${DEPLOY_PATH}/shared" else local rsh rsh="$(build_rsync_rsh)" rsync "${RSYNC_OPTS[@]}" -e "$rsh" \ --exclude node_modules --exclude uploads --exclude .git \ --exclude '*.db' --exclude .env --exclude .dev_env \ "$ROOT/server/" "${REMOTE}:${DEPLOY_PATH}/server/" rsync "${RSYNC_OPTS[@]}" -e "$rsh" \ --exclude .git \ "$ROOT/shared/" "${REMOTE}:${DEPLOY_PATH}/shared/" fi echo ">>> Сервер: npm ci, prisma generate, migrate deploy" remote_exec bash -lc "set -e cd \"$DEPLOY_PATH/server\" npm ci npx prisma generate npx prisma migrate deploy " if [[ "${DEPLOY_USER}" == "root" && "${DEPLOY_SKIP_CHOWN}" != "1" ]]; then echo ">>> chown ${DEPLOY_SERVER_OWNER}" remote_exec chown -R "${DEPLOY_SERVER_OWNER}:${DEPLOY_SERVER_OWNER}" "$DEPLOY_PATH/server" remote_exec chown -R "${DEPLOY_SERVER_OWNER}:${DEPLOY_SERVER_OWNER}" "$DEPLOY_PATH/shared" fi if [[ -n "${DEPLOY_RESTART_CMD}" ]]; then echo ">>> Рестарт: $DEPLOY_RESTART_CMD" remote_exec bash -lc "$DEPLOY_RESTART_CMD" fi echo ">>> Сервер задеплоен" fi # --- Save deployed commit --- if [[ "$TARGET" == "auto" && "$FORCE" != true ]]; then CURRENT_HEAD=$(git -C "$ROOT" rev-parse HEAD) echo "$CURRENT_HEAD" > "$ROOT/.deployed-commit" echo ">>> Сохранён коммит $CURRENT_HEAD" fi echo ">>> Готово." ``` - [ ] **Step 2: Make executable** ```bash chmod +x scripts/deploy-auto.sh ``` - [ ] **Step 3: Commit** ```bash git add scripts/deploy-auto.sh && git commit -m "feat: add deploy-auto.sh with git diff detection" ``` --- ### Task 4: Write SERVER_SETUP.md **Files:** - Create: `scripts/SERVER_SETUP.md` - [ ] **Step 1: Create SERVER_SETUP.md** ```markdown # Первичная настройка LXC для Craftshop Выполнять от **root** на свежем Debian/Ubuntu LXC. --- ## 1. Базовые пакеты и Node.js ```bash apt-get update -y apt-get install -y ca-certificates curl gnupg curl git curl -fsSL https://deb.nodesource.com/setup_22.x | bash - apt-get install -y nodejs node --version # >= 22 npm --version ``` ## 2. Пользователь и каталоги ```bash useradd --create-home --shell /bin/bash deploy mkdir -p /opt/craftshop/server/uploads /opt/craftshop/www chown -R deploy:deploy /opt/craftshop chmod 755 /opt/craftshop /opt/craftshop/server /opt/craftshop/www ``` ## 3. systemd unit ```bash cat >/etc/systemd/system/craftshop-api.service <<'UNIT' [Unit] Description=Craftshop API (Fastify) After=network.target [Service] Type=simple User=deploy Group=deploy WorkingDirectory=/opt/craftshop/server EnvironmentFile=-/opt/craftshop/server/.env ExecStart=/usr/bin/node src/index.js Restart=on-failure RestartSec=5 LimitNOFILE=65535 [Install] WantedBy=multi-user.target UNIT systemctl daemon-reload systemctl enable craftshop-api.service ``` ## 4. Nginx ```bash apt-get install -y nginx cat >/etc/nginx/sites-available/craftshop <<'NGX' server { listen 80 default_server; listen [::]:80 default_server; server_name _; root /opt/craftshop/www; index index.html; location /api/ { client_max_body_size 250m; proxy_pass http://127.0.0.1:3333; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } location /uploads/ { proxy_pass http://127.0.0.1:3333; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } location / { try_files $uri $uri/ /index.html; } } NGX rm -f /etc/nginx/sites-enabled/default ln -sf /etc/nginx/sites-available/craftshop /etc/nginx/sites-enabled/craftshop nginx -t && systemctl reload nginx ``` ## 5. NetBird VPN Установи NetBird и подключись к сети: ```bash curl -fsSL https://pkgs.netbird.io/install.sh | sh netbird up # потребуется SSO-логин ``` Проверь: `ip a` — должен появиться интерфейс `wt0` с IP из твоей NetBird-сети. ## 6. VPS с Nginx Proxy Manager На VPS (где установлен NPM): 1. Добавь DNS-запись (A) для домена, указывающую на IP VPS 2. В NPM → Proxy Hosts → Add: - Domain: `craftshop.твой-домен` - Forward Hostname: `` (IP интерфейса `wt0` на LXC) - Forward Port: `80` - SSL: запросить Let's Encrypt сертификат 3. Сохрани ## 7. Переменные окружения ```bash cat >/opt/craftshop/server/.env <<'ENV' DATABASE_URL="file:./prod.db" PORT=3333 JWT_SECRET=<сгенерируй случайную строку> ADMIN_EMAIL=<твой email> CORS_ORIGIN=https://craftshop.твой-домен IS_DEFAULT_CODE_ENABLED=false ENV chown deploy:deploy /opt/craftshop/server/.env chmod 600 /opt/craftshop/server/.env ``` Сгенерировать `JWT_SECRET`: ```bash node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" ``` ## 8. Первый деплой На машине разработчика: ```bash ./scripts/deploy-auto.sh --force ``` После деплоя: ```bash systemctl start craftshop-api systemctl status craftshop-api curl http://127.0.0.1:3333/health ``` Также проверь через внешний URL: `https://craftshop.твой-домен/api/health`. ``` - [ ] **Step 2: Commit** ```bash git add scripts/SERVER_SETUP.md && git commit -m "docs: add SERVER_SETUP.md for first-time LXC setup" ``` --- ### Task 5: Update README.md **Files:** - Modify: `README.md` - [ ] **Step 1: Remove old doc references and add deploy section** Удалить строки со ссылками на удалённые doc-файлы: - `docs/test-deploy-proxmox.md` - `docs/deploy-changes.md` - `docs/nginx-upload-limit.md` Добавить в конец README.md: ```markdown ## Деплой ```bash # Настроить scripts/deploy.env cp scripts/deploy.env.example scripts/deploy.env # Заполнить DEPLOY_HOST, DEPLOY_PATH # Первичная настройка LXC: см. scripts/SERVER_SETUP.md # Деплой только изменившихся компонентов: ./scripts/deploy-auto.sh # Полный деплой (игнорировать diff): ./scripts/deploy-auto.sh --force ``` ``` - [ ] **Step 2: Commit** ```bash git add README.md && git commit -m "docs: update README with new deploy instructions" ``` --- ## Self-review checklist 1. **Spec coverage:** - Удаление файлов → Task 1 ✓ - .gitignore → Task 2 ✓ - deploy-auto.sh с детектом diff → Task 3 ✓ - Интерфейс команд (--force, -f, -b) → Task 3 ✓ - Transport rsync/tar → Task 3 ✓ - SERVER_SETUP.md → Task 4 ✓ - README.md → Task 5 ✓ - Все пункты spec покрыты ✓ 2. **Placeholder scan:** полный код в каждом шаге, нет "TBD", "TODO", "implement later" ✓ 3. **Type consistency:** единый подход к переменным (DEPLOY_*), флагам, transport-функциям ✓