Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d2f6e89d58 | |||
| 789c1854fe | |||
| ec44dcca4e | |||
| 71e9891247 | |||
| 2adc02cb3f |
@@ -0,0 +1,79 @@
|
|||||||
|
# syntax=docker/dockerfile:1.7
|
||||||
|
#
|
||||||
|
# awg-dash — единый образ: AmneziaWG gateway + policy routing + REST API + Web UI.
|
||||||
|
# Задача: #18 (эпик #15). ADR: docs/adr/002-single-image-modular-monolith.md
|
||||||
|
#
|
||||||
|
# ВАЖНО: AMNEZIAWG_GO_REF / AMNEZIAWG_TOOLS_REF по умолчанию указывают на master
|
||||||
|
# и должны быть запинены на проверенный тег/коммит перед релизом (см. #45).
|
||||||
|
|
||||||
|
ARG GO_IMAGE=golang:1.25-alpine
|
||||||
|
|
||||||
|
# --- stage 1: сборка awg-утилит (Go) -----------------------------------------
|
||||||
|
FROM ${GO_IMAGE} AS go-build
|
||||||
|
|
||||||
|
ARG AMNEZIAWG_GO_REPO=https://github.com/amnezia-vpn/amneziawg-go.git
|
||||||
|
ARG AMNEZIAWG_GO_REF=master
|
||||||
|
ARG AMNEZIAWG_TOOLS_REPO=https://github.com/amnezia-vpn/amneziawg-tools.git
|
||||||
|
ARG AMNEZIAWG_TOOLS_REF=master
|
||||||
|
|
||||||
|
RUN apk add --no-cache git make gcc musl-dev linux-headers
|
||||||
|
|
||||||
|
WORKDIR /src
|
||||||
|
RUN git clone --depth 1 --branch "${AMNEZIAWG_GO_REF}" "${AMNEZIAWG_GO_REPO}" amneziawg-go \
|
||||||
|
&& cd amneziawg-go && make && cp amneziawg-go /usr/local/bin/amneziawg-go
|
||||||
|
|
||||||
|
RUN git clone --depth 1 --branch "${AMNEZIAWG_TOOLS_REF}" "${AMNEZIAWG_TOOLS_REPO}" amneziawg-tools \
|
||||||
|
&& cd amneziawg-tools/src && make \
|
||||||
|
&& cp wg /usr/local/bin/awg \
|
||||||
|
&& if [ -f wg-quick ]; then cp wg-quick /usr/local/bin/awg-quick; fi
|
||||||
|
|
||||||
|
# --- stage 2: runtime -------------------------------------------------------
|
||||||
|
FROM alpine:3.21 AS runtime
|
||||||
|
|
||||||
|
LABEL org.opencontainers.image.title="awg-dash" \
|
||||||
|
org.opencontainers.image.description="AmneziaWG gateway with intelligent routing: REST API + Web UI + split-tunnel" \
|
||||||
|
org.opencontainers.image.source="http://192.168.1.111:3000/embeddevio/awg-dash"
|
||||||
|
|
||||||
|
# Сетевые утилиты: iptables/ip6tables, ipset, iproute2 (policy routing), dnsmasq (ADR-005),
|
||||||
|
# curl (HEALTHCHECK), tini (PID 1), su-exec (запуск API под непривилегированным пользователем).
|
||||||
|
RUN apk add --no-cache \
|
||||||
|
bash curl iproute2 ipset iptables ip6tables dnsmasq \
|
||||||
|
python3 py3-pip tini su-exec \
|
||||||
|
&& addgroup -S awg \
|
||||||
|
&& adduser -S -G awg -h /app awg
|
||||||
|
|
||||||
|
ENV PATH=/opt/venv/bin:$PATH \
|
||||||
|
PYTHONUNBUFFERED=1 \
|
||||||
|
PYTHONDONTWRITEBYTECODE=1 \
|
||||||
|
PYTHONPATH=/app/backend \
|
||||||
|
AWG_CONFIG_DIR=/config \
|
||||||
|
AWG_DATA_DIR=/data \
|
||||||
|
AWG_RUN_DIR=/run/awg \
|
||||||
|
AWG_API_HOST=0.0.0.0 \
|
||||||
|
AWG_API_PORT=8080
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY requirements.txt /app/requirements.txt
|
||||||
|
RUN apk add --no-cache --virtual .build-deps gcc musl-dev python3-dev libffi-dev \
|
||||||
|
&& python3 -m venv /opt/venv \
|
||||||
|
&& /opt/venv/bin/pip install --no-cache-dir --upgrade pip \
|
||||||
|
&& /opt/venv/bin/pip install --no-cache-dir -r /app/requirements.txt \
|
||||||
|
&& apk del .build-deps
|
||||||
|
|
||||||
|
COPY backend/ /app/backend/
|
||||||
|
COPY --from=go-build /usr/local/bin/awg /usr/local/bin/awg
|
||||||
|
COPY --from=go-build /usr/local/bin/amneziawg-go /usr/local/bin/amneziawg-go
|
||||||
|
COPY deploy/entrypoint.sh /entrypoint.sh
|
||||||
|
RUN chmod +x /entrypoint.sh \
|
||||||
|
&& mkdir -p /config /data /run/awg \
|
||||||
|
&& chown -R awg:awg /app /config /data /run/awg
|
||||||
|
|
||||||
|
VOLUME ["/config", "/data"]
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
# healthcheck ходит в /api/v1/health (задача #31). До реализации API контейнер
|
||||||
|
# будет помечаться как unhealthy — это ожидаемое состояние до #31.
|
||||||
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
|
||||||
|
CMD curl -fsS "http://127.0.0.1:${AWG_API_PORT}/api/v1/health" >/dev/null || exit 1
|
||||||
|
|
||||||
|
ENTRYPOINT ["/sbin/tini", "--", "/entrypoint.sh"]
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
# Пример клиентского конфига AmneziaWG для awg-dash.
|
||||||
|
#
|
||||||
|
# Скопируйте в том /config как awg0.conf (имя файла = имя интерфейса) или
|
||||||
|
# загрузите через Web UI (задача #38). Значения ниже — заглушки, замените своими.
|
||||||
|
# Файл в репозитории не является рабочим конфигом и не содержит реальных ключей.
|
||||||
|
|
||||||
|
[Interface]
|
||||||
|
# Адрес внутри туннеля (из панели провайдера AmneziaWG)
|
||||||
|
Address = 10.8.1.2/32
|
||||||
|
|
||||||
|
# DNS: адрес должен указывать на этот шлюз, иначе не сработает DNS-split (ADR-005)
|
||||||
|
DNS = 10.8.1.1
|
||||||
|
|
||||||
|
PrivateKey = <PRIVATE_KEY>
|
||||||
|
|
||||||
|
# Обфускация AmneziaWG (Amnezia VPN: экспорт -> AmneziaWG)
|
||||||
|
Jc = 4
|
||||||
|
Jmin = 40
|
||||||
|
Jmax = 70
|
||||||
|
S1 = 0
|
||||||
|
S2 = 0
|
||||||
|
H1 = 1
|
||||||
|
H2 = 2
|
||||||
|
H3 = 3
|
||||||
|
H4 = 4
|
||||||
|
|
||||||
|
[Peer]
|
||||||
|
PublicKey = <SERVER_PUBLIC_KEY>
|
||||||
|
PresharedKey = <PRESHARED_KEY>
|
||||||
|
AllowedIPs = 0.0.0.0/0, ::/0
|
||||||
|
Endpoint = <SERVER_HOST>:<SERVER_PORT>
|
||||||
|
PersistentKeepalive = 25
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Супервизор контейнера awg-dash (задача #18, ADR-002).
|
||||||
|
# Последовательность: preflight -> подготовка -> запуск awg-api под awg.
|
||||||
|
# Привилегированный агент awg-agent стартует отсюда же после реализации (#22).
|
||||||
|
#
|
||||||
|
# Роль процесса: все сетевые операции выполняет агент под root; здесь только
|
||||||
|
# проверяем, что нужные возможности и устройства проброшены, и падаем явной
|
||||||
|
# ошибкой, если контейнер запущен без них (а не тихо деградируем).
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
CONFIG_DIR="${AWG_CONFIG_DIR:-/config}"
|
||||||
|
DATA_DIR="${AWG_DATA_DIR:-/data}"
|
||||||
|
RUN_DIR="${AWG_RUN_DIR:-/run/awg}"
|
||||||
|
API_HOST="${AWG_API_HOST:-0.0.0.0}"
|
||||||
|
API_PORT="${AWG_API_PORT:-8080}"
|
||||||
|
ROUTING_TABLE_ID=100
|
||||||
|
ROUTING_TABLE_NAME=awg-vpn
|
||||||
|
|
||||||
|
log() { printf '[entrypoint] %s\n' "$*"; }
|
||||||
|
fail() { printf '[entrypoint] FATAL: %s\n' "$*" >&2; exit 1; }
|
||||||
|
|
||||||
|
log 'preflight'
|
||||||
|
[ -e /dev/net/tun ] || fail '/dev/net/tun не проброшен: добавьте --device=/dev/net/tun:/dev/net/tun'
|
||||||
|
iptables -t mangle -L -n >/dev/null 2>&1 || fail 'iptables недоступен: нужен --cap-add=NET_ADMIN (и/или --privileged)'
|
||||||
|
command -v awg >/dev/null 2>&1 || fail 'бинарь awg не найден в образе'
|
||||||
|
command -v amneziawg-go >/dev/null 2>&1 || log 'WARN: amneziawg-go не найден — остаётся только kernel-модуль'
|
||||||
|
command -v ipset >/dev/null 2>&1 || fail 'ipset не найден в образе'
|
||||||
|
|
||||||
|
log 'подготовка каталогов состояния'
|
||||||
|
mkdir -p "$CONFIG_DIR" "$DATA_DIR" "$RUN_DIR"
|
||||||
|
chown -R awg:awg "$CONFIG_DIR" "$DATA_DIR" "$RUN_DIR" 2>/dev/null || true
|
||||||
|
chmod 700 "$CONFIG_DIR" "$DATA_DIR" 2>/dev/null || true
|
||||||
|
|
||||||
|
mkdir -p /etc/iproute2
|
||||||
|
touch /etc/iproute2/rt_tables 2>/dev/null || true
|
||||||
|
if ! grep -q "^${ROUTING_TABLE_ID} ${ROUTING_TABLE_NAME}" /etc/iproute2/rt_tables 2>/dev/null; then
|
||||||
|
printf '%s %s\n' "$ROUTING_TABLE_ID" "$ROUTING_TABLE_NAME" >> /etc/iproute2/rt_tables \
|
||||||
|
|| log 'WARN: не удалось записать таблицу маршрутизации в /etc/iproute2/rt_tables'
|
||||||
|
fi
|
||||||
|
|
||||||
|
sysctl -w net.ipv4.ip_forward=1 >/dev/null 2>&1 \
|
||||||
|
|| log 'WARN: net.ipv4.ip_forward не включён — задайте --sysctl net.ipv4.ip_forward=1 или --privileged (см. runbook #45)'
|
||||||
|
|
||||||
|
log "awg: $(awg --version 2>/dev/null | head -n 1 || echo 'version unknown')"
|
||||||
|
|
||||||
|
api_pid=''
|
||||||
|
if [ -f /app/backend/awg_api/app.py ]; then
|
||||||
|
log "запуск awg-api на ${API_HOST}:${API_PORT}"
|
||||||
|
su-exec awg /opt/venv/bin/uvicorn awg_api.app:app --host "$API_HOST" --port "$API_PORT" &
|
||||||
|
api_pid=$!
|
||||||
|
else
|
||||||
|
log 'WARN: awg_api/app.py ещё не реализован (задача #31) — API не запущен, контейнер работает только как агент'
|
||||||
|
fi
|
||||||
|
|
||||||
|
stop() {
|
||||||
|
if [ -n "$api_pid" ] && kill -0 "$api_pid" 2>/dev/null; then
|
||||||
|
log 'остановка awg-api'
|
||||||
|
kill -TERM "$api_pid" 2>/dev/null || true
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
trap stop INT TERM
|
||||||
|
|
||||||
|
if [ -n "$api_pid" ]; then
|
||||||
|
# ждём завершения API и прокидываем его код возврата в Docker
|
||||||
|
set +e
|
||||||
|
wait "$api_pid"
|
||||||
|
rc=$?
|
||||||
|
set -e
|
||||||
|
log "awg-api завершился с кодом $rc"
|
||||||
|
exit "$rc"
|
||||||
|
fi
|
||||||
|
|
||||||
|
log 'активных процессов нет (agent #22 и api #31 ещё не реализованы) — ожидание сигнала'
|
||||||
|
while :; do
|
||||||
|
sleep 3600 &
|
||||||
|
wait $!
|
||||||
|
done
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
# awg-dash: Руководство по работе с командой разработки
|
|
||||||
|
|
||||||
> Команда состоит из 5 агентов Hermes с изолированными профилями.
|
|
||||||
> Репозиторий: `embeddevio/awg-dash` (Gitea)
|
|
||||||
> MCP сервер: `http://127.0.0.1:8100/mcp`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. Роли
|
|
||||||
|
|
||||||
| Роль | Профиль | Запуск | Ответственность |
|
|
||||||
|------|---------|--------|-----------------|
|
|
||||||
| **TechLead** | `techlead-agent` | `hermes --profile techlead-agent chat` | Архитектура, декомпозиция эпиков, контроль качества, мерж, релизы |
|
|
||||||
| **Backend** | `backend-engineer` | `hermes --profile backend-engineer chat` | REST API, бизнес-логика, модели, unit-тесты |
|
|
||||||
| **Frontend** | `frontend-engineer` | `hermes --profile frontend-engineer chat` | UI/UX, дашборды, e2e-тесты |
|
|
||||||
| **QA** | `qa-engineer` | `hermes --profile qa-engineer chat` | Тест-планы, автотесты, регресс, security |
|
|
||||||
| **DevOps** | `devops` | `hermes --profile devops chat` | CI/CD, Docker, деплой, мониторинг |
|
|
||||||
|
|
||||||
Маршрутизация: при создании issue с лейблом `backend`/`frontend`/`qa`/`devops`/`epic` Hermes направляет задачу соответствующему профилю автоматически.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. Быстрый старт
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Запустить TechLead агента (отвечает за архитектуру и координацию)
|
|
||||||
tmux new-session -d -s techlead 'hermes --profile techlead-agent chat'
|
|
||||||
|
|
||||||
# Подключиться к сессии
|
|
||||||
tmux attach -t techlead
|
|
||||||
|
|
||||||
# Когда нужен бэкенд — запустить в другом окне
|
|
||||||
tmux new-session -d -s backend 'hermes --profile backend-engineer chat'
|
|
||||||
|
|
||||||
# Остальные профили по мере необходимости
|
|
||||||
tmux new-session -d -s frontend 'hermes --profile frontend-engineer chat'
|
|
||||||
tmux new-session -d -s qa 'hermes --profile qa-engineer chat'
|
|
||||||
tmux new-session -d -s devops 'hermes --profile devops chat'
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. Процесс разработки
|
|
||||||
|
|
||||||
```
|
|
||||||
TechLead: Эпик → Subtasks with AC
|
|
||||||
├── Backend: API + тесты → PR
|
|
||||||
├── Frontend: UI компоненты → PR
|
|
||||||
├── QA: Тест-план → e2e → Review PRs
|
|
||||||
├── DevOps: CI/CD + деплой в staging
|
|
||||||
└── TechLead: Code Review → Merge (если все gates зелёные)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.1 Эпик (создаёт TechLead)
|
|
||||||
|
|
||||||
1. Зайти в профиль TechLead: `hermes --profile techlead-agent chat`
|
|
||||||
2. Дать задачу. Агент создаст issue с лейблом `epic`.
|
|
||||||
3. В теле issue — GIVEN/WHEN/THEN acceptance criteria.
|
|
||||||
4. Агент создаст задачи-потомки с лейблами ролей.
|
|
||||||
|
|
||||||
**ID лейблов (использовать числа, не строки):**
|
|
||||||
|
|
||||||
| ID | Лейбл |
|
|
||||||
|----|-------|
|
|
||||||
| 1 | `epic` |
|
|
||||||
| 2 | `backend` |
|
|
||||||
| 3 | `frontend` |
|
|
||||||
| 4 | `devops` |
|
|
||||||
| 5 | `qa` |
|
|
||||||
| 6 | `blocker` |
|
|
||||||
| 7 | `release` |
|
|
||||||
| 8 | `hotfix` |
|
|
||||||
|
|
||||||
### 3.2 Разработка (Backend / Frontend)
|
|
||||||
|
|
||||||
1. Зайти в профиль (`hermes --profile backend-engineer chat`).
|
|
||||||
2. Сообщить номер issue — агент сам прочитает задачу.
|
|
||||||
3. Агент создаёт ветку `feat/N-slug`, пишет код, тесты, открывает PR.
|
|
||||||
4. **Важно:** агент не сливает PR. Только TechLead мержит.
|
|
||||||
|
|
||||||
### 3.3 QA
|
|
||||||
|
|
||||||
1. Зайти в профиль QA: `hermes --profile qa-engineer chat`
|
|
||||||
2. Попросить проверить PR #N. Агент прочитает diff, напишет тест-план, выполнит тесты.
|
|
||||||
3. Результат — комментарий на PR с pass/fail.
|
|
||||||
|
|
||||||
### 3.4 DevOps
|
|
||||||
|
|
||||||
1. Зайти в профиль DevOps: `hermes --profile devops chat`
|
|
||||||
2. Сообщить о мерже — агент собирает образ, деплоит в staging.
|
|
||||||
|
|
||||||
### 3.5 Code Review и Merge (TechLead)
|
|
||||||
|
|
||||||
**Gate для мержа:**
|
|
||||||
- [ ] CI зелёный (lint + тесты)
|
|
||||||
- [ ] coverage ≥ 70%
|
|
||||||
- [ ] Security scan — low/none
|
|
||||||
- [ ] QA approve
|
|
||||||
- [ ] Хотя бы 1 review от владельца роли
|
|
||||||
|
|
||||||
**Gate для релиза:**
|
|
||||||
- [ ] Все задачи эпика закрыты
|
|
||||||
- [ ] Regression pass
|
|
||||||
- [ ] Smoke tests green
|
|
||||||
- [ ] Документация обновлена
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. Labels reference
|
|
||||||
|
|
||||||
Можно установить лейбл через Gitea MCP:
|
|
||||||
|
|
||||||
```
|
|
||||||
issue_write(method='add_labels', owner='embeddevio', repo='awg-dash',
|
|
||||||
issue_number=N, labels=[2]) # backend
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. Создание задачи вручную
|
|
||||||
|
|
||||||
```python
|
|
||||||
# Через MCP инструмент
|
|
||||||
mcp__gitea__issue_write(
|
|
||||||
method='create',
|
|
||||||
owner='embeddevio',
|
|
||||||
repo='awg-dash',
|
|
||||||
title='Frontend: status indicator',
|
|
||||||
body='**AC:** GIVEN tunnel active WHEN page loads THEN green indicator shown',
|
|
||||||
labels=[3] # frontend
|
|
||||||
)
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. Правила для всех
|
|
||||||
|
|
||||||
- Все операции с репозиторием — только через MCP (не curl, не shell git).
|
|
||||||
- Никто не пушит в `main` напрямую.
|
|
||||||
- Никто не сливает свой PR (кроме TechLead после прохождения gates).
|
|
||||||
- PR #1 (TechLead bootstrap) — не мержить, это шаблоны и конфиги.
|
|
||||||
- Если задача кажется непонятной — агент откроет issue с уточняющими вопросами.
|
|
||||||
- Критическая проблема → лейбл `blocker` → TechLead подключается.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. Переключение между профилями
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Из текущей сессии можно выйти (/exit) и зайти в другой профиль
|
|
||||||
# Или через отдельный tmux:
|
|
||||||
|
|
||||||
tmux new-session -d -s backend 'hermes --profile backend-engineer chat'
|
|
||||||
tmux attach -t backend
|
|
||||||
|
|
||||||
# Просмотр запущенных сессий агентов
|
|
||||||
tmux ls
|
|
||||||
```
|
|
||||||
Reference in New Issue
Block a user