Compare commits

21 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
16 changed files with 954 additions and 288 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:
# 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 have caddy take care of automatic TLS

11
.gitignore vendored
View File

@@ -29,6 +29,9 @@ Thumbs.db
*.bak
*~
# MCP server config (contains API tokens)
.mcp.json
# Resolved markdown files (generated by editor)
*.resolved
*.resolved.*
@@ -46,3 +49,11 @@ workflows/.wmill/tmp/
!workflows/g/
!workflows/wmill.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` で任意ディレクトリを指定可能。

View File

@@ -1,233 +1,155 @@
# Windmill サーバー設定手順
# Windmill サーバー設定手順 (VPS移行版)
現在の状態:
- ディレクトリ: `/home/windmill/windmill`
- Giteaから正常にpull完了
本番環境VPSへのデプロイ手順です。
既にTraefikが稼働している環境`traefik-net` ネットワークが存在する環境)を前提としています。
## 前提条件
- サーバー上でTraefikが稼働しており、`traefik-net` ネットワークが存在すること。
- ドメイン `windmill.keinafarm.net` がサーバーのIPに向けられていること。
## ステップ1: リポジトリの準備
サーバー上の任意の場所(例: `/home/windmill/windmill`)にリポジトリをクローンします。
**重要**: WindmillのGit同期機能を使用するため、このディレクトリパスは重要です。
## ステップ1: docker-compose.yml の置き換え
```bash
cd /home/windmill/windmill
# 現在のファイルをバックアップ
cp docker-compose.yml docker-compose.yml.local.backup
# 新しいdocker-compose.ymlを作成
# (ダウンロードしたファイルの内容をコピー)
nano docker-compose.yml
mkdir -p /home/windmill
cd /home/windmill
git clone https://gitea.keinafarm.net/akira/windmill.git windmill
cd windmill
```
または、ローカルで修正してgit pushする方法
## ステップ2: 環境変数の設定
`.env` ファイルを作成し、本番用の設定を行います。
```bash
# ローカルで
cd /home/akira/develop/windmill
cp /path/to/downloaded/docker-compose.yml .
git add docker-compose.yml
git commit -m "Update docker-compose.yml for server deployment"
git push gitea main
# サーバーで
cd /home/windmill/windmill
git pull origin main
```
## ステップ2: .env ファイルの確認
```bash
cd /home/windmill/windmill
# .envファイルを編集
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
WM_IMAGE=ghcr.io/windmill-labs/windmill:main
DATABASE_URL=postgresql://postgres:YOUR_STRONG_PASSWORD@db:5432/windmill
LOG_MAX_SIZE=20m
LOG_MAX_FILE=10
```
⚠️ **重要**: `YOUR_STRONG_PASSWORD` を強力なパスワードに変更してください
## ステップ3: Traefikネットワークの確認
```bash
# traefik-netネットワークが存在するか確認
docker network ls | grep traefik-net
# もし存在しない場合は作成
docker network create traefik-net
```
## ステップ4: Caddyファイルの削除不要
```bash
cd /home/windmill/windmill
# Caddyfileは不要Traefikを使用
rm Caddyfile # または mv Caddyfile Caddyfile.bak
```
## ステップ5: sync_to_git.sh の更新
```bash
cd /home/windmill/windmill
# 既存のsync_to_git.shを新しいバージョンに置き換え
nano sync_to_git.sh
# (ダウンロードした内容をコピー)
chmod +x sync_to_git.sh
```
## ステップ6: Git認証情報の設定
プッシュ時に認証が必要な場合、以下のいずれかを設定:
### 方法A: Git Credential Helper推奨
```bash
cd /home/windmill/windmill
# 認証情報を保存
git config credential.helper store
# 一度手動でpushパスワード/トークンを入力)
git push origin main
# Username: akira
# Password: <Gitea access token>
# 以降は認証情報が保存されている
```
### 方法B: SSH鍵より安全
```bash
# SSH鍵を生成
ssh-keygen -t ed25519 -C "windmill@keinafarm.net"
# 公開鍵をGiteaに登録
cat ~/.ssh/id_ed25519.pub
# Gitea → Settings → SSH/GPG Keys → Add Key
# リモートURLをSSHに変更
cd /home/windmill/windmill
git remote set-url origin git@gitea.keinafarm.net:akira/windmil.git
```
## ステップ7: Windmillの起動
```bash
cd /home/windmill/windmill
# コンテナを起動
docker-compose up -d
# ログを確認
docker-compose logs -f windmill_server
# 状態確認
docker-compose ps
```
## ステップ8: 動作確認
## ステップ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
# APIバージョンチェック
curl -k https://windmill.keinafarm.net/api/version
# Windmillサーバーコンテナに入る
docker exec -it windmill_server /bin/bash
# ブラウザでアクセス
# https://windmill.keinafarm.net
```
## ステップ9: Windmill初期設定
1. ブラウザで `https://windmill.keinafarm.net` にアクセス
2. 初回セットアップウィザードに従う
3. 管理者アカウントを作成
4. Workspaceを作成例: `admins`
## ステップ10: Git同期スクリプトの設定Windmill内
1. Windmill UI → Scripts → New Script
2. 以下の内容でBashスクリプトを作成
```bash
#!/bin/bash
set -x
export WM_BASE_URL="http://windmill_server:8000"
export WM_WORKSPACE="admins" # あなたのworkspace名に変更
export PATH=$HOME/.npm-global/bin:$PATH
echo "=== START SYNC ==="
if ! command -v wmill &> /dev/null; then
# コンテナ内で実行windmill-cli をインストール
npm install -g windmill-cli
fi
cd /workspace
# wmill.yamlがあるディレクトリに移動して sync push
cd /workspace/workflows
wmill sync push \
--token "<4-1で取得したトークン>" \
--base-url "http://localhost:8000" \
--workspace admins \
--yes
wmill sync pull --token "$WM_TOKEN" --base-url "$WM_BASE_URL" --workspace "$WM_WORKSPACE" --skip-variables --skip-secrets --skip-resources --yes --verbose || exit 1
git config --global --add safe.directory /workspace
git config --global user.email "bot@example.com"
git config --global user.name "Bot"
git add .
git commit -m "Auto-sync $(date)" || echo "No changes"
git push origin main
echo "=== END SYNC ==="
exit
```
3. Schedule → New Schedule
- Cron: `*/15 * * * *` (15分ごと)
- Script: 上記で作成したスクリプト
> **注意**: `wmill sync push` はディスク→DBへの反映です。
> 逆に `wmill sync pull` はDB→ディスクへの反映です。
> スケジュールされた `git_sync` フローが `sync pull` を実行するため、
> **UIで直接スクリプトを修正した場合、次回の sync pull で正しくディスクにも反映されます。**
4. Variables → New Variable
- `WM_TOKEN`: Windmill APIトークンSettings → Tokensで作成
### 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** から、スケジュールが有効になっていることを確認してください。
---
## トラブルシューティング
### Traefikでアクセスできない
### ディスク上のファイルが古い内容に戻る
`git_sync` フローが `wmill sync pull`DB→ディスクを実行するため、UIで修正した内容がディスクに上書きされます。
スクリプトの修正は **Windmill UI上で直接編集** するのが確実です。
### git push が失敗する
```bash
# Traefikのログ確認
docker logs traefik
# windmill_serverがtraefik-netに接続されているか
docker network inspect traefik-net | grep windmill
```
### データベース接続エラー
```bash
# DBの状態確認
docker-compose ps db
docker-compose logs db
# 再起動
docker-compose restart db
```
### Gitプッシュが失敗する
```bash
# 認証情報の確認
cd /home/windmill/windmill
# サーバー上でリモートURLにトークンが含まれているか確認
cd ~/windmill
git remote -v
git config --list | grep credential
# 手動でテスト
git push origin main
# https://<user>:<token>@gitea.keinafarm.net/... の形式であること
```
## 重要なコマンド
### 開発環境(ローカル)での起動
ローカルで起動する場合は `docker-compose-dev.yml` を使用します:
```bash
# 再起動
docker-compose restart
# ログ確認
docker-compose logs -f
# 停止
docker-compose down
# 完全削除(データも削除)
docker-compose down -v
docker-compose -f docker-compose-dev.yml up -d
```
## セキュリティチェック
- [ ] `.env` のパスワードを変更
- [ ] Windmill管理者に強力なパスワード設定
- [ ] Git認証をSSH鍵またはcredential helperで設定
- [ ] Traefik Basic認証の追加検討必要に応じて
### ログ確認
```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.9"
x-logging: &default-logging
driver: "json-file"
options:
@@ -9,9 +7,9 @@ x-logging: &default-logging
networks:
traefik-net:
external: true # Traefik管理下のネットワーク
external: true # サーバー上の既存Traefikネットワーク
windmill-internal:
driver: bridge # Windmill内部通信用
driver: bridge
services:
db:
@@ -25,7 +23,7 @@ services:
expose:
- 5432
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
POSTGRES_DB: windmill
healthcheck:
test: [ "CMD-SHELL", "pg_isready -U postgres" ]
@@ -39,24 +37,29 @@ services:
windmill_server:
image: ${WM_IMAGE}
container_name: windmill_server
pull_policy: always
pull_policy: if_not_present
deploy:
replicas: 1
restart: unless-stopped
expose:
- 8000
- 2525
environment:
- DATABASE_URL=${DATABASE_URL}
- 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:
db:
condition: service_healthy
volumes:
- worker_logs:/tmp/windmill/logs
- /home/windmill/windmill:/workspace
# Git同期のために、カレントディレクトリリポジトリルート/workspaceにマウント
# これにより、コンテナ内から .git ディレクトリにアクセス可能となり、git pushが可能になる
- .:/workspace
labels:
# Traefik設定
- "traefik.enable=true"
# HTTPSルーター
- "traefik.http.routers.windmill.rule=Host(`windmill.keinafarm.net`)"
@@ -76,7 +79,7 @@ services:
windmill_worker:
image: ${WM_IMAGE}
pull_policy: always
pull_policy: if_not_present
deploy:
replicas: 3
resources:
@@ -95,14 +98,15 @@ services:
- /var/run/docker.sock:/var/run/docker.sock
- worker_dependency_cache:/tmp/windmill/cache
- worker_logs:/tmp/windmill/logs
- /home/windmill/windmill:/workspace
# WorkerからもGit同期が必要な場合に備えてマウント
- .:/workspace
networks:
- windmill-internal
logging: *default-logging
windmill_worker_native:
image: ${WM_IMAGE}
pull_policy: always
pull_policy: if_not_present
deploy:
replicas: 1
resources:
@@ -125,31 +129,9 @@ services:
- windmill-internal
logging: *default-logging
windmill_indexer:
image: ${WM_IMAGE}
pull_policy: always
deploy:
replicas: 0 # 必要に応じて1に変更
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
networks:
- windmill-internal
logging: *default-logging
windmill_extra:
image: ghcr.io/windmill-labs/windmill-extra:latest
pull_policy: always
image: ghcr.io/windmill-labs/windmill-extra:${WM_VERSION}
pull_policy: if_not_present
restart: unless-stopped
expose:
- 3001
@@ -168,15 +150,53 @@ services:
networks:
- windmill-internal
logging: *default-logging
# Caddyは使わないTraefikを使用
# caddy:
# deploy:
# replicas: 0
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"
windmill_mcp:
build:
context: ./mcp
dockerfile: Dockerfile
container_name: windmill_mcp
restart: unless-stopped
expose:
- 8001
environment:
- WINDMILL_TOKEN=${WINDMILL_TOKEN}
- WINDMILL_URL=https://windmill.keinafarm.net
- 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
volumes:
db_data: null
worker_dependency_cache: null
worker_logs: null
worker_memory: null
windmill_index: null
lsp_cache: null

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

@@ -50,10 +50,13 @@ if [[ -n $(git status --porcelain) ]]; then
Synced workflows from Windmill workspace"
# 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
}
@@ -63,36 +66,3 @@ else
fi
echo -e "${GREEN}=== Sync Complete ===${NC}"
NC='\033[0m' # No Color
echo -e "${GREEN}=== Windmill Workflow Git Sync ===${NC}"
# ディレクトリに移動
cd /home/akira/develop/windmill/workflows
# PATHを設定
export PATH=~/.npm-global/bin:$PATH
# Windmillから最新を取得
echo -e "${YELLOW}Pulling from Windmill...${NC}"
wmill sync pull --skip-variables --skip-secrets --skip-resources --yes
# 変更があるか確認
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}
Synced workflows from Windmill workspace"
echo -e "${GREEN}✓ Changes committed to Git${NC}"
else
echo -e "${GREEN}✓ No changes detected${NC}"
fi
echo -e "${GREEN}=== Sync Complete ===${NC}"

View File

@@ -1,22 +1,68 @@
#!/bin/bash
set -x
export WM_BASE_URL="http://windmill_server:8000"
export WM_WORKSPACE="admins"
export PATH=$HOME/.npm-global/bin:$PATH
set -e
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
echo -e "${YELLOW}Installing windmill-cli...${NC}"
npm install -g windmill-cli
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 config --global user.email "bot@example.com"
git config --global user.name "Bot"
# Git設定コンテナ内での一時設定
git config --global --add safe.directory "$REPO_ROOT"
git config --global user.email "bot@keinafarm.net"
git config --global user.name "Windmill Bot"
git add .
git commit -m "Auto-sync $(date)" || echo "No changes"
# 1. Windmill(DB) -> Local Diskwmill.yamlがあるディレクトリで実行
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():
print("Hello from Git Sync Test")
return {"status": "success"}