Agents Webhookサテライト連携を追加 #1

Merged
yotta merged 5 commits from codex/add-agents-webhook-satellite into main 2026-07-18 00:14:29 +00:00
9 changed files with 202 additions and 28 deletions
Showing only changes of commit c14716c46b - Show all commits
+9
View File
@@ -0,0 +1,9 @@
.git
.gitea
.env
.venv
__pycache__
*.py[cod]
README.md
start.sh
start.ps1
+54
View File
@@ -0,0 +1,54 @@
name: Validate and publish container image
on:
pull_request:
branches:
- main
push:
branches:
- main
env:
REGISTRY: harbor.mukan.0am.jp
IMAGE_NAME: harbor.mukan.0am.jp/services/email-to-discord
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v4
- name: Build test stage
run: |
docker build \
--target test \
--tag "${IMAGE_NAME}:test-${{ gitea.sha }}" \
.
- name: Build runtime image
run: |
docker build \
--target runtime \
--tag "${IMAGE_NAME}:${{ gitea.sha }}" \
--tag "${IMAGE_NAME}:latest" \
.
- name: Log in to Harbor
if: gitea.event_name == 'push'
env:
HARBOR_USERNAME: ${{ secrets.HARBOR_USERNAME }}
HARBOR_PASSWORD: ${{ secrets.HARBOR_PASSWORD }}
run: |
printf '%s' "${HARBOR_PASSWORD}" | docker login "${REGISTRY}" \
--username "${HARBOR_USERNAME}" \
--password-stdin
- name: Push runtime image
if: gitea.event_name == 'push'
run: |
docker push "${IMAGE_NAME}:${{ gitea.sha }}"
docker push "${IMAGE_NAME}:latest"
+14 -5
View File
@@ -1,4 +1,4 @@
FROM python:3.11-slim
FROM python:3.11-slim AS base
# 作業ディレクトリを設定
WORKDIR /app
@@ -15,14 +15,23 @@ RUN pip install --no-cache-dir -r requirements.txt
# アプリケーションファイルをコピー
COPY app.py .
# テスト用ステージ(CIではこのステージまでビルドして単体テストを実行)
FROM base AS test
COPY docker-compose.yml docker-compose.local.yml ./
COPY tests ./tests
RUN python -m unittest discover -s tests -v
# 実行用ステージ
FROM base AS runtime
# 非rootユーザーを作成
RUN useradd --create-home --shell /bin/bash appuser
RUN chown -R appuser:appuser /app
RUN useradd --create-home --shell /bin/bash appuser \
&& chown -R appuser:appuser /app
USER appuser
# ヘルスチェックを追加
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD python -c "import sys; sys.exit(0)"
CMD python -c "from pathlib import Path; import sys; sys.exit(0 if b'app.py' in Path('/proc/1/cmdline').read_bytes() else 1)"
# アプリケーションを実行
CMD ["python", "app.py"]
CMD ["python", "app.py"]
+43 -4
View File
@@ -80,7 +80,46 @@ cp .env.example .env
# .envファイルを編集して実際の値を設定
```
## 🐳 Docker Composeでの実行
## 🚢 Agents `container_tools` へのデプロイ
`container_tools` はイメージをビルドせず、レジストリからpullして起動します。このリポジトリでは、`main` へのpush時にGitea Actionsが次のイメージをHarborへ公開します。
```text
harbor.mukan.0am.jp/services/email-to-discord:<commit-sha>
harbor.mukan.0am.jp/services/email-to-discord:latest
```
### 1. Gitea Actions Secretsの設定
リポジトリのActions Secretsへ次の2項目を登録します。実際の値をリポジトリやComposeへコミットしないでください。
- `HARBOR_USERNAME`
- `HARBOR_PASSWORD`
Pull Requestではテストステージと実行イメージのビルドだけを行い、`main` へマージされたときだけHarborへpushします。
### 2. デプロイ用Composeの準備
通常の [`docker-compose.yml`](docker-compose.yml) は `container_tools` 用テンプレートです。次の値を実際のデプロイ情報へ置き換えます。
- イメージタグの `latest`(可能ならGitea Actionsが公開したコミットSHAへ変更)
- `REPLACE_WITH_EMAIL_USER`
- `REPLACE_WITH_EMAIL_PASSWORD`
- `REPLACE_WITH_WEBHOOK_URL`
- `REPLACE_WITH_WEBHOOK_TOKEN`
- `REPLACE_WITH_DISCORD_CHANNEL_ID`
Discordへの直接通知も併用する場合だけ、空の `DISCORD_WEBHOOK_URL` に実際のURLを設定します。`WEBHOOK_SECRET` は設定しません。
Agentsの実行時情報にある `WEBHOOK_URL``http://127.0.0.1:<port>/webhook` の場合、コンテナ内の `127.0.0.1` はサテライト自身を指すため、`http://host.docker.internal:<port>/webhook` に置き換えてください。ComposeにはLinuxからDockerホストへ到達するための `host-gateway` を設定済みです。
### 3. デプロイ
置換後のCompose全文を、安定した `project_name` とともに `container_tools` へ渡します。デプロイ用Composeには、`container_tools` が禁止する `build``container_name``env_file`、host bind mountを含めていません。
## 🐳 ローカルDocker Composeでの実行
ローカルでソースからビルドする場合は、デプロイ用とは別の `docker-compose.local.yml` を使用します。
### 1. `.env` の設定
@@ -101,13 +140,13 @@ DISCORD_CHANNEL_ID=123456789012345678
```bash
# コンテナをビルドして起動
docker-compose up -d
docker compose -f docker-compose.local.yml up --build -d
# ログの確認
docker-compose logs -f
docker compose -f docker-compose.local.yml logs -f
# コンテナの停止
docker-compose down
docker compose -f docker-compose.local.yml down
```
## 🚀 Linux用シェルスクリプトでの実行
+22
View File
@@ -0,0 +1,22 @@
services:
email-to-discord:
build:
context: .
target: runtime
container_name: email-to-discord-monitor
restart: unless-stopped
extra_hosts:
- "host.docker.internal:host-gateway"
env_file:
- .env
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
deploy:
resources:
limits:
memory: 256M
reservations:
memory: 128M
+16 -10
View File
@@ -1,21 +1,27 @@
version: '3.8'
services:
email-to-discord:
build: .
container_name: email-to-discord-monitor
image: harbor.mukan.0am.jp/services/email-to-discord:latest
restart: unless-stopped
env_file:
- .env
# ログの設定
extra_hosts:
- "host.docker.internal:host-gateway"
environment:
IMAP_SERVER: imap.gmail.com
IMAP_PORT: "993"
USE_SSL: "true"
MAILBOX: INBOX
EMAIL_USER: REPLACE_WITH_EMAIL_USER
EMAIL_PASSWORD: REPLACE_WITH_EMAIL_PASSWORD
DISCORD_WEBHOOK_URL: ""
WEBHOOK_URL: REPLACE_WITH_WEBHOOK_URL
WEBHOOK_TOKEN: REPLACE_WITH_WEBHOOK_TOKEN
DISCORD_CHANNEL_ID: "REPLACE_WITH_DISCORD_CHANNEL_ID"
WEBHOOK_SOURCE: email-monitor
CHECK_INTERVAL: "60"
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
# リソース制限
deploy:
resources:
limits:
+3 -3
View File
@@ -225,7 +225,7 @@ function Invoke-Cleanup {
# docker-compose で起動
function Start-Compose {
Log-Info "docker-compose でサービスを起動しています..."
docker-compose up -d
docker-compose -f docker-compose.local.yml up --build -d
if ($LASTEXITCODE -eq 0) {
Log-Info "サービスが起動しました"
Log-Blue "ログを確認するには: .\start.ps1 compose-logs"
@@ -238,7 +238,7 @@ function Start-Compose {
# docker-compose で停止
function Stop-Compose {
Log-Info "docker-compose でサービスを停止しています..."
docker-compose down
docker-compose -f docker-compose.local.yml down
Log-Info "サービスを停止しました"
}
@@ -322,7 +322,7 @@ switch ($Action.ToLower()) {
Stop-Compose
}
"compose-logs" {
docker-compose logs -f
docker-compose -f docker-compose.local.yml logs -f
}
"python" {
Start-Python
+6 -6
View File
@@ -230,9 +230,9 @@ compose_up() {
log_info "docker-compose でサービスを起動しています..."
if command -v docker-compose &> /dev/null; then
docker-compose up -d
docker-compose -f docker-compose.local.yml up --build -d
else
docker compose up -d
docker compose -f docker-compose.local.yml up --build -d
fi
log_info "サービスが起動しました"
@@ -244,9 +244,9 @@ compose_down() {
log_info "docker-compose でサービスを停止しています..."
if command -v docker-compose &> /dev/null; then
docker-compose down
docker-compose -f docker-compose.local.yml down
else
docker compose down
docker compose -f docker-compose.local.yml down
fi
log_info "サービスを停止しました"
@@ -327,9 +327,9 @@ main() {
;;
"compose-logs")
if command -v docker-compose &> /dev/null; then
docker-compose logs -f
docker-compose -f docker-compose.local.yml logs -f
else
docker compose logs -f
docker compose -f docker-compose.local.yml logs -f
fi
;;
"python")
+35
View File
@@ -0,0 +1,35 @@
import re
import unittest
from pathlib import Path
REPOSITORY_ROOT = Path(__file__).resolve().parents[1]
class ContainerToolsDeploymentTests(unittest.TestCase):
def test_deployment_compose_uses_prebuilt_image_only(self):
compose = (REPOSITORY_ROOT / "docker-compose.yml").read_text(encoding="utf-8")
self.assertIn(
"image: harbor.mukan.0am.jp/services/email-to-discord:latest",
compose,
)
self.assertIn("host.docker.internal:host-gateway", compose)
for forbidden_key in ("build", "container_name", "env_file"):
self.assertIsNone(
re.search(rf"^\s+{forbidden_key}:", compose, flags=re.MULTILINE),
f"container_toolsで禁止されている'{forbidden_key}'が含まれています",
)
def test_local_compose_keeps_source_build_support(self):
compose = (REPOSITORY_ROOT / "docker-compose.local.yml").read_text(
encoding="utf-8"
)
self.assertRegex(compose, r"(?m)^\s+build:")
self.assertIn("target: runtime", compose)
self.assertIn("host.docker.internal:host-gateway", compose)
if __name__ == "__main__":
unittest.main()