mirror of
https://github.com/Xe138/windmill-git-sync.git
synced 2026-04-01 17:27:23 -04:00
Initial commit: Windmill Git Sync service
Add containerized service for syncing Windmill workspaces to Git repositories. Features: - Flask webhook server for triggering syncs from Windmill - wmill CLI integration for pulling workspace content - Automated Git commits and push to remote repository - Network-isolated (only accessible within Docker network) - Designed to integrate with existing Windmill docker-compose files Key components: - Docker container with Python 3.11, wmill CLI, Git, and Flask - Sync engine with error handling and logging - External volume support for persistent workspace data - Comprehensive documentation (README.md and CLAUDE.md)
This commit is contained in:
14
.env.example
Normal file
14
.env.example
Normal file
@@ -0,0 +1,14 @@
|
||||
# Windmill Configuration
|
||||
WINDMILL_BASE_URL=http://windmill_server:8000
|
||||
WINDMILL_TOKEN=your-windmill-token-here
|
||||
WINDMILL_WORKSPACE=home
|
||||
|
||||
# Workspace Volume (external Docker volume name)
|
||||
WORKSPACE_VOLUME=windmill-workspace-data
|
||||
|
||||
# Git Configuration
|
||||
GIT_REMOTE_URL=https://github.com/username/repo.git
|
||||
GIT_TOKEN=your-github-pat-here
|
||||
GIT_BRANCH=main
|
||||
GIT_USER_NAME=Windmill Git Sync
|
||||
GIT_USER_EMAIL=windmill@example.com
|
||||
37
.gitignore
vendored
Normal file
37
.gitignore
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
# Environment variables
|
||||
.env
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# IDEs
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
|
||||
# Docker
|
||||
.dockerignore
|
||||
165
CLAUDE.md
Normal file
165
CLAUDE.md
Normal file
@@ -0,0 +1,165 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Project Overview
|
||||
|
||||
This is a containerized service for synchronizing Windmill workspaces to Git repositories. The service provides a Flask webhook server that Windmill can call to trigger automated backups of workspace content to a remote Git repository.
|
||||
|
||||
### Architecture
|
||||
|
||||
The system consists of three main components:
|
||||
|
||||
1. **Flask Web Server** (`app/server.py`): Lightweight HTTP server that exposes webhook endpoints for triggering syncs and health checks. Only accessible within the Docker network (not exposed to host).
|
||||
|
||||
2. **Sync Engine** (`app/sync.py`): Core logic that orchestrates the sync process:
|
||||
- Pulls workspace content from Windmill using the `wmill` CLI
|
||||
- Manages Git repository state (init on first run, subsequent updates)
|
||||
- Commits changes and pushes to remote Git repository with PAT authentication
|
||||
- Handles error cases and provides detailed logging
|
||||
|
||||
3. **Docker Container**: Bundles Python 3.11, wmill CLI, Git, and the Flask application. Uses volume mounts for persistent workspace storage.
|
||||
|
||||
### Key Design Decisions
|
||||
|
||||
- **Integrated with Windmill docker-compose**: This service is designed to be added as an additional service in your existing Windmill docker-compose file. It shares the same Docker network and can reference Windmill services directly (e.g., `windmill_server`).
|
||||
- **Network isolation**: Service uses `expose` instead of `ports` - accessible only within Docker network, not from host machine. No authentication needed since it's isolated.
|
||||
- **Webhook-only triggering**: Sync happens only when explicitly triggered via HTTP POST to `/sync`. This gives Windmill full control over backup timing via scheduled flows.
|
||||
- **HTTPS + Personal Access Token**: Git authentication uses PAT injected into HTTPS URL (format: `https://TOKEN@github.com/user/repo.git`). No SSH key management required.
|
||||
- **Stateless operation**: Each sync is independent. The container can be restarted without losing state (workspace data persists in Docker volume).
|
||||
- **Single workspace focus**: Designed to sync one Windmill workspace per container instance. For multiple workspaces, run multiple containers with different configurations.
|
||||
|
||||
## Common Development Commands
|
||||
|
||||
### Build and Run
|
||||
|
||||
```bash
|
||||
# Build the Docker image
|
||||
docker-compose build
|
||||
|
||||
# Start the service
|
||||
docker-compose up -d
|
||||
|
||||
# View logs
|
||||
docker-compose logs -f
|
||||
|
||||
# Stop the service
|
||||
docker-compose down
|
||||
```
|
||||
|
||||
### Testing
|
||||
|
||||
```bash
|
||||
# Test the sync manually (from inside container)
|
||||
docker-compose exec windmill-git-sync python app/sync.py
|
||||
|
||||
# Test webhook endpoint (from another container in the network)
|
||||
docker-compose exec windmill_server curl -X POST http://windmill-git-sync:8080/sync
|
||||
|
||||
# Health check (from another container in the network)
|
||||
docker-compose exec windmill_server curl http://windmill-git-sync:8080/health
|
||||
```
|
||||
|
||||
### Development Workflow
|
||||
|
||||
```bash
|
||||
# Edit code locally, rebuild and restart
|
||||
docker-compose down
|
||||
docker-compose up -d --build
|
||||
|
||||
# View live logs during testing
|
||||
docker-compose logs -f windmill-git-sync
|
||||
|
||||
# Access container shell for debugging
|
||||
docker-compose exec windmill-git-sync /bin/bash
|
||||
|
||||
# Inspect workspace directory
|
||||
docker-compose exec windmill-git-sync ls -la /workspace
|
||||
```
|
||||
|
||||
## Environment Configuration
|
||||
|
||||
All configuration is done via `.env` file (copy from `.env.example`). Required variables:
|
||||
|
||||
- `WINDMILL_TOKEN`: API token from Windmill for workspace access
|
||||
- `WORKSPACE_VOLUME`: External Docker volume name for persistent workspace storage (default: `windmill-workspace-data`)
|
||||
- `GIT_REMOTE_URL`: HTTPS URL of Git repository (e.g., `https://github.com/user/repo.git`)
|
||||
- `GIT_TOKEN`: Personal Access Token with repo write permissions
|
||||
|
||||
### Docker Compose Integration
|
||||
|
||||
The `docker-compose.yml` file contains a service definition meant to be **added to your existing Windmill docker-compose file**, not run standalone. The service:
|
||||
- Does not declare its own network (uses the implicit network from the parent compose file)
|
||||
- Assumes a Windmill service named `windmill_server` exists in the same compose file
|
||||
- Uses `depends_on: windmill_server` to ensure proper startup order
|
||||
- Requires an external Docker volume specified in `WORKSPACE_VOLUME` env var (created via `docker volume create windmill-workspace-data`)
|
||||
|
||||
## Code Structure
|
||||
|
||||
```
|
||||
app/
|
||||
├── server.py # Flask application with /health and /sync endpoints
|
||||
└── sync.py # Core sync logic (wmill pull → git commit → push)
|
||||
```
|
||||
|
||||
### Important Functions
|
||||
|
||||
- `sync.sync_windmill_to_git()`: Main entry point for sync operation. Returns dict with `success` bool and `message` string.
|
||||
- `sync.validate_config()`: Checks required env vars are set. Raises ValueError if missing.
|
||||
- `sync.run_wmill_sync()`: Executes `wmill sync pull` command with proper environment variables.
|
||||
- `sync.commit_and_push_changes()`: Stages all changes, commits with automated message, and pushes to remote.
|
||||
|
||||
### Error Handling
|
||||
|
||||
The sync engine uses a try/except pattern that always returns a result dict, never raises to the web server. This ensures webhook requests always get a proper HTTP response with error details in JSON.
|
||||
|
||||
## Git Workflow
|
||||
|
||||
When making changes to this codebase:
|
||||
|
||||
1. Changes are tracked in the project's own Git repository (not the Windmill workspace backup repo)
|
||||
2. The service manages commits to the **remote backup repository** specified in `GIT_REMOTE_URL`
|
||||
3. Commits to the backup repo use the automated format: "Automated Windmill workspace backup - {workspace_name}"
|
||||
|
||||
## Network Architecture
|
||||
|
||||
This service is designed to be added to your existing Windmill docker-compose file. When added, all services share the same Docker Compose network automatically.
|
||||
|
||||
Expected service topology within the same docker-compose file:
|
||||
|
||||
```
|
||||
Services in docker-compose.yml:
|
||||
├── windmill_server (Windmill API server on port 8000)
|
||||
├── windmill_worker (Windmill workers)
|
||||
├── postgres (Database)
|
||||
└── windmill-git-sync (this service on port 8080)
|
||||
```
|
||||
|
||||
The service references `windmill_server` via `WINDMILL_BASE_URL=http://windmill_server:8000`. If your Windmill server service has a different name, update `WINDMILL_BASE_URL` in `.env`.
|
||||
|
||||
## Extending the Service
|
||||
|
||||
### Adding Scheduled Syncs
|
||||
|
||||
To add cron-based scheduling in addition to webhooks:
|
||||
|
||||
1. Install `APScheduler` in `requirements.txt`
|
||||
2. Add scheduler initialization in `server.py`
|
||||
3. Update configuration to support `SYNC_SCHEDULE` env var (e.g., `0 */6 * * *` for every 6 hours)
|
||||
|
||||
### Adding Slack/Discord Notifications
|
||||
|
||||
To notify on sync completion:
|
||||
|
||||
1. Add `slack-sdk` or `discord-webhook` to `requirements.txt`
|
||||
2. Add notification function in `sync.py`
|
||||
3. Call notification function in `sync_windmill_to_git()` after successful push
|
||||
4. Add webhook URL as env var in `.env` and `docker-compose.yml`
|
||||
|
||||
### Supporting SSH Authentication
|
||||
|
||||
To support SSH keys instead of PAT:
|
||||
|
||||
1. Update `docker-compose.yml` to mount SSH key: `~/.ssh/id_rsa:/root/.ssh/id_rsa:ro`
|
||||
2. Add logic in `sync.get_authenticated_url()` to detect SSH vs HTTPS URLs
|
||||
3. Configure Git to use SSH: `git config core.sshCommand "ssh -i /root/.ssh/id_rsa"`
|
||||
29
Dockerfile
Normal file
29
Dockerfile
Normal file
@@ -0,0 +1,29 @@
|
||||
FROM python:3.11-slim
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install system dependencies
|
||||
RUN apt-get update && apt-get install -y \
|
||||
git \
|
||||
curl \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install wmill CLI
|
||||
RUN curl -L https://github.com/windmill-labs/windmill/releases/latest/download/wmill-linux-amd64 -o /usr/local/bin/wmill \
|
||||
&& chmod +x /usr/local/bin/wmill
|
||||
|
||||
# Copy requirements and install Python dependencies
|
||||
COPY requirements.txt .
|
||||
RUN pip install --no-cache-dir -r requirements.txt
|
||||
|
||||
# Copy application code
|
||||
COPY app/ ./app/
|
||||
|
||||
# Create workspace directory
|
||||
RUN mkdir -p /workspace
|
||||
|
||||
# Expose port for webhook server
|
||||
EXPOSE 8080
|
||||
|
||||
# Run the Flask server
|
||||
CMD ["python", "-u", "app/server.py"]
|
||||
91
README.md
Normal file
91
README.md
Normal file
@@ -0,0 +1,91 @@
|
||||
# Windmill Git Sync
|
||||
|
||||
A containerized service for syncing Windmill workspaces to Git repositories via webhook triggers.
|
||||
|
||||
## Overview
|
||||
|
||||
This service provides automated backup of Windmill workspaces to Git. It runs a lightweight Flask web server that responds to webhook requests from Windmill, syncing the workspace content using the `wmill` CLI and pushing changes to a remote Git repository.
|
||||
|
||||
## Features
|
||||
|
||||
- **Webhook-triggered sync**: Windmill can trigger backups via HTTP POST requests
|
||||
- **Dockerized**: Runs as a container in the same network as Windmill
|
||||
- **Git integration**: Automatic commits and pushes to remote repository
|
||||
- **Authentication**: Supports Personal Access Token (PAT) authentication for Git
|
||||
- **Health checks**: Built-in health endpoint for monitoring
|
||||
|
||||
## Quick Start
|
||||
|
||||
This service is designed to be added to your existing Windmill docker-compose file.
|
||||
|
||||
1. Copy the example environment file:
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
2. Edit `.env` with your configuration:
|
||||
- Set `WINDMILL_TOKEN` to your Windmill API token
|
||||
- Set `GIT_REMOTE_URL` to your Git repository URL
|
||||
- Set `GIT_TOKEN` to your Git Personal Access Token
|
||||
- Set `WORKSPACE_VOLUME` to an external Docker volume name
|
||||
|
||||
3. Create the external volume:
|
||||
```bash
|
||||
docker volume create windmill-workspace-data
|
||||
```
|
||||
|
||||
4. Add the `windmill-git-sync` service block from `docker-compose.yml` to your existing Windmill docker-compose file.
|
||||
|
||||
5. Build and start the service:
|
||||
```bash
|
||||
docker-compose up -d windmill-git-sync
|
||||
```
|
||||
|
||||
6. Trigger a sync from Windmill (see Integration section below) or test from another container:
|
||||
```bash
|
||||
docker-compose exec windmill_server curl -X POST http://windmill-git-sync:8080/sync
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
All configuration is done via environment variables in `.env`:
|
||||
|
||||
| Variable | Required | Description |
|
||||
|----------|----------|-------------|
|
||||
| `WINDMILL_BASE_URL` | Yes | URL of Windmill instance (e.g., `http://windmill:8000`) |
|
||||
| `WINDMILL_TOKEN` | Yes | Windmill API token for authentication |
|
||||
| `WINDMILL_WORKSPACE` | No | Workspace name (default: `default`) |
|
||||
| `WORKSPACE_VOLUME` | Yes | External Docker volume name for workspace data |
|
||||
| `GIT_REMOTE_URL` | Yes | HTTPS Git repository URL |
|
||||
| `GIT_TOKEN` | Yes | Git Personal Access Token |
|
||||
| `GIT_BRANCH` | No | Branch to push to (default: `main`) |
|
||||
| `GIT_USER_NAME` | No | Git commit author name |
|
||||
| `GIT_USER_EMAIL` | No | Git commit author email |
|
||||
|
||||
## API Endpoints
|
||||
|
||||
This service is only accessible within the Docker network (not exposed to the host).
|
||||
|
||||
- `GET /health` - Health check endpoint
|
||||
- `POST /sync` - Trigger a workspace sync to Git
|
||||
|
||||
## Integration with Windmill
|
||||
|
||||
Create a scheduled flow or script in Windmill to trigger backups:
|
||||
|
||||
```typescript
|
||||
export async function main() {
|
||||
const response = await fetch('http://windmill-git-sync:8080/sync', {
|
||||
method: 'POST'
|
||||
});
|
||||
return await response.json();
|
||||
}
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
See [CLAUDE.md](CLAUDE.md) for development instructions and architecture details.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
54
app/server.py
Normal file
54
app/server.py
Normal file
@@ -0,0 +1,54 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Flask server for receiving webhook triggers from Windmill to sync workspace to Git.
|
||||
Internal service - not exposed outside Docker network.
|
||||
"""
|
||||
import logging
|
||||
from flask import Flask, jsonify
|
||||
from sync import sync_windmill_to_git
|
||||
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
@app.route('/health', methods=['GET'])
|
||||
def health():
|
||||
"""Health check endpoint."""
|
||||
return jsonify({'status': 'healthy'}), 200
|
||||
|
||||
|
||||
@app.route('/sync', methods=['POST'])
|
||||
def trigger_sync():
|
||||
"""
|
||||
Trigger a sync from Windmill workspace to Git repository.
|
||||
This endpoint is only accessible within the Docker network.
|
||||
"""
|
||||
logger.info("Sync triggered via webhook")
|
||||
|
||||
try:
|
||||
result = sync_windmill_to_git()
|
||||
|
||||
if result['success']:
|
||||
logger.info(f"Sync completed successfully: {result['message']}")
|
||||
return jsonify(result), 200
|
||||
else:
|
||||
logger.error(f"Sync failed: {result['message']}")
|
||||
return jsonify(result), 500
|
||||
|
||||
except Exception as e:
|
||||
logger.exception("Unexpected error during sync")
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'message': f'Sync failed with error: {str(e)}'
|
||||
}), 500
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
logger.info("Starting Windmill Git Sync server on port 8080")
|
||||
app.run(host='0.0.0.0', port=8080, debug=False)
|
||||
176
app/sync.py
Normal file
176
app/sync.py
Normal file
@@ -0,0 +1,176 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Core sync logic for pulling Windmill workspace and pushing to Git.
|
||||
"""
|
||||
import os
|
||||
import subprocess
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from git import Repo, GitCommandError
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Configuration from environment variables
|
||||
WORKSPACE_DIR = Path('/workspace')
|
||||
WINDMILL_BASE_URL = os.getenv('WINDMILL_BASE_URL', 'http://windmill:8000')
|
||||
WINDMILL_TOKEN = os.getenv('WINDMILL_TOKEN', '')
|
||||
WINDMILL_WORKSPACE = os.getenv('WINDMILL_WORKSPACE', 'default')
|
||||
GIT_REMOTE_URL = os.getenv('GIT_REMOTE_URL', '')
|
||||
GIT_TOKEN = os.getenv('GIT_TOKEN', '')
|
||||
GIT_BRANCH = os.getenv('GIT_BRANCH', 'main')
|
||||
GIT_USER_NAME = os.getenv('GIT_USER_NAME', 'Windmill Git Sync')
|
||||
GIT_USER_EMAIL = os.getenv('GIT_USER_EMAIL', 'windmill@example.com')
|
||||
|
||||
|
||||
def validate_config():
|
||||
"""Validate required configuration is present."""
|
||||
missing = []
|
||||
|
||||
if not WINDMILL_TOKEN:
|
||||
missing.append('WINDMILL_TOKEN')
|
||||
if not GIT_REMOTE_URL:
|
||||
missing.append('GIT_REMOTE_URL')
|
||||
if not GIT_TOKEN:
|
||||
missing.append('GIT_TOKEN')
|
||||
|
||||
if missing:
|
||||
raise ValueError(f"Missing required environment variables: {', '.join(missing)}")
|
||||
|
||||
|
||||
def get_authenticated_url(url: str, token: str) -> str:
|
||||
"""Insert token into HTTPS Git URL for authentication."""
|
||||
if url.startswith('https://'):
|
||||
# Format: https://TOKEN@github.com/user/repo.git
|
||||
return url.replace('https://', f'https://{token}@')
|
||||
return url
|
||||
|
||||
|
||||
def run_wmill_sync():
|
||||
"""Run wmill sync to pull workspace from Windmill."""
|
||||
logger.info(f"Syncing Windmill workspace '{WINDMILL_WORKSPACE}' from {WINDMILL_BASE_URL}")
|
||||
|
||||
env = os.environ.copy()
|
||||
env['WM_BASE_URL'] = WINDMILL_BASE_URL
|
||||
env['WM_TOKEN'] = WINDMILL_TOKEN
|
||||
env['WM_WORKSPACE'] = WINDMILL_WORKSPACE
|
||||
|
||||
try:
|
||||
# Run wmill sync in the workspace directory
|
||||
result = subprocess.run(
|
||||
['wmill', 'sync', 'pull', '--yes'],
|
||||
cwd=WORKSPACE_DIR,
|
||||
env=env,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
check=True
|
||||
)
|
||||
|
||||
logger.info("Windmill sync completed successfully")
|
||||
logger.debug(f"wmill output: {result.stdout}")
|
||||
|
||||
return True
|
||||
|
||||
except subprocess.CalledProcessError as e:
|
||||
logger.error(f"wmill sync failed: {e.stderr}")
|
||||
raise RuntimeError(f"Failed to sync from Windmill: {e.stderr}")
|
||||
|
||||
|
||||
def init_or_update_git_repo():
|
||||
"""Initialize Git repository or open existing one."""
|
||||
git_dir = WORKSPACE_DIR / '.git'
|
||||
|
||||
if git_dir.exists():
|
||||
logger.info("Opening existing Git repository")
|
||||
repo = Repo(WORKSPACE_DIR)
|
||||
else:
|
||||
logger.info("Initializing new Git repository")
|
||||
repo = Repo.init(WORKSPACE_DIR)
|
||||
|
||||
# Configure user
|
||||
repo.config_writer().set_value("user", "name", GIT_USER_NAME).release()
|
||||
repo.config_writer().set_value("user", "email", GIT_USER_EMAIL).release()
|
||||
|
||||
return repo
|
||||
|
||||
|
||||
def commit_and_push_changes(repo: Repo):
|
||||
"""Commit changes and push to remote Git repository."""
|
||||
# Check if there are any changes
|
||||
if not repo.is_dirty(untracked_files=True):
|
||||
logger.info("No changes to commit")
|
||||
return False
|
||||
|
||||
# Stage all changes
|
||||
repo.git.add(A=True)
|
||||
|
||||
# Create commit
|
||||
commit_message = f"Automated Windmill workspace backup - {WINDMILL_WORKSPACE}"
|
||||
repo.index.commit(commit_message)
|
||||
logger.info(f"Created commit: {commit_message}")
|
||||
|
||||
# Configure remote with authentication
|
||||
authenticated_url = get_authenticated_url(GIT_REMOTE_URL, GIT_TOKEN)
|
||||
|
||||
try:
|
||||
# Check if remote exists
|
||||
if 'origin' in [remote.name for remote in repo.remotes]:
|
||||
origin = repo.remote('origin')
|
||||
origin.set_url(authenticated_url)
|
||||
else:
|
||||
origin = repo.create_remote('origin', authenticated_url)
|
||||
|
||||
# Push to remote
|
||||
logger.info(f"Pushing to {GIT_REMOTE_URL} (branch: {GIT_BRANCH})")
|
||||
origin.push(refspec=f'HEAD:{GIT_BRANCH}', force=False)
|
||||
logger.info("Push completed successfully")
|
||||
|
||||
return True
|
||||
|
||||
except GitCommandError as e:
|
||||
logger.error(f"Git push failed: {str(e)}")
|
||||
raise RuntimeError(f"Failed to push to Git remote: {str(e)}")
|
||||
|
||||
|
||||
def sync_windmill_to_git():
|
||||
"""
|
||||
Main sync function: pulls from Windmill, commits, and pushes to Git.
|
||||
|
||||
Returns:
|
||||
dict: Result with 'success' boolean and 'message' string
|
||||
"""
|
||||
try:
|
||||
# Validate configuration
|
||||
validate_config()
|
||||
|
||||
# Pull from Windmill
|
||||
run_wmill_sync()
|
||||
|
||||
# Initialize/update Git repo
|
||||
repo = init_or_update_git_repo()
|
||||
|
||||
# Commit and push changes
|
||||
has_changes = commit_and_push_changes(repo)
|
||||
|
||||
if has_changes:
|
||||
message = f"Successfully synced workspace '{WINDMILL_WORKSPACE}' to Git"
|
||||
else:
|
||||
message = "Sync completed - no changes to commit"
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'message': message
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.exception("Sync failed")
|
||||
return {
|
||||
'success': False,
|
||||
'message': str(e)
|
||||
}
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Allow running sync directly for testing
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
result = sync_windmill_to_git()
|
||||
print(result)
|
||||
25
docker-compose.yml
Normal file
25
docker-compose.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
services:
|
||||
# ... existing Windmill services (windmill_server, windmill_worker, postgres, etc.) ...
|
||||
|
||||
windmill-git-sync:
|
||||
build: .
|
||||
container_name: windmill-git-sync
|
||||
expose:
|
||||
- "8080"
|
||||
volumes:
|
||||
- ${WORKSPACE_VOLUME}:/workspace
|
||||
environment:
|
||||
# Windmill connection
|
||||
- WINDMILL_BASE_URL=http://windmill_server:8000
|
||||
- WINDMILL_TOKEN=${WINDMILL_TOKEN}
|
||||
- WINDMILL_WORKSPACE=${WINDMILL_WORKSPACE:-default}
|
||||
|
||||
# Git configuration
|
||||
- GIT_REMOTE_URL=${GIT_REMOTE_URL}
|
||||
- GIT_TOKEN=${GIT_TOKEN}
|
||||
- GIT_BRANCH=${GIT_BRANCH:-main}
|
||||
- GIT_USER_NAME=${GIT_USER_NAME:-Windmill Git Sync}
|
||||
- GIT_USER_EMAIL=${GIT_USER_EMAIL:-windmill@example.com}
|
||||
restart: unless-stopped
|
||||
depends_on:
|
||||
- windmill_server
|
||||
4
requirements.txt
Normal file
4
requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
Flask==3.0.0
|
||||
GitPython==3.1.40
|
||||
requests==2.31.0
|
||||
python-dotenv==1.0.0
|
||||
31
setup.sh
Executable file
31
setup.sh
Executable file
@@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
# Setup script for windmill-git-sync
|
||||
|
||||
set -e
|
||||
|
||||
echo "Setting up Windmill Git Sync..."
|
||||
|
||||
# Create .env file if it doesn't exist
|
||||
if [ ! -f .env ]; then
|
||||
echo "Creating .env file from template..."
|
||||
cp .env.example .env
|
||||
echo "⚠️ Please edit .env with your configuration"
|
||||
else
|
||||
echo "✓ .env file already exists"
|
||||
fi
|
||||
|
||||
# Create Docker volume if it doesn't exist
|
||||
if ! docker volume inspect windmill-workspace-data >/dev/null 2>&1; then
|
||||
echo "Creating windmill-workspace-data Docker volume..."
|
||||
docker volume create windmill-workspace-data
|
||||
echo "✓ Volume created"
|
||||
else
|
||||
echo "✓ windmill-workspace-data already exists"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Setup complete! Next steps:"
|
||||
echo "1. Edit .env with your Windmill and Git configuration"
|
||||
echo "2. Add the windmill-git-sync service block from docker-compose.yml to your Windmill docker-compose file"
|
||||
echo "3. Run: docker-compose up -d windmill-git-sync"
|
||||
echo "4. Test from within Docker network: docker-compose exec windmill_server curl -X POST http://windmill-git-sync:8080/sync"
|
||||
Reference in New Issue
Block a user