From 53ae4634b9c7fbf0e01df6a7134ac1268f177617 Mon Sep 17 00:00:00 2001 From: AlexIves16 Date: Fri, 24 Apr 2026 01:11:44 +0500 Subject: [PATCH] add: CI/CD session canvas 2026-04-23 --- session-2026-04-23.canvas | 157 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 session-2026-04-23.canvas diff --git a/session-2026-04-23.canvas b/session-2026-04-23.canvas new file mode 100644 index 0000000..77e00aa --- /dev/null +++ b/session-2026-04-23.canvas @@ -0,0 +1,157 @@ +{ + "nodes": [ + { + "id": "title", + "type": "text", + "x": -200, + "y": -600, + "width": 400, + "height": 80, + "text": "# Сессия 23 апреля 2026\n## CI/CD через Gitea Actions" + }, + { + "id": "problem1", + "type": "text", + "x": -800, + "y": -400, + "width": 340, + "height": 200, + "text": "## ❌ Проблема 1\n**node not in PATH**\nRunner выполнял job на хосте без node\n\n**Fix:** настроить runner label\n`ubuntu-latest:docker://node:20-alpine`" + }, + { + "id": "problem2", + "type": "text", + "x": -400, + "y": -400, + "width": 340, + "height": 200, + "text": "## ❌ Проблема 2\n**getaddrinfo ENOTFOUND gitea**\nJob контейнер не в сети infra\n\n**Fix:** `container.network: infra` в config.yaml runner'а" + }, + { + "id": "problem3", + "type": "text", + "x": 0, + "y": -400, + "width": 340, + "height": 200, + "text": "## ❌ Проблема 3\n**actions/checkout тянет с GitHub**\nНе знает про Gitea\n\n**Fix:** заменить на ручной `git clone` с токеном через env переменную" + }, + { + "id": "problem4", + "type": "text", + "x": 400, + "y": -400, + "width": 340, + "height": 200, + "text": "## ❌ Проблема 4\n**bash not found**\nnode:20-alpine не имеет bash\n\n**Fix:** `shell: sh` + `defaults.run.shell: sh`" + }, + { + "id": "problem5", + "type": "text", + "x": -800, + "y": -140, + "width": 340, + "height": 200, + "text": "## ❌ Проблема 5\n**Token URL parse error**\nCRLF переносы в токене ломают URL\n\n**Fix:**\n```sh\nCLEAN_TOKEN=$(printf '%s' \"${TOKEN}\" | tr -d '\\r\\n')\n```" + }, + { + "id": "problem6", + "type": "text", + "x": -400, + "y": -140, + "width": 340, + "height": 200, + "text": "## ❌ Проблема 6\n**rsync not found**\nНет rsync в Alpine\n\n**Fix:** `apk add --no-cache openssh-client rsync`\n\nПотом поняли что SSH не нужен вообще — runner на том же сервере" + }, + { + "id": "problem7", + "type": "text", + "x": 0, + "y": -140, + "width": 340, + "height": 200, + "text": "## ❌ Проблема 7\n**docker compose -f not found**\ndocker-cli в Alpine не поддерживает compose\n\n**Fix:** `apk add docker-cli-compose`\nИспользовать `docker-compose` (v1)" + }, + { + "id": "problem8", + "type": "text", + "x": 400, + "y": -140, + "width": 340, + "height": 200, + "text": "## ❌ Проблема 8\n**Duplicate mount: /var/run/docker.sock**\nУказали sock и в options и в volumes\n\n**Fix:** убрать sock из options, оставить только xflow/npb" + }, + { + "id": "problem9", + "type": "text", + "x": -800, + "y": 120, + "width": 340, + "height": 180, + "text": "## ❌ Проблема 9\n**Runner label \\\"ubuntu-latest\\\"**\nЭкранированные кавычки в YAML\n\n**Fix:** убрать кавычки вокруг лейбла в config.yaml:\n`- ubuntu-latest:docker://node:20-alpine`" + }, + { + "id": "problem10", + "type": "text", + "x": -400, + "y": 120, + "width": 340, + "height": 180, + "text": "## ❌ Проблема 10\n**No such file /opt/xflow/docker-compose.yml**\nVolume не пробрасывался в job контейнер\n\n**Fix:** `container.options` в config.yaml:\n`\"-v /opt/xflow:/opt/xflow\"`\n+ `valid_volumes`" + }, + { + "id": "problem11", + "type": "text", + "x": 0, + "y": 120, + "width": 340, + "height": 180, + "text": "## ❌ Проблема 11\n**env file /opt/xflow/.env not found**\n\n**Fix:** создать `/opt/xflow/.env` на сервере с переменными:\nNODE_ENV, DATABASE_URL,\nNUXT_JWT_SECRET, GITEA_URL и т.д." + }, + { + "id": "solution", + "type": "text", + "x": -200, + "y": 380, + "width": 500, + "height": 340, + "text": "## ✅ Итоговый стек деплоя\n\n**Runner config.yaml:**\n```yaml\nrunner:\n labels:\n - ubuntu-latest:docker://node:20-alpine\ncontainer:\n network: infra\n options: \"-v /opt/xflow:/opt/xflow -v /opt/npb:/opt/npb\"\n valid_volumes:\n - /opt/xflow\n - /opt/npb\n - /var/run/docker.sock\n```\n\n**Workflow шаги:**\n1. `git clone` через env TOKEN\n2. `npm ci && npm run build`\n3. `cp -r .output/ /opt/xflow/.output/`\n4. `docker-compose up -d --force-recreate`" + }, + { + "id": "result", + "type": "text", + "x": -200, + "y": 780, + "width": 500, + "height": 260, + "text": "## 🎉 Результат\n\n- **XFlow** запущен на `http://a-crm.controlsystem.kz` (порт 3002)\n- **NPB** деплоится на порт 3003\n- **Gitea runner** `avtex-runner` обслуживает всю организацию AVTEX\n- Автодеплой при каждом `git push` в master/main\n- Секрет `GIT_TOKEN` на уровне организации\n\n**Следующие шаги:**\n- `prisma migrate deploy`\n- Переписать API с db.ts на Prisma\n- Nginx proxy для NPB" + }, + { + "id": "arch", + "type": "text", + "x": 400, + "y": 380, + "width": 340, + "height": 260, + "text": "## 🏗️ Архитектура сервера\n\n```\nDocker network: infra\n├── gitea (3000/3001)\n├── postgres (5432)\n├── gitea-runner\n│ ├── /var/run/docker.sock\n│ ├── /opt/xflow (volume)\n│ └── /opt/npb (volume)\n├── xflow (3002→3000)\n└── npb (3003→3000)\n```\n\nJump host: `alex@178.238.78.98`\nTarget: `root@10.0.0.120`" + } + ], + "edges": [ + { "id": "e1", "fromNode": "title", "fromSide": "bottom", "toNode": "problem1", "toSide": "top" }, + { "id": "e2", "fromNode": "title", "fromSide": "bottom", "toNode": "problem2", "toSide": "top" }, + { "id": "e3", "fromNode": "title", "fromSide": "bottom", "toNode": "problem3", "toSide": "top" }, + { "id": "e4", "fromNode": "title", "fromSide": "bottom", "toNode": "problem4", "toSide": "top" }, + { "id": "e5", "fromNode": "problem1", "fromSide": "bottom", "toNode": "problem5", "toSide": "top" }, + { "id": "e6", "fromNode": "problem3", "fromSide": "bottom", "toNode": "problem6", "toSide": "top" }, + { "id": "e7", "fromNode": "problem4", "fromSide": "bottom", "toNode": "problem7", "toSide": "top" }, + { "id": "e8", "fromNode": "problem2", "fromSide": "bottom", "toNode": "problem8", "toSide": "top" }, + { "id": "e9", "fromNode": "problem5", "fromSide": "bottom", "toNode": "problem9", "toSide": "top" }, + { "id": "e10", "fromNode": "problem6", "fromSide": "bottom", "toNode": "problem10", "toSide": "top" }, + { "id": "e11", "fromNode": "problem7", "fromSide": "bottom", "toNode": "problem11", "toSide": "top" }, + { "id": "e12", "fromNode": "problem9", "fromSide": "bottom", "toNode": "solution", "toSide": "top" }, + { "id": "e13", "fromNode": "problem10", "fromSide": "bottom", "toNode": "solution", "toSide": "top" }, + { "id": "e14", "fromNode": "problem11", "fromSide": "bottom", "toNode": "solution", "toSide": "top" }, + { "id": "e15", "fromNode": "solution", "fromSide": "bottom", "toNode": "result", "toSide": "top" }, + { "id": "e16", "fromNode": "solution", "fromSide": "right", "toNode": "arch", "toSide": "left" } + ] +}