Compare commits

24 Commits

Author SHA1 Message Date
Akira
7102f0f553 .gitignore: ワークフロー定義ファイルをmainブランチから除外
u/ と workflows/f,u,g/ はsyncブランチがgit_syncで管理するため
mainブランチのローカル作業ディレクトリでは追跡しない

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 02:37:12 +09:00
Akira
da2466462e windmill_update_flow: エンドポイントを flows/edit → flows/update に修正
Windmill API の正しいフロー更新エンドポイントは /flows/update/{path}。
/flows/edit/{path} は 404 を返す(誤ったエンドポイント)。

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 02:30:56 +09:00
Akira
77f3326868 windmill_mcp.py: ホスト設定をコンストラクタで渡すよう修正
- FastMCP("windmill") でデフォルト host=127.0.0.1 だと DNS rebinding 保護が
  自動有効化されて外部ホスト名からのアクセスが "Invalid Host header" で拒否される
- MCP_HOST/MCP_PORT 環境変数をコンストラクタ時点で渡すことで
  host=0.0.0.0 の場合は保護が無効になり Traefik 経由のアクセスが通る
- ドメイン名を windmill_mcp → windmill-mcp に修正(DNS は hyphen のみ対応)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 02:06:51 +09:00
Akira
2dbe8c8a74 Windmill MCP サーバーを SSE 化してサーバーデプロイに対応
- windmill_mcp.py: MCP_TRANSPORT 環境変数で stdio/sse を切り替え可能に
- mcp/Dockerfile: Python 3.12-slim ベースのコンテナイメージを追加
- docker-compose.yml: windmill_mcp サービスを追加(Traefik 経由で windmill-mcp.keinafarm.net に公開)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 01:39:31 +09:00
Akira
c163d00f51 再起動お疲れ様です。メモリを確認して、MCP サーバーが動作するかテストします。
Windmill [windmill_list_flows]
OUT
{"result":"- u/antigravity/git_sync: Git Sync Workflow\n- f/mail/mail_filter: メールフィルタリング\n- f/shiraou/shiraou_notification: 白皇集落営農 変更通知\n- f/app_custom/system_heartbeat: Windmill Heartbeat - システム自己診断"}
MCP サーバーは正常に動作しています。フロー一覧が取得できました:

パス	名前
u/antigravity/git_sync	Git Sync Workflow
f/mail/mail_filter	メールフィルタリング
f/shiraou/shiraou_notification	白皇集落営農 変更通知
f/app_custom/system_heartbeat	Windmill Heartbeat - システム自己診断
次に何を行いますか?前回の続きがあればお教えください。
2026-02-25 21:48:18 +09:00
Akira
5e92ea62ef 完了したこと
1. Traefik "too many services" エラー修正 

原因: windmill_extra コンテナのラベルに2つの問題があった

ルーターに service の明示指定がなかった(Traefik が判断できない)
windmill_extra が traefik-net に接続されていなかった(Traefik がルーティング不可)
修正内容:

traefik.http.routers.windmill-lsp.service=windmill-lsp を追加
traefik.http.routers.windmill-debug.service=windmill-debug を追加
windmill_extra を traefik-net に接続
サーバーの docker-compose.yml を永続化(/workspace/docker-compose.yml)
2. Windmill MCP サーバー実装 

mcp/windmill_mcp.py を作成・コミット。7 ツール実装:

windmill_list_flows / windmill_get_flow / windmill_run_flow
windmill_list_recent_jobs / windmill_get_job_logs
windmill_list_scripts / windmill_get_script
Claude Code への登録: .mcp.json + .claude/settings.local.json の enabledMcpjsonServers で自動承認済
2026-02-25 21:44:28 +09:00
Akira
f80c2a2518 完了したこと
1. Traefik "too many services" エラー修正 

原因: windmill_extra コンテナのラベルに2つの問題があった

ルーターに service の明示指定がなかった(Traefik が判断できない)
windmill_extra が traefik-net に接続されていなかった(Traefik がルーティング不可)
修正内容:

traefik.http.routers.windmill-lsp.service=windmill-lsp を追加
traefik.http.routers.windmill-debug.service=windmill-debug を追加
windmill_extra を traefik-net に接続
サーバーの docker-compose.yml を永続化(/workspace/docker-compose.yml)
2. Windmill MCP サーバー実装 

mcp/windmill_mcp.py を作成・コミット。7 ツール実装:

windmill_list_flows / windmill_get_flow / windmill_run_flow
windmill_list_recent_jobs / windmill_get_job_logs
windmill_list_scripts / windmill_get_script
Claude Code への登録: .mcp.json + .claude/settings.local.json の enabledMcpjsonServers で自動承認済
2026-02-25 21:44:19 +09:00
Akira
6de184968d Windmill MCP サーバーを追加
mcp/windmill_mcp.py: FastMCP ベースの Python MCP サーバー
  - windmill_list_flows: フロー一覧
  - windmill_get_flow: フロー定義取得
  - windmill_run_flow: フローのトリガー実行
  - windmill_list_recent_jobs: ジョブ一覧(成功/失敗/実行中フィルタ対応)
  - windmill_get_job_logs: ジョブログ・実行結果取得
  - windmill_list_scripts: スクリプト一覧
  - windmill_get_script: スクリプトソースコード取得

.gitignore: .mcp.json, __pycache__/ を除外

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-25 21:42:41 +09:00
Akira
be710b920e 変更内容
削除したもの
traefik-net 外部ネットワーク
windmill_server の全 Traefik ラベル
windmill_extra の全 Traefik ラベル
サーバー固有のボリューム /home/windmill/windmill:/workspace(ローカルには不要)
Google OAuth 環境変数(ローカル開発では不要)
windmill_indexer(replicas: 0 で無意味だったので削除)
追加・変更したもの
項目	変更前	変更後
ネットワーク	traefik-net + windmill-internal	windmill-internal のみ
アクセス方法	Traefik経由(HTTPS)	Caddy経由 http://localhost
POSTGRES_PASSWORD	${POSTGRES_PASSWORD}	${DATABASE_PASSWORD}(.envと統一)
windmill_worker replicas	3	1(ローカル用に軽量化)
pull_policy	always	if_not_present(毎回pullしない)
BASE_URL	https://windmill.keinafarm.net	http://localhost
新規作成
Caddyfile.local — ローカル用のシンプルなCaddy設定(LSP WebSocket・デバッガー対応)
起動方法

docker compose -f docker-compose-dev.yml up -d
起動後、ブラウザで http://localhost にアクセスすればWindmillが使えます。

注意: windmill_worker に /var/run/docker.sock をマウントしていますが、Windows の Docker Desktop は通常これをサポートしています。もし問題が出る場合はそのボリューム行を削除してください。
2026-02-22 10:37:12 +09:00
Akira
d59e55a54e 手順書更新 2026-02-19 16:13:43 +09:00
Akira
53f84e2647 スクリーンショットを確認しました。
test_git_sync.py
 に IndentationError(インデントエラー) があります。ファイルを確認します。

Analyzed
test_git_sync.py#L1-4
3行目の return のインデントが print より深くなっています。修正します。

Edited
test_git_sync.py
+1
-2
修正しました。return のインデントが print と揃っていなかったのが原因です。

ローカルからGiteaにプッシュして、サーバー側で git pull した後、Windmill上で再度テストを実行してみてください:

bash
git add .
git commit -m "Fix indentation in test_git_sync.py"
git push origin main
その後、サーバーのコンテナ内(またはホスト側)で再度 wmill sync push するか、Windmill UIから直接スクリプトを編集しても大丈夫です。
2026-02-19 15:45:18 +09:00
Akira
86c0180c3f おっしゃる通り、説明が不十分でした。整理させてください。
/workspace はコンテナ内のパスです。
docker-compose.yml
 の - .:/workspace によって、サーバーのホスト上の ~/windmill/(リポジトリルート)がコンテナ内の /workspace にマウントされています。
a.sh
 はWindmillのWorkerコンテナ内で実行されるので、スクリプト内で使う /workspace はコンテナ内のパスとして正しいです。

ただし、問題は
wmill.yaml
 がリポジトリルートではなく workflows/ サブディレクトリにある ことです。コンテナ内で言うと /workspace/workflows/ に
wmill.yaml
 があるので、スクリプトの cd /workspace を cd /workspace/workflows に変更する必要があります。

修正します:

Analyzed
a.sh#L1-63
なるほど、ポイントは以下の通りです:

cd /workspace → コンテナ内ではリポジトリルートです。しかし
wmill.yaml
 はここにはなく、workflows/ の中にあります。
wmill sync pull は
wmill.yaml
 がある場所で実行しなければ動きません。
git 操作(add, commit, push)はリポジトリルート(/workspace)で行う必要があります。
つまり、wmill sync pull は /workspace/workflows で、git 操作は /workspace で行う、という二段構えが必要です。修正します:

Edited
a.sh
+9
-5
修正しました。最終的なスクリプトの流れを確認します:

Analyzed
a.sh#L1-67
修正後のスクリプトは以下の流れになっています:

cd /workspace/workflows →
wmill.yaml
 がある場所で wmill sync pull を実行(Windmill DB → ファイルに書き出し)
cd /workspace → Gitリポジトリルートに戻って git add / commit / push(ファイル → Gitea)
ここで重要な補足:

/workspace はサーバーのホスト上のパスではなく、コンテナ内のパスです。
docker-compose.yml
 の - .:/workspace により、ホスト上の ~/windmill/(docker-composeを実行した場所)がコンテナ内の /workspace にマウントされます。
つまり、コンテナ内の /workspace/workflows/ = ホスト上の ~/windmill/workflows/ です。
a.sh
 はWindmillのWorkerコンテナ内で実行されるので、コンテナ内パス(/workspace)を使うのが正しいです。
この変更をGiteaにプッシュしますか?プッシュ後、サーバー側で git pull して再度お試しください。
2026-02-19 15:25:08 +09:00
Akira
f8e9c95403 ご指摘のコードブロックを削除した理由は、それが重複しており、かつサーバー環境(コンテナ内)では動作しないパスを含んでいたためです。
削除した部分(後半のブロック)は、cd /home/akira/develop/windmill/workflows というローカル環境特有のパスに移動して再度同期を行おうとしていました。 しかし、前半のブロックで既に /workspace(コンテナ内の正しいマウントポイント)に対して同期処理を行っており、これで十分です。 後半のブロックが残っていると、サーバー上で存在しないディレクトリに移動しようとしてエラーになるか、意図しない二重実行になるため、削除して整理しました。

これでスクリプトはシンプルになり、サーバー環境に適したものになりました。 変更をGiteaにプッシュしましたので、SERVER_SETUP.md の手順に従ってサーバー上で設定を進めてください。
2026-02-19 15:03:25 +09:00
Akira
793846e7f5 Google OAUTH関連 2026-02-19 14:50:31 +09:00
Akira
29d2dbbb57 DATABASE_PASSWORD 2026-02-19 14:18:23 +09:00
Akira
0acac8799d password 2026-02-19 14:11:22 +09:00
Akira
39a850b064 versionのパスワード 2026-02-19 14:10:27 +09:00
Akira
90e805a360 存在しないバージョン言われた 2026-02-19 14:04:02 +09:00
Akira
f700a3454e バージョンがlastestだと起動しない 2026-02-19 13:54:09 +09:00
Akira
0d1278b9ac compose見ました — 原因は完全に特定できました。
👉 Windmillに BASE_URL が設定されていません。

これがあると:

OAuth callback が壊れる

redirect_uri_mismatch

/api/oauth/callback/google が見つからない

全部起きます。

これは Windmill + Traefik の典型トラブルです。
2026-02-19 11:30:35 +09:00
Akira
639ac23efa Prepare for VPS migration: Create server specific docker-compose.yml and rename dev config 2026-02-18 15:13:53 +09:00
Bot
297299c3f8 Auto-sync Fri Feb 13 10:45:00 UTC 2026 2026-02-13 10:45:00 +00:00
Bot
2f2ae074f5 Auto-sync Fri Feb 13 10:42:01 UTC 2026 2026-02-13 10:42:01 +00:00
Bot
2c96d29c6f Auto-sync Fri Feb 13 10:39:01 UTC 2026 2026-02-13 10:39:01 +00:00
17 changed files with 1054 additions and 137 deletions

View File

@@ -0,0 +1,32 @@
{
"permissions": {
"allow": [
"Bash(curl -m 10 -s -o /dev/null -w \"%{http_code}\" -H \"Connection: Upgrade\" -H \"Upgrade: websocket\" https://windmill.keinafarm.net/ws/)",
"Bash(ssh -i \"/c/Users/akira/.ssh/ssh-key-20241206.pem\" -t claude@keinafarm.net \"cat /home/windmill/windmill/docker-compose.yml\")",
"Bash(ssh -i \"/c/Users/akira/.ssh/ssh-key-20241206.pem\" akira@keinafarm.net \"docker exec windmill_server cat /workspace/docker-compose.yml\")",
"Bash(ssh -i \"/c/Users/akira/.ssh/ssh-key-20241206.pem\" akira@keinafarm.net \"docker compose --project-directory /home/windmill/windmill version 2>&1\")",
"Bash(ssh -i \"/c/Users/akira/.ssh/ssh-key-20241206.pem\" akira@keinafarm.net \"docker compose --project-directory /home/windmill/windmill ps 2>&1\")",
"Bash(ssh -i \"/c/Users/akira/.ssh/ssh-key-20241206.pem\" akira@keinafarm.net \"docker exec windmill_server cat /workspace/.env\")",
"Bash(ssh -i \"/c/Users/akira/.ssh/ssh-key-20241206.pem\" akira@keinafarm.net \"docker network inspect traefik-net --format ''{{json .Name}}'' && docker network connect traefik-net windmill-windmill_extra-1 && echo ''Connected successfully''\")",
"Bash(ssh -i \"/c/Users/akira/.ssh/ssh-key-20241206.pem\" akira@keinafarm.net \"docker exec windmill_server cat /workspace/docker-compose.yml > /tmp/windmill-compose.yml && cat /tmp/windmill-compose.yml | wc -l\")",
"Bash(ssh -i \"/c/Users/akira/.ssh/ssh-key-20241206.pem\" akira@keinafarm.net \"grep -A 20 ''windmill_extra:'' /tmp/windmill-compose-fixed.yml | grep -E ''\\(traefik-net|windmill-lsp.service|windmill-debug.service\\)''\")",
"Bash(ssh -i \"/c/Users/akira/.ssh/ssh-key-20241206.pem\" akira@keinafarm.net \"grep -n ''windmill-lsp\\\\|windmill-debug\\\\|traefik-net'' /tmp/windmill-compose-fixed.yml\")",
"Bash(ssh -i:*)",
"WebFetch(domain:github.com)",
"Bash(python -c \"from mcp.server.fastmcp import FastMCP; print\\(''mcp OK''\\)\")",
"Bash(pip install mcp httpx)",
"Bash(WINDMILL_TOKEN=qLJ3VPZ61kTDiIwaUPUu1dXszGrsN1Dh python -c \":*)",
"Read(//c/Users/akira/.claude/**)",
"Bash(git add .gitignore mcp/)",
"Bash(git commit:*)",
"mcp__windmill__windmill_list_flows"
],
"additionalDirectories": [
"C:\\Users\\akira\\Develop\\windmill",
"C:\\Users\\akira\\.claude\\projects\\c--Users-akira-Develop-windmill\\memory\\"
]
},
"enabledMcpjsonServers": [
"windmill"
]
}

9
.env
View File

@@ -1,9 +1,14 @@
DATABASE_URL=postgres://postgres:changeme@db/windmill?sslmode=disable DATABASE_PASSWORD=DbForWindMillPassword
WM_VERSION=1.638.0
DATABASE_URL=postgres://postgres:${DATABASE_PASSWORD}@db/windmill?sslmode=disable
# For Enterprise Edition, use: # For Enterprise Edition, use:
# WM_IMAGE=ghcr.io/windmill-labs/windmill-ee:main # WM_IMAGE=ghcr.io/windmill-labs/windmill-ee:main
WM_IMAGE=ghcr.io/windmill-labs/windmill:main WM_IMAGE=ghcr.io/windmill-labs/windmill:${WM_VERSION}
GOOGLE_OAUTH_CLIENT_ID=976427934311-6oj0l38ptn6ui2hoj37qbs137lcnu6kg.apps.googleusercontent.com
GOOGLE_OAUTH_CLIENT_SECRET=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
# To use another port than :80, setup the Caddyfile and the caddy section of the docker-compose to your needs: https://caddyserver.com/docs/getting-started # To use another port than :80, setup the Caddyfile and the caddy section of the docker-compose to your needs: https://caddyserver.com/docs/getting-started
# To have caddy take care of automatic TLS # To have caddy take care of automatic TLS

11
.gitignore vendored
View File

@@ -29,6 +29,9 @@ Thumbs.db
*.bak *.bak
*~ *~
# MCP server config (contains API tokens)
.mcp.json
# Resolved markdown files (generated by editor) # Resolved markdown files (generated by editor)
*.resolved *.resolved
*.resolved.* *.resolved.*
@@ -46,3 +49,11 @@ workflows/.wmill/tmp/
!workflows/g/ !workflows/g/
!workflows/wmill.yaml !workflows/wmill.yaml
!workflows/wmill-lock.yaml !workflows/wmill-lock.yaml
__pycache__/
# Windmill workflow definitions (managed by git_sync on sync branch)
# Local working directory should not track these on main
u/
workflows/f/
workflows/u/
workflows/g/

12
Caddyfile.local Normal file
View File

@@ -0,0 +1,12 @@
:80 {
# LSP - Language Server Protocol for code intelligence
reverse_proxy /ws/* http://windmill_extra:3001
# Debugger - Interactive debugging via DAP WebSocket
handle_path /ws_debug/* {
reverse_proxy http://windmill_extra:3003
}
# Default: Windmill server
reverse_proxy /* http://windmill_server:8000
}

112
HANDOFF.md Normal file
View File

@@ -0,0 +1,112 @@
# Windmill 作業引き継ぎメモ
> 作成: 2026-02-25keinasystem_t02 セッションから移管)
---
## 現在の状態
### git_sync フロー(修正済み ✅)
**問題**: `u/antigravity/git_sync` フローが `wmill sync pull` で認証エラー
**原因**: `$WM_TOKEN`(ジョブトークン)はワークスペーススコープのため、
wmill CLIが内部で呼ぶ `/api/users/whoami`グローバルAPIで401
**解決**:
- `/home/windmill/windmill/wmill_config/remotes.ndjson` を作成(永続設定)
- グローバルスコープトークン `CQKYm1bUwszHCT4Ww6TGyQX97XMs8qg8`(ラベル: `git-sync`)を使用
- フロースクリプトを `--config-dir /workspace/wmill_config` を使う形に修正
- 動作確認: Success: True (2秒) ✅
### windmill.keinafarm.net 外部アクセス504 未修正 ❌)
- 外部からアクセスすると 30秒後に 504 Gateway Timeout
- サーバー内部からは `http://localhost:8000` で正常にアクセスできる
- **Caddyfile がこのディレクトリにある** → 原因調査・修正が必要
### Windmill MCP サーバー(未着手 ⬜)
- LLMClaudeが直接 Windmill を操作できるようにする
- 外部アクセスが直れば `https://windmill.keinafarm.net` に接続
- 直らない場合は SSH トンネル経由
---
## 重要な情報
### サーバー接続
```
SSH: root@keinafarm.net
Windmill内部URL: http://localhost:8000
Windmillサーバー上のパス: /home/windmill/windmill/
```
### API トークン
| トークン | スコープ | 用途 |
|---------|---------|------|
| `qLJ3VPZ61kTDiIwaUPUu1dXszGrsN1Dh` | ワークスペースadmins | 通常のAPI操作 |
| `CQKYm1bUwszHCT4Ww6TGyQX97XMs8qg8` | グローバル | git-sync用wmill CLI |
### 2つの Git リポジトリ(別物!)
| リポジトリ | パス | 用途 |
|-----------|------|------|
| `windmill.git` (Gitea) | サーバー `/home/windmill/windmill/` | wmill CLI で自動同期 |
| `windmill_workflow.git` (Gitea) | ローカル `C:\Users\akira\Develop\windmill` | このディレクトリ |
### wmill_config永続設定
```
サーバーパス: /home/windmill/windmill/wmill_config/
コンテナ内パス: /workspace/wmill_config/
remotes.ndjson: {"remote":"http://windmill_server:8000/","workspaceId":"admins","name":"admins","token":"CQKYm1bUwszHCT4Ww6TGyQX97XMs8qg8"}
activeWorkspace: admins
```
---
## 次にやること(優先順)
### 1. windmill.keinafarm.net 504 を修正
`Caddyfile` を確認して、windmill へのリバースプロキシ設定を見直す。
タイムアウト設定が足りない可能性が高い。
```
# 確認コマンド(サーバー内部では正常)
ssh root@keinafarm.net "curl -s http://localhost:8000/api/version"
```
### 2. Windmill MCP サーバーを実装
**方針**: カスタム軽量 Python MCP サーバー6〜8 tools
実装する tools:
- `windmill_list_flows` — フロー一覧
- `windmill_get_flow` — フローのスクリプト取得
- `windmill_run_flow` — フローをトリガー
- `windmill_list_recent_jobs` — 最近のジョブ一覧(成功/失敗)
- `windmill_get_job_logs` — ジョブの詳細ログ
- `windmill_list_scripts` — スクリプト一覧
- `windmill_get_script` — スクリプト取得
既存実装: `rothnic/windmill-mcp`GitHub, スター0, 更新停止)→ 使わず自作
### 3. mail_filter.flow.json をコミット
ローカルの `windmill_workflow``f/mail/mail_filter.flow.json` が未コミット状態。
---
## 参考: wmill 設定ファイル形式
```
~/.config/windmill/remotes.ndjson (1行1ワークスペース)
{"remote":"http://...../","workspaceId":"...","name":"local_alias","token":"..."}
~/.config/windmill/activeWorkspace (プレーンテキスト)
local_alias
```
`wmill sync pull --config-dir /path/to/config` で任意ディレクトリを指定可能。

155
SERVER_SETUP.md Normal file
View File

@@ -0,0 +1,155 @@
# Windmill サーバー設定手順 (VPS移行版)
本番環境VPSへのデプロイ手順です。
既にTraefikが稼働している環境`traefik-net` ネットワークが存在する環境)を前提としています。
## 前提条件
- サーバー上でTraefikが稼働しており、`traefik-net` ネットワークが存在すること。
- ドメイン `windmill.keinafarm.net` がサーバーのIPに向けられていること。
## ステップ1: リポジトリの準備
サーバー上の任意の場所(例: `/home/windmill/windmill`)にリポジトリをクローンします。
**重要**: WindmillのGit同期機能を使用するため、このディレクトリパスは重要です。
```bash
mkdir -p /home/windmill
cd /home/windmill
git clone https://gitea.keinafarm.net/akira/windmill.git windmill
cd windmill
```
## ステップ2: 環境変数の設定
`.env` ファイルを作成し、本番用の設定を行います。
```bash
cp .env .env.production
nano .env
```
以下の内容を確認・修正してください:
- `DATABASE_URL`: `postgres://postgres:あなたの強力なパスワード@db/windmill?sslmode=disable`
- `POSTGRES_PASSWORD`: 上記と同じパスワード
- `WM_IMAGE`: `ghcr.io/windmill-labs/windmill:main`
## ステップ3: 起動
`docker-compose.yml` は本番用に構成されていますTraefik連携済み
```bash
docker-compose up -d
```
## ステップ4: Git同期用ワークフローのセットアップ
Windmill上で「登録されたワークフローをGitに保存する」機能を有効にする手順です。
`git_sync` フローが定期実行されると、Windmill DB上のスクリプト/フローの変更がGiteaリポジトリに自動コミットプッシュされます。
### 4-1. Windmill APIトークンの取得
1. ブラウザで `https://windmill.keinafarm.net` にログイン
2. 左下の **Settings****Account** をクリック
3. **Tokens** セクションで **Create token** をクリック
4. Label例: `git-sync`)を入力し、作成
5. 表示されたトークンをコピーしておく(後のステップで使用)
### 4-2. ワークフロー定義の取り込み(初回のみ)
リポジトリの `workflows/` にある定義ファイルをWindmill DBに取り込みます。
```bash
# Windmillサーバーコンテナに入る
docker exec -it windmill_server /bin/bash
# コンテナ内で実行windmill-cli をインストール
npm install -g windmill-cli
# wmill.yamlがあるディレクトリに移動して sync push
cd /workspace/workflows
wmill sync push \
--token "<4-1で取得したトークン>" \
--base-url "http://localhost:8000" \
--workspace admins \
--yes
exit
```
> **注意**: `wmill sync push` はディスク→DBへの反映です。
> 逆に `wmill sync pull` はDB→ディスクへの反映です。
> スケジュールされた `git_sync` フローが `sync pull` を実行するため、
> **UIで直接スクリプトを修正した場合、次回の sync pull で正しくディスクにも反映されます。**
### 4-3. Gitea認証情報の設定git push用
`git_sync` フローが Gitea へ `git push` できるよう、サーバー上のリモートURLにGiteaのアクセストークンを含めます。
```bash
# サーバーのホスト側で実行
cd ~/windmill
# 現在のリモートURLを確認
git remote -v
# Giteaのアクセストークンを含んだURLに変更
git remote set-url origin https://<username>:<giteaトークン>@gitea.keinafarm.net/akira/windmill.git
```
> **Giteaトークンの作成方法**: Gitea`https://gitea.keinafarm.net`)にログイン →
> 右上アバター → Settings → Applications → Generate New Token
### 4-4. WM_TOKEN Variable の設定
WindmillのWeb画面で、`git_sync` フローが使用する変数を登録します。
1. 左メニューの **Variables** をクリック
2. **+ Variable** をクリック
3. 以下を入力:
- **Path**: `u/antigravity/wm_token`
- **Value**: 4-1で取得したWindmill APIトークン
- **Is Secret**: ✅ オン
4. **Save** をクリック
> **注意**: `git_sync` フローのスクリプト(`a.sh`)内で `$WM_TOKEN` として参照されます。
> フローのInput設定で、この変数が正しく紐づけられていることを確認してください。
### 4-5. git_sync フローの手動実行テスト
1. Windmill UI で **`u/antigravity/git_sync`** フローを開く
2. **Run** ボタンで手動実行
3. **Runs** ページで実行ログを確認
4. 成功すれば、Giteaリポジトリに自動コミットが作成されているはず
### 4-6. スケジュール実行の確認
`git_sync.schedule.yaml` により、2分ごとに自動実行されるスケジュールが登録されています。
左メニューの **Schedules** から、スケジュールが有効になっていることを確認してください。
---
## トラブルシューティング
### ディスク上のファイルが古い内容に戻る
`git_sync` フローが `wmill sync pull`DB→ディスクを実行するため、UIで修正した内容がディスクに上書きされます。
スクリプトの修正は **Windmill UI上で直接編集** するのが確実です。
### git push が失敗する
```bash
# サーバー上でリモートURLにトークンが含まれているか確認
cd ~/windmill
git remote -v
# https://<user>:<token>@gitea.keinafarm.net/... の形式であること
```
### 開発環境(ローカル)での起動
ローカルで起動する場合は `docker-compose-dev.yml` を使用します:
```bash
docker-compose -f docker-compose-dev.yml up -d
```
### ログ確認
```bash
docker-compose logs -f
```

152
docker-compose-dev.yml Normal file
View File

@@ -0,0 +1,152 @@
version: "3.9"
x-logging: &default-logging
driver: "json-file"
options:
max-size: "${LOG_MAX_SIZE:-20m}"
max-file: "${LOG_MAX_FILE:-10}"
compress: "true"
networks:
windmill-internal:
driver: bridge
services:
db:
deploy:
replicas: 1
image: postgres:16
shm_size: 1g
restart: unless-stopped
volumes:
- db_data:/var/lib/postgresql/data
expose:
- 5432
environment:
POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
POSTGRES_DB: windmill
healthcheck:
test: [ "CMD-SHELL", "pg_isready -U postgres" ]
interval: 10s
timeout: 5s
retries: 5
logging: *default-logging
networks:
- windmill-internal
windmill_server:
image: ${WM_IMAGE}
container_name: windmill_server
pull_policy: if_not_present
deploy:
replicas: 1
restart: unless-stopped
expose:
- 8000
environment:
- DATABASE_URL=${DATABASE_URL}
- MODE=server
- BASE_URL=http://localhost
depends_on:
db:
condition: service_healthy
volumes:
- worker_logs:/tmp/windmill/logs
networks:
- windmill-internal
logging: *default-logging
windmill_worker:
image: ${WM_IMAGE}
pull_policy: if_not_present
deploy:
replicas: 1
resources:
limits:
cpus: "1"
memory: 2048M
restart: unless-stopped
environment:
- DATABASE_URL=${DATABASE_URL}
- MODE=worker
- WORKER_GROUP=default
depends_on:
db:
condition: service_healthy
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- worker_dependency_cache:/tmp/windmill/cache
- worker_logs:/tmp/windmill/logs
networks:
- windmill-internal
logging: *default-logging
windmill_worker_native:
image: ${WM_IMAGE}
pull_policy: if_not_present
deploy:
replicas: 1
resources:
limits:
cpus: "1"
memory: 2048M
restart: unless-stopped
environment:
- DATABASE_URL=${DATABASE_URL}
- MODE=worker
- WORKER_GROUP=native
- NUM_WORKERS=8
- SLEEP_QUEUE=200
depends_on:
db:
condition: service_healthy
volumes:
- worker_logs:/tmp/windmill/logs
networks:
- windmill-internal
logging: *default-logging
windmill_extra:
image: ghcr.io/windmill-labs/windmill-extra:${WM_VERSION}
pull_policy: if_not_present
restart: unless-stopped
expose:
- 3001
- 3003
environment:
- ENABLE_LSP=true
- ENABLE_MULTIPLAYER=false
- ENABLE_DEBUGGER=true
- DEBUGGER_PORT=3003
- ENABLE_NSJAIL=false
- REQUIRE_SIGNED_DEBUG_REQUESTS=false
- WINDMILL_BASE_URL=http://windmill_server:8000
volumes:
- lsp_cache:/pyls/.cache
networks:
- windmill-internal
logging: *default-logging
caddy:
image: caddy:2.9-alpine
restart: unless-stopped
ports:
- "80:80"
volumes:
- ./Caddyfile.local:/etc/caddy/Caddyfile
- caddy_data:/data
- caddy_config:/config
networks:
- windmill-internal
logging: *default-logging
depends_on:
- windmill_server
- windmill_extra
volumes:
db_data: null
worker_dependency_cache: null
worker_logs: null
lsp_cache: null
caddy_data: null
caddy_config: null

View File

@@ -1,5 +1,3 @@
version: "3.7"
x-logging: &default-logging x-logging: &default-logging
driver: "json-file" driver: "json-file"
options: options:
@@ -7,10 +5,15 @@ x-logging: &default-logging
max-file: "${LOG_MAX_FILE:-10}" max-file: "${LOG_MAX_FILE:-10}"
compress: "true" compress: "true"
networks:
traefik-net:
external: true # サーバー上の既存Traefikネットワーク
windmill-internal:
driver: bridge
services: services:
db: db:
deploy: deploy:
# To use an external database, set replicas to 0 and set DATABASE_URL to the external database url in the .env file
replicas: 1 replicas: 1
image: postgres:16 image: postgres:16
shm_size: 1g shm_size: 1g
@@ -20,7 +23,7 @@ services:
expose: expose:
- 5432 - 5432
environment: environment:
POSTGRES_PASSWORD: changeme POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
POSTGRES_DB: windmill POSTGRES_DB: windmill
healthcheck: healthcheck:
test: [ "CMD-SHELL", "pg_isready -U postgres" ] test: [ "CMD-SHELL", "pg_isready -U postgres" ]
@@ -28,197 +31,172 @@ services:
timeout: 5s timeout: 5s
retries: 5 retries: 5
logging: *default-logging logging: *default-logging
networks:
- windmill-internal
windmill_server: windmill_server:
image: ${WM_IMAGE} image: ${WM_IMAGE}
pull_policy: always container_name: windmill_server
pull_policy: if_not_present
deploy: deploy:
replicas: 1 replicas: 1
restart: unless-stopped restart: unless-stopped
expose: expose:
- 8000 - 8000
- 2525
environment: environment:
- DATABASE_URL=${DATABASE_URL} - DATABASE_URL=${DATABASE_URL}
- MODE=server - MODE=server
- BASE_URL=https://windmill.keinafarm.net
- OAUTH_REDIRECT_BASE_URL=https://windmill.keinafarm.net
- GOOGLE_OAUTH_ENABLED=true
- GOOGLE_OAUTH_CLIENT_ID=${GOOGLE_OAUTH_CLIENT_ID}
- GOOGLE_OAUTH_CLIENT_SECRET=${GOOGLE_OAUTH_CLIENT_SECRET}
depends_on: depends_on:
db: db:
condition: service_healthy condition: service_healthy
volumes: volumes:
- worker_logs:/tmp/windmill/logs - worker_logs:/tmp/windmill/logs
- /home/akira/develop/windmill:/workspace # Git同期のために、カレントディレクトリリポジトリルート/workspaceにマウント
# これにより、コンテナ内から .git ディレクトリにアクセス可能となり、git pushが可能になる
- .:/workspace
labels:
- "traefik.enable=true"
# HTTPSルーター
- "traefik.http.routers.windmill.rule=Host(`windmill.keinafarm.net`)"
- "traefik.http.routers.windmill.entrypoints=websecure"
- "traefik.http.routers.windmill.tls=true"
- "traefik.http.routers.windmill.tls.certresolver=letsencrypt"
- "traefik.http.services.windmill.loadbalancer.server.port=8000"
# HTTPからHTTPSへのリダイレクト
- "traefik.http.routers.windmill-http.rule=Host(`windmill.keinafarm.net`)"
- "traefik.http.routers.windmill-http.entrypoints=web"
- "traefik.http.routers.windmill-http.middlewares=windmill-https-redirect"
- "traefik.http.middlewares.windmill-https-redirect.redirectscheme.scheme=https"
networks:
- traefik-net
- windmill-internal
logging: *default-logging logging: *default-logging
windmill_worker: windmill_worker:
image: ${WM_IMAGE} image: ${WM_IMAGE}
pull_policy: always pull_policy: if_not_present
deploy: deploy:
replicas: 3 replicas: 3
resources: resources:
limits: limits:
cpus: "1" cpus: "1"
memory: 2048M memory: 2048M
# for GB, use syntax '2Gi'
restart: unless-stopped restart: unless-stopped
# Uncomment to enable PID namespace isolation (recommended for security)
# Requires privileged mode for --mount-proc flag
# See: https://www.windmill.dev/docs/advanced/security_isolation
# privileged: true
environment: environment:
- DATABASE_URL=${DATABASE_URL} - DATABASE_URL=${DATABASE_URL}
- MODE=worker - MODE=worker
- WORKER_GROUP=default - WORKER_GROUP=default
# If running with non-root/non-windmill UID (e.g., user: "1001:1001"),
# add: - HOME=/tmp
# Uncomment to enable PID namespace isolation (requires privileged: true above)
# - ENABLE_UNSHARE_PID=true
depends_on: depends_on:
db: db:
condition: service_healthy condition: service_healthy
# to mount the worker folder to debug, KEEP_JOB_DIR=true and mount /tmp/windmill
volumes: volumes:
# mount the docker socket to allow to run docker containers from within the workers
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
- worker_dependency_cache:/tmp/windmill/cache - worker_dependency_cache:/tmp/windmill/cache
- worker_logs:/tmp/windmill/logs - worker_logs:/tmp/windmill/logs
# mount the windmill workspace directory for git sync workflow # WorkerからもGit同期が必要な場合に備えてマウント
- /home/akira/develop/windmill:/workspace - .:/workspace
networks:
- windmill-internal
logging: *default-logging logging: *default-logging
## This worker is specialized for "native" jobs. Native jobs run in-process and thus are much more lightweight than other jobs
windmill_worker_native: windmill_worker_native:
# Use ghcr.io/windmill-labs/windmill-ee:main for the ee
image: ${WM_IMAGE} image: ${WM_IMAGE}
pull_policy: always pull_policy: if_not_present
deploy: deploy:
replicas: 1 replicas: 1
resources: resources:
limits: limits:
cpus: "1" cpus: "1"
memory: 2048M memory: 2048M
# for GB, use syntax '2Gi'
restart: unless-stopped restart: unless-stopped
# Uncomment to enable PID namespace isolation (recommended for security)
# Requires privileged mode for --mount-proc flag
# See: https://www.windmill.dev/docs/advanced/security_isolation
# privileged: true
environment: environment:
- DATABASE_URL=${DATABASE_URL} - DATABASE_URL=${DATABASE_URL}
- MODE=worker - MODE=worker
- WORKER_GROUP=native - WORKER_GROUP=native
- NUM_WORKERS=8 - NUM_WORKERS=8
- SLEEP_QUEUE=200 - SLEEP_QUEUE=200
# Uncomment to enable PID namespace isolation (requires privileged: true above)
# - ENABLE_UNSHARE_PID=true
depends_on: depends_on:
db: db:
condition: service_healthy condition: service_healthy
volumes: volumes:
- worker_logs:/tmp/windmill/logs - worker_logs:/tmp/windmill/logs
logging: *default-logging networks:
# This worker is specialized for reports or scraping jobs. It is assigned the "reports" worker group which has an init script that installs chromium and can be targeted by using the "chromium" worker tag. - windmill-internal
# windmill_worker_reports:
# image: ${WM_IMAGE}
# pull_policy: always
# deploy:
# replicas: 1
# resources:
# limits:
# cpus: "1"
# memory: 2048M
# # for GB, use syntax '2Gi'
# restart: unless-stopped
# # Uncomment to enable PID namespace isolation (recommended for security)
# # Requires privileged mode for --mount-proc flag
# # See: https://www.windmill.dev/docs/advanced/security_isolation
# # privileged: true
# environment:
# - DATABASE_URL=${DATABASE_URL}
# - MODE=worker
# - WORKER_GROUP=reports
# # Uncomment to enable PID namespace isolation (requires privileged: true above)
# # - ENABLE_UNSHARE_PID=true
# depends_on:
# db:
# condition: service_healthy
# # to mount the worker folder to debug, KEEP_JOB_DIR=true and mount /tmp/windmill
# volumes:
# # mount the docker socket to allow to run docker containers from within the workers
# - /var/run/docker.sock:/var/run/docker.sock
# - worker_dependency_cache:/tmp/windmill/cache
# - worker_logs:/tmp/windmill/logs
# The indexer powers full-text job and log search, an EE feature.
windmill_indexer:
image: ${WM_IMAGE}
pull_policy: always
deploy:
replicas: 0 # set to 1 to enable full-text job and log search
restart: unless-stopped
expose:
- 8002
environment:
- PORT=8002
- DATABASE_URL=${DATABASE_URL}
- MODE=indexer
depends_on:
db:
condition: service_healthy
volumes:
- windmill_index:/tmp/windmill/search
- worker_logs:/tmp/windmill/logs
logging: *default-logging logging: *default-logging
# Combined extra services: LSP, Multiplayer, and Debugger
# Each service can be enabled/disabled via environment variables:
# - ENABLE_LSP=true (default) - Language Server Protocol for code intelligence
# - ENABLE_MULTIPLAYER=false - Real-time collaboration (Enterprise Edition)
# - ENABLE_DEBUGGER=false - Interactive debugging via DAP WebSocket
windmill_extra: windmill_extra:
image: ghcr.io/windmill-labs/windmill-extra:latest image: ghcr.io/windmill-labs/windmill-extra:${WM_VERSION}
pull_policy: always pull_policy: if_not_present
restart: unless-stopped restart: unless-stopped
expose: expose:
- 3001 # LSP - 3001
- 3002 # Multiplayer - 3002
- 3003 # Debugger - 3003
environment: environment:
- ENABLE_LSP=true - ENABLE_LSP=true
- ENABLE_MULTIPLAYER=false # Set to true to enable multiplayer (Enterprise Edition) - ENABLE_MULTIPLAYER=false
- ENABLE_DEBUGGER=true # Set to true to enable debugger - ENABLE_DEBUGGER=true
- DEBUGGER_PORT=3003 # Debugger service port - DEBUGGER_PORT=3003
- ENABLE_NSJAIL=false # Set to true for nsjail sandboxing (requires privileged: true) - ENABLE_NSJAIL=false
- REQUIRE_SIGNED_DEBUG_REQUESTS=false # Set to true to require JWT tokens for debug sessions - REQUIRE_SIGNED_DEBUG_REQUESTS=false
- WINDMILL_BASE_URL=http://windmill_server:8000 - WINDMILL_BASE_URL=http://windmill_server:8000
volumes: volumes:
- lsp_cache:/pyls/.cache - lsp_cache:/pyls/.cache
networks:
- windmill-internal
logging: *default-logging logging: *default-logging
labels:
# LSPなどのWebSocket用設定Caddyfileの代替
- "traefik.enable=true"
# LSPへのルーティング (/ws/* -> 3001)
- "traefik.http.routers.windmill-lsp.rule=Host(`windmill.keinafarm.net`) && PathPrefix(`/ws/`)"
- "traefik.http.routers.windmill-lsp.entrypoints=websecure"
- "traefik.http.routers.windmill-lsp.tls=true"
- "traefik.http.services.windmill-lsp.loadbalancer.server.port=3001"
# Debuggerへのルーティング (/ws_debug/* -> 3003)
- "traefik.http.routers.windmill-debug.rule=Host(`windmill.keinafarm.net`) && PathPrefix(`/ws_debug/`)"
- "traefik.http.routers.windmill-debug.entrypoints=websecure"
- "traefik.http.routers.windmill-debug.tls=true"
- "traefik.http.services.windmill-debug.loadbalancer.server.port=3003"
caddy: windmill_mcp:
image: ghcr.io/windmill-labs/caddy-l4:latest build:
context: ./mcp
dockerfile: Dockerfile
container_name: windmill_mcp
restart: unless-stopped restart: unless-stopped
# Configure the mounted Caddyfile and the exposed ports or use another reverse proxy if needed expose:
volumes: - 8001
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
# - ./certs:/certs # Provide custom certificate files like cert.pem and key.pem to enable HTTPS - See the corresponding section in the Caddyfile
ports:
# To change the exposed port, simply change 80:80 to <desired_port>:80. No other changes needed
- 80:80
- 25:25
# - 443:443 # Uncomment to enable HTTPS handling by Caddy
environment: environment:
- BASE_URL=":80" - WINDMILL_TOKEN=${WINDMILL_TOKEN}
# - BASE_URL=":443" # uncomment and comment line above to enable HTTPS via custom certificate and key files - WINDMILL_URL=https://windmill.keinafarm.net
# - BASE_URL=mydomain.com # Uncomment and comment line above to enable HTTPS handling by Caddy - WINDMILL_WORKSPACE=admins
- MCP_TRANSPORT=sse
- MCP_HOST=0.0.0.0
- MCP_PORT=8001
labels:
- "traefik.enable=true"
# HTTPS ルーター
- "traefik.http.routers.windmill-mcp.rule=Host(`windmill-mcp.keinafarm.net`)"
- "traefik.http.routers.windmill-mcp.entrypoints=websecure"
- "traefik.http.routers.windmill-mcp.tls=true"
- "traefik.http.routers.windmill-mcp.tls.certresolver=letsencrypt"
- "traefik.http.services.windmill-mcp.loadbalancer.server.port=8001"
# HTTP → HTTPS リダイレクト
- "traefik.http.routers.windmill-mcp-http.rule=Host(`windmill-mcp.keinafarm.net`)"
- "traefik.http.routers.windmill-mcp-http.entrypoints=web"
- "traefik.http.routers.windmill-mcp-http.middlewares=windmill-https-redirect"
networks:
- traefik-net
logging: *default-logging logging: *default-logging
volumes: volumes:
db_data: null db_data: null
worker_dependency_cache: null worker_dependency_cache: null
worker_logs: null worker_logs: null
worker_memory: null
windmill_index: null
lsp_cache: null lsp_cache: null
caddy_data: null

5
env.host Normal file
View File

@@ -0,0 +1,5 @@
WM_IMAGE=ghcr.io/windmill-labs/windmill:main
POSTGRES_PASSWORD=MyS3cur3P@ssw0rd!2024
DATABASE_URL=postgresql://postgres:${POSTGRES_PASSWORD}@db:5432/windmill
LOG_MAX_SIZE=20m
LOG_MAX_FILE=10

12
mcp/.env.example Normal file
View File

@@ -0,0 +1,12 @@
# Windmill MCP Server の設定
# このファイルを .env にコピーして値を設定してください
# Windmill のベース URLデフォルト: https://windmill.keinafarm.net
WINDMILL_URL=https://windmill.keinafarm.net
# Windmill API トークン(必須)
# Windmill の「設定 > トークン」から作成してください
WINDMILL_TOKEN=your_token_here
# 対象ワークスペース(デフォルト: admins
WINDMILL_WORKSPACE=admins

14
mcp/Dockerfile Normal file
View File

@@ -0,0 +1,14 @@
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY windmill_mcp.py .
ENV MCP_TRANSPORT=sse
ENV MCP_HOST=0.0.0.0
ENV MCP_PORT=8001
CMD ["python", "windmill_mcp.py"]

View File

@@ -0,0 +1,14 @@
{
"mcpServers": {
"windmill": {
"command": "python",
"args": ["windmill_mcp.py"],
"cwd": "/path/to/mcp",
"env": {
"WINDMILL_TOKEN": "your_api_token_here",
"WINDMILL_URL": "https://windmill.keinafarm.net",
"WINDMILL_WORKSPACE": "admins"
}
}
}
}

2
mcp/requirements.txt Normal file
View File

@@ -0,0 +1,2 @@
mcp>=1.0.0
httpx>=0.27.0

343
mcp/windmill_mcp.py Normal file
View File

@@ -0,0 +1,343 @@
#!/usr/bin/env python3
"""Windmill MCP Server - Claude が Windmill を直接操作できるようにする"""
import os
import json
import sys
import httpx
from mcp.server.fastmcp import FastMCP
WINDMILL_URL = os.environ.get("WINDMILL_URL", "https://windmill.keinafarm.net")
WINDMILL_TOKEN = os.environ.get("WINDMILL_TOKEN", "")
WINDMILL_WORKSPACE = os.environ.get("WINDMILL_WORKSPACE", "admins")
MCP_HOST = os.environ.get("MCP_HOST", "127.0.0.1")
MCP_PORT = int(os.environ.get("MCP_PORT", "8001"))
if not WINDMILL_TOKEN:
print("Error: WINDMILL_TOKEN 環境変数が設定されていません", file=sys.stderr)
sys.exit(1)
mcp = FastMCP("windmill", host=MCP_HOST, port=MCP_PORT)
def _headers() -> dict:
return {"Authorization": f"Bearer {WINDMILL_TOKEN}"}
def _api(path: str) -> str:
return f"{WINDMILL_URL}/api/w/{WINDMILL_WORKSPACE}/{path}"
@mcp.tool()
def windmill_list_flows(per_page: int = 20) -> str:
"""Windmill のフロー一覧を取得する
Args:
per_page: 取得件数最大100
"""
resp = httpx.get(
_api("flows/list"),
headers=_headers(),
params={"per_page": min(per_page, 100)},
timeout=30,
)
resp.raise_for_status()
flows = resp.json()
if not flows:
return "フローが見つかりませんでした"
lines = [
f"- {f['path']}: {f.get('summary', '(概要なし)')}" for f in flows
]
return "\n".join(lines)
@mcp.tool()
def windmill_get_flow(path: str) -> str:
"""指定したパスのフロー定義(スクリプト含む)を取得する
Args:
path: フローのパス (例: u/antigravity/git_sync)
"""
resp = httpx.get(_api(f"flows/get/{path}"), headers=_headers(), timeout=30)
resp.raise_for_status()
return json.dumps(resp.json(), indent=2, ensure_ascii=False)
@mcp.tool()
def windmill_run_flow(path: str, args: str = "{}") -> str:
"""フローをトリガーして実行する
Args:
path: フローのパス (例: u/antigravity/git_sync)
args: JSON形式の入力引数 (例: {"key": "value"})
"""
try:
args_dict = json.loads(args)
except json.JSONDecodeError as e:
return f"Error: argsのJSON形式が不正です: {e}"
resp = httpx.post(
_api(f"jobs/run/f/{path}"),
headers=_headers(),
json=args_dict,
timeout=30,
)
resp.raise_for_status()
job_id = resp.text.strip().strip('"')
return (
f"フローを開始しました。\n"
f"ジョブID: {job_id}\n"
f"詳細URL: {WINDMILL_URL}/run/{job_id}?workspace={WINDMILL_WORKSPACE}"
)
@mcp.tool()
def windmill_list_recent_jobs(
limit: int = 20,
success_only: bool = False,
failure_only: bool = False,
script_path_filter: str = "",
) -> str:
"""最近のジョブ一覧を取得する
Args:
limit: 取得件数最大100
success_only: Trueにすると成功ジョブのみ表示
failure_only: Trueにすると失敗ジョブのみ表示
script_path_filter: パスで絞り込む (例: u/antigravity/git_sync)
"""
params: dict = {"per_page": min(limit, 100)}
if success_only:
params["success"] = "true"
if failure_only:
params["success"] = "false"
if script_path_filter:
params["script_path_filter"] = script_path_filter
resp = httpx.get(_api("jobs/list"), headers=_headers(), params=params, timeout=30)
resp.raise_for_status()
jobs = resp.json()
if not jobs:
return "ジョブが見つかりませんでした"
lines = []
for j in jobs:
success = j.get("success")
if success is True:
status = "[OK]"
elif success is False:
status = "[FAIL]"
else:
status = "[RUNNING]"
path = j.get("script_path", "unknown")
started = (j.get("started_at") or "")[:19] or "pending"
job_id = j.get("id", "")
lines.append(f"{status} [{started}] {path} (ID: {job_id})")
return "\n".join(lines)
@mcp.tool()
def windmill_get_job_logs(job_id: str) -> str:
"""ジョブの詳細情報とログを取得する
Args:
job_id: ジョブのIDwindmill_list_recent_jobs で確認できる)
"""
resp = httpx.get(_api(f"jobs_u/get/{job_id}"), headers=_headers(), timeout=30)
resp.raise_for_status()
job = resp.json()
success = job.get("success")
if success is True:
state = "成功 [OK]"
elif success is False:
state = "失敗 [FAIL]"
else:
state = "実行中 [RUNNING]"
result_parts = [
f"ジョブID: {job_id}",
f"パス: {job.get('script_path', 'N/A')}",
f"状態: {state}",
f"開始: {job.get('started_at', 'N/A')}",
f"終了: {job.get('created_at', 'N/A')}",
]
log_resp = httpx.get(
_api(f"jobs_u/getlogs/{job_id}"), headers=_headers(), timeout=30
)
if log_resp.status_code == 200:
result_parts.append("\n--- ログ ---")
result_parts.append(log_resp.text)
result_val = job.get("result")
if result_val is not None:
result_parts.append("\n--- 実行結果 ---")
result_parts.append(
json.dumps(result_val, indent=2, ensure_ascii=False)
if isinstance(result_val, (dict, list))
else str(result_val)
)
return "\n".join(result_parts)
@mcp.tool()
def windmill_create_flow(path: str, summary: str, flow_definition: str, description: str = "") -> str:
"""新しいフローを作成する
Args:
path: フローのパス (例: u/admin/my_flow)
summary: フローの概要
flow_definition: フローの定義 (JSON形式の文字列)
description: フローの詳細説明 (省略可)
"""
try:
flow_value = json.loads(flow_definition)
except json.JSONDecodeError as e:
return f"Error: flow_definitionのJSON形式が不正です: {e}"
payload = {
"path": path,
"summary": summary,
"description": description,
"value": flow_value,
}
resp = httpx.post(
_api("flows/create"),
headers=_headers(),
json=payload,
timeout=30,
)
resp.raise_for_status()
return (
f"フローを作成しました。\n"
f"パス: {path}\n"
f"URL: {WINDMILL_URL}/flows/edit/{path}?workspace={WINDMILL_WORKSPACE}"
)
@mcp.tool()
def windmill_update_flow(path: str, summary: str, flow_definition: str, description: str = "") -> str:
"""既存のフローを更新する
Args:
path: フローのパス (例: u/admin/my_flow)
summary: フローの概要
flow_definition: フローの定義 (JSON形式の文字列)
description: フローの詳細説明 (省略可)
"""
try:
flow_value = json.loads(flow_definition)
except json.JSONDecodeError as e:
return f"Error: flow_definitionのJSON形式が不正です: {e}"
payload = {
"path": path,
"summary": summary,
"description": description,
"value": flow_value,
}
resp = httpx.post(
_api(f"flows/update/{path}"),
headers=_headers(),
json=payload,
timeout=30,
)
resp.raise_for_status()
return (
f"フローを更新しました。\n"
f"パス: {path}\n"
f"URL: {WINDMILL_URL}/flows/edit/{path}?workspace={WINDMILL_WORKSPACE}"
)
@mcp.tool()
def windmill_create_script(
path: str, language: str, content: str, summary: str = "", description: str = ""
) -> str:
"""新しいスクリプトを作成する(既存パスの場合は新バージョンを登録する)
Args:
path: スクリプトのパス (例: u/admin/my_script)
language: 言語 (python3, deno, bun, bash など)
content: スクリプトのソースコード
summary: スクリプトの概要 (省略可)
description: スクリプトの詳細説明 (省略可)
"""
payload = {
"path": path,
"language": language,
"content": content,
"summary": summary,
"description": description,
}
resp = httpx.post(
_api("scripts/create"),
headers=_headers(),
json=payload,
timeout=30,
)
resp.raise_for_status()
hash_val = resp.text.strip().strip('"')
return (
f"スクリプトを作成しました。\n"
f"パス: {path}\n"
f"ハッシュ: {hash_val}\n"
f"URL: {WINDMILL_URL}/scripts/edit/{path}?workspace={WINDMILL_WORKSPACE}"
)
@mcp.tool()
def windmill_list_scripts(per_page: int = 20) -> str:
"""Windmill のスクリプト一覧を取得する
Args:
per_page: 取得件数最大100
"""
resp = httpx.get(
_api("scripts/list"),
headers=_headers(),
params={"per_page": min(per_page, 100)},
timeout=30,
)
resp.raise_for_status()
scripts = resp.json()
if not scripts:
return "スクリプトが見つかりませんでした"
lines = [
f"- {s['path']} [{s.get('language', '?')}]: {s.get('summary', '(概要なし)')}"
for s in scripts
]
return "\n".join(lines)
@mcp.tool()
def windmill_get_script(path: str) -> str:
"""指定したパスのスクリプトのソースコードを取得する
Args:
path: スクリプトのパス (例: u/antigravity/test_git_sync)
"""
resp = httpx.get(_api(f"scripts/get/{path}"), headers=_headers(), timeout=30)
resp.raise_for_status()
script = resp.json()
result_parts = [
f"パス: {script.get('path', 'N/A')}",
f"言語: {script.get('language', 'N/A')}",
f"概要: {script.get('summary', 'N/A')}",
"",
"--- コード ---",
script.get("content", "(コードなし)"),
]
return "\n".join(result_parts)
if __name__ == "__main__":
transport = os.environ.get("MCP_TRANSPORT", "stdio")
mcp.run(transport=transport)

View File

@@ -5,19 +5,34 @@
set -e set -e
# 色付き出力
GREEN='\033[0;32m'
YELLOW='\033[1;33m'#!/bin/bash
# Windmill Workflow Git Auto-Sync Script for Gitea
# このスクリプトは、Windmillワークフローを自動的にGiteaにコミットプッシュします
set -e
# 色付き出力 # 色付き出力
GREEN='\033[0;32m' GREEN='\033[0;32m'
YELLOW='\033[1;33m' YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m' # No Color NC='\033[0m' # No Color
echo -e "${GREEN}=== Windmill Workflow Git Sync ===${NC}" echo -e "${GREEN}=== Windmill Workflow Git Sync (Gitea) ===${NC}"
# ディレクトリに移動 # 作業ディレクトリに移動
cd /home/akira/develop/windmill/workflows cd /workspace
# PATHを設定 # PATHを設定
export PATH=~/.npm-global/bin:$PATH export PATH=~/.npm-global/bin:$PATH
# Git設定safe.directoryエラー対策
git config --global --add safe.directory /workspace
git config --global user.email "bot@example.com"
git config --global user.name "Windmill Bot"
# Windmillから最新を取得 # Windmillから最新を取得
echo -e "${YELLOW}Pulling from Windmill...${NC}" echo -e "${YELLOW}Pulling from Windmill...${NC}"
wmill sync pull --skip-variables --skip-secrets --skip-resources --yes wmill sync pull --skip-variables --skip-secrets --skip-resources --yes
@@ -35,7 +50,17 @@ if [[ -n $(git status --porcelain) ]]; then
Synced workflows from Windmill workspace" Synced workflows from Windmill workspace"
echo -e "${GREEN}✓ Changes committed to Git${NC}"
# Giteaにプッシュ
echo -e "${YELLOW}Pushing to Gitea...${NC}"
git push origin main || {
echo -e "${RED}Failed to push to Gitea. Check credentials.${NC}"
# トークンや認証情報が設定されていない場合のヒント
echo -e "${YELLOW}Hint: Ensure you have set up git credentials or use a token in the remote URL.${NC}"
exit 1
}
echo -e "${GREEN}✓ Changes pushed to Gitea${NC}"
else else
echo -e "${GREEN}✓ No changes detected${NC}" echo -e "${GREEN}✓ No changes detected${NC}"
fi fi

View File

@@ -1,22 +1,68 @@
#!/bin/bash #!/bin/bash
set -x set -e
export WM_BASE_URL="http://windmill_server:8000"
export WM_WORKSPACE="admins"
export PATH=$HOME/.npm-global/bin:$PATH
echo "=== START SYNC ===" # 色付き出力
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m' # No Color
echo -e "${GREEN}=== Windmill Workflow Git Sync ===${NC}"
# リポジトリルート(コンテナ内: docker-compose.ymlの .:/workspace マウント)
REPO_ROOT="/workspace"
# wmill.yamlがあるディレクトリWindmill CLIはここで実行する
WMILL_DIR="${REPO_ROOT}/workflows"
# Windmill CLIのセットアップ
if ! command -v wmill &> /dev/null; then if ! command -v wmill &> /dev/null; then
echo -e "${YELLOW}Installing windmill-cli...${NC}"
npm install -g windmill-cli npm install -g windmill-cli
fi fi
wmill sync pull --token "$WM_TOKEN" --base-url "$WM_BASE_URL" --workspace "$WM_WORKSPACE" --skip-variables --skip-secrets --skip-resources --yes --verbose || exit 1 # 環境変数チェック
if [ -z "$WM_TOKEN" ]; then
echo -e "${RED}Error: WM_TOKEN is not set.${NC}"
exit 1
fi
# WM_BASE_URLはWindmill内で自動設定される場合があるが、念のため
: "${WM_BASE_URL:=http://windmill_server:8000}"
# Workspaceは環境変数または引数で
: "${WM_WORKSPACE:=admins}"
git config --global --add safe.directory /workspace # Git設定コンテナ内での一時設定
git config --global user.email "bot@example.com" git config --global --add safe.directory "$REPO_ROOT"
git config --global user.name "Bot" git config --global user.email "bot@keinafarm.net"
git config --global user.name "Windmill Bot"
git add . # 1. Windmill(DB) -> Local Diskwmill.yamlがあるディレクトリで実行
git commit -m "Auto-sync $(date)" || echo "No changes" echo -e "${YELLOW}Pulling from Windmill...${NC}"
cd "$WMILL_DIR"
wmill sync pull --token "$WM_TOKEN" --base-url "$WM_BASE_URL" --workspace "$WM_WORKSPACE" --skip-variables --skip-secrets --skip-resources --yes || exit 1
echo "=== END SYNC ===" # 2. Local Disk -> Git RemoteGitリポジトリルートに戻ってgit操作
cd "$REPO_ROOT"
if [[ -n $(git status --porcelain) ]]; then
echo -e "${YELLOW}Changes detected, committing to Git...${NC}"
git add -A
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
git commit -m "Auto-sync: ${TIMESTAMP}"
echo -e "${YELLOW}Pushing to Gitea...${NC}"
# リモートの変更を先に取り込むローカルPCからのpushがある場合に備えて
git pull --rebase origin main || {
echo -e "${RED}Failed to pull from remote. Trying push anyway...${NC}"
}
git push origin main || {
echo -e "${RED}Failed to push. Need credentials in git remote url or credential helper.${NC}"
echo -e "${YELLOW}Hint: git remote set-url origin https://<token>@gitea.keinafarm.net/...${NC}"
exit 1
}
echo -e "${GREEN}✓ Changes pushed to Gitea${NC}"
else
echo -e "${GREEN}✓ No changes detected${NC}"
fi
echo -e "${GREEN}=== Sync Complete ===${NC}"

View File

@@ -1,4 +1,3 @@
def main(): def main():
print("Hello from Git Sync Test") print("Hello from Git Sync Test")
return {"status": "success"} return {"status": "success"}