# Раннер Forgejo Actions для NetPulse — окремим стеком. # # Окремий файл, а не сервіс у docker-compose.yml, свідомо: CI не має # ані підніматись, ані падати разом із продуктом. `docker compose down` # на стенді не повинен забирати з собою раннер, а перезапуск раннера не # повинен торкатись бази. # # ───────────────────────────────────────────────────────────────────── # РЕЄСТРАЦІЯ (робиться один раз, руками) # # 1. У Forgejo: Налаштування → Actions → Runners → Create registration # token. Рівень вибирає, що раннер бачитиме: # репозиторій — лише netpulse (для цієї задачі досить); # організація/інстанс — усі репозиторії, включно з майбутніми. # Раннер має root-доступ до докера машини, тому ширший рівень # означає, що будь-який workflow у будь-якому репозиторії отримує # цей доступ. Беріть найвужчий, який покриває потребу. # # 2. Реєстрація (виконати В КАТАЛОЗІ repo/deploy): # # docker compose -f docker-compose.ci.yml run --rm runner \ # forgejo-runner register --no-interactive \ # --instance https://git.zotac.keenetic.link \ # --token \ # --name netpulse-ci \ # --labels docker:docker://node:22-alpine # # Результат — файл .runner у томі runner-data. Він переживає # перестворення контейнера; повторно реєструватись не треба. # # 3. Запуск: # # docker compose -f docker-compose.ci.yml up -d # docker compose -f docker-compose.ci.yml logs -f runner # # У Forgejo раннер має зʼявитись у списку зі станом «idle». # # ЯКЩО FORGEJO НА ЦІЙ САМІЙ МАШИНІ. Публічне імʼя git.zotac.keenetic.link # з контейнера може не резолвитись у себе (розворот NAT на роутері # роблять не всі). Ознака — раннер не реєструється або мовчить після # старту. Лікується одним рядком у сервісі runner: # # extra_hosts: # - "git.zotac.keenetic.link:host-gateway" # # ───────────────────────────────────────────────────────────────────── # ЧОГО ЦЕЙ ФАЙЛ КОШТУЄ # # Памʼять у спокої: раннер ~40 МБ + прибиральник ~5 МБ. # Памʼять під час прогону: одна робота за раз (capacity: 1), стеля # контейнера роботи — 1600 МБ (deploy/act-runner.config.yml), плюс # контейнер Postgres у роботах server і dbtest (~400 МБ із # TS_TUNE_MEMORY). Роботи з базою дві, але йдуть вони по черзі, тож # пік від цього не росте: ~2 ГБ понад те, що вже їсть бойовий стек. # Диск: образи ~1.1 ГБ (alpine 8 МБ, node:22-alpine ~180 МБ, # golang:1.25-alpine ~350 МБ, timescaledb ~450 МБ, сам раннер # ~120 МБ) + кеші зі стелями 1200/900/400 МБ, які тримають самі # роботи наприкінці кожного прогону. # Разом у найгіршому випадку ~3.6 ГБ диска. # # ───────────────────────────────────────────────────────────────────── name: netpulse-ci services: runner: # ВЕРСІЮ ЗВІРТЕ ЗІ СВОЇМ FORGEJO. Раннер і сервер сумісні не в # будь-яких парах, а який саме Forgejo стоїть у вас — звідси не # видно. Це єдиний рядок у цьому файлі, який неможливо перевірити # без доступу до стенду. image: ${FORGEJO_RUNNER_IMAGE:-code.forgejo.org/forgejo/runner:6} container_name: netpulse-ci-runner restart: unless-stopped command: ["forgejo-runner", "daemon", "--config", "/etc/forgejo/runner.yml"] working_dir: /data environment: DOCKER_HOST: unix:///var/run/docker.sock TZ: ${TZ:-Europe/Kyiv} # Група власника сокета докера. Образ раннера працює не від root # (uid 1000), а сокет має права rw лише для власника й групи — без # цього рядка демон падає в циклі з «permission denied ... docker.sock» # і жодна робота не стартує. # # Номер групи різний на різних машинах, тому змінна. Свій дізнатись: # stat -c %g /var/run/docker.sock # Помилка тут видима одразу: демон не піднімається й пише причину. group_add: - "${DOCKER_GID:-991}" volumes: # Сокет докера, а не docker-in-docker. DinD означав би ще один # демон, власний пул образів (тобто ДРУГУ копію golang, node і # timescaledb) і власний шар зберігання — на машині з ~7 ГБ це # не поміщається. Ціна вибору названа чесно: усе, що потрапить у # workflow, виконується з правами root на цьому докері. - /var/run/docker.sock:/var/run/docker.sock - runner-data:/data - ./act-runner.config.yml:/etc/forgejo/runner.yml:ro # Сам демон нічого важкого не робить — уся робота в дочірніх # контейнерах, і їхню стелю задає container.options у конфізі. mem_limit: 256m # Прибиральник. Половина сенсу цього стеку. # # act лишає по собі зупинені контейнери робіт і сервісів, а зміна # тега образу лишає «висячий» попередній. Саме по собі це не # зникає, і на диску, якого ~7 ГБ, воно закінчується не роками, а # тижнями. CI, який забив диск, зупинить продукт, а не покращить. # # Кешів Go й npm цей сервіс НЕ чіпає навмисно: їх підрізають самі # роботи, останнім кроком. У той момент точно ніхто не компілює, тож # чистка не може вирвати файл з-під `go build`. Прибиральник, який # ходить за розкладом, такої гарантії не має. gc: image: docker:28-cli container_name: netpulse-ci-gc restart: unless-stopped volumes: - /var/run/docker.sock:/var/run/docker.sock # Томи кешів підключені лише щоб їх БАЧИТИ: compose створює # іменований том тоді, коли його хтось монтує, а розмір у логу # прибиральника — єдине місце, де за ним можна стежити, не # заходячи на машину. - gomod:/caches/gomod:ro - gobuild:/caches/gobuild:ro - npm:/caches/npm:ro group_add: - "${DOCKER_GID:-991}" entrypoint: ["/bin/sh", "-c"] # Список з ОДНИМ елементом, а не рядок. `command:` рядком compose # розбиває на слова за правилами оболонки, і `/bin/sh -c` отримує # аргументом саме «while» — контейнер падає в циклі з «syntax error: # unexpected end of file (expecting "do")». Блоковий скаляр у списку # лишається одним аргументом, як і задумано. command: - | while :; do # БЕЗ -a і БЕЗ --volumes. `prune -a` знесла б образи бойового # стеку, які просто зараз не запущені (наприклад, під час # оновлення), а `--volumes` — дані. Тут прибирається лише те, # що вже нічим не тримається: зупинені контейнери, підвішені # образи, порожні мережі, кеш складання. docker system prune -f --filter 'until=72h' >/dev/null 2>&1 || true free=$$(df -Pm /caches/gobuild 2>/dev/null | awk 'NR==2{print $$4}') echo "$$(date -Iseconds) CI-gc: вільно $${free:-?} МБ; кеші:" \ "$$(du -sh /caches/gomod /caches/gobuild /caches/npm 2>/dev/null | tr '\n' ' ')" sleep 21600 done mem_limit: 64m volumes: # Реєстрація раннера (.runner) — без неї доведеться реєструватись # заново після кожного перестворення контейнера. runner-data: # Кеші збірок. Імена задані явно, БЕЗ префікса проєкту: у # .forgejo/workflows/ci.yml вони пишуться саме так, а контейнери робіт # створює act напряму, а не compose, тож жодного префікса він не # додасть. Ці ж імена мають бути в container.valid_volumes конфігу # раннера, інакше роботи впадуть на монтуванні. gomod: name: netpulse-ci-gomod gobuild: name: netpulse-ci-gobuild npm: name: netpulse-ci-npm