158 lines
7.5 KiB
Plaintext
158 lines
7.5 KiB
Plaintext
{
|
||
"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" }
|
||
]
|
||
}
|